File indexing completed on 2024-09-28 07:02:17
0001
0002
0003
0004
0005
0006
0007 #pragma once
0008
0009
0010 #include <algorithm>
0011 #include <fstream>
0012 #include <iostream>
0013 #include <string>
0014 #include <utility>
0015 #include <vector>
0016
0017
0018 #include "App.hpp"
0019 #include "ConfigFwd.hpp"
0020 #include "StringTools.hpp"
0021
0022 namespace CLI {
0023
0024 namespace detail {
0025
0026 inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') {
0027 if(arg.empty()) {
0028 return std::string(2, stringQuote);
0029 }
0030
0031 if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
0032 return arg;
0033 }
0034
0035 if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
0036 double val;
0037 if(detail::lexical_cast(arg, val)) {
0038 return arg;
0039 }
0040 }
0041
0042 if(arg.size() == 1) {
0043 return std::string(1, characterQuote) + arg + characterQuote;
0044 }
0045
0046 if(arg.front() == '0') {
0047 if(arg[1] == 'x') {
0048 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) {
0049 return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f');
0050 })) {
0051 return arg;
0052 }
0053 } else if(arg[1] == 'o') {
0054 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) {
0055 return arg;
0056 }
0057 } else if(arg[1] == 'b') {
0058 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) {
0059 return arg;
0060 }
0061 }
0062 }
0063 if(arg.find_first_of(stringQuote) == std::string::npos) {
0064 return std::string(1, stringQuote) + arg + stringQuote;
0065 } else {
0066 return characterQuote + arg + characterQuote;
0067 }
0068 }
0069
0070
0071 inline std::string ini_join(const std::vector<std::string> &args,
0072 char sepChar = ',',
0073 char arrayStart = '[',
0074 char arrayEnd = ']',
0075 char stringQuote = '"',
0076 char characterQuote = '\'') {
0077 std::string joined;
0078 if(args.size() > 1 && arrayStart != '\0') {
0079 joined.push_back(arrayStart);
0080 }
0081 std::size_t start = 0;
0082 for(const auto &arg : args) {
0083 if(start++ > 0) {
0084 joined.push_back(sepChar);
0085 if(isspace(sepChar) == 0) {
0086 joined.push_back(' ');
0087 }
0088 }
0089 joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote));
0090 }
0091 if(args.size() > 1 && arrayEnd != '\0') {
0092 joined.push_back(arrayEnd);
0093 }
0094 return joined;
0095 }
0096
0097 inline std::vector<std::string> generate_parents(const std::string §ion, std::string &name) {
0098 std::vector<std::string> parents;
0099 if(detail::to_lower(section) != "default") {
0100 if(section.find('.') != std::string::npos) {
0101 parents = detail::split(section, '.');
0102 } else {
0103 parents = {section};
0104 }
0105 }
0106 if(name.find('.') != std::string::npos) {
0107 std::vector<std::string> plist = detail::split(name, '.');
0108 name = plist.back();
0109 detail::remove_quotes(name);
0110 plist.pop_back();
0111 parents.insert(parents.end(), plist.begin(), plist.end());
0112 }
0113
0114
0115 for(auto &parent : parents) {
0116 detail::remove_quotes(parent);
0117 }
0118 return parents;
0119 }
0120
0121
0122 inline void checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection) {
0123
0124 std::string estring;
0125 auto parents = detail::generate_parents(currentSection, estring);
0126 if(!output.empty() && output.back().name == "--") {
0127 std::size_t msize = (parents.size() > 1U) ? parents.size() : 2;
0128 while(output.back().parents.size() >= msize) {
0129 output.push_back(output.back());
0130 output.back().parents.pop_back();
0131 }
0132
0133 if(parents.size() > 1) {
0134 std::size_t common = 0;
0135 std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1);
0136 for(std::size_t ii = 0; ii < mpair; ++ii) {
0137 if(output.back().parents[ii] != parents[ii]) {
0138 break;
0139 }
0140 ++common;
0141 }
0142 if(common == mpair) {
0143 output.pop_back();
0144 } else {
0145 while(output.back().parents.size() > common + 1) {
0146 output.push_back(output.back());
0147 output.back().parents.pop_back();
0148 }
0149 }
0150 for(std::size_t ii = common; ii < parents.size() - 1; ++ii) {
0151 output.emplace_back();
0152 output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
0153 output.back().name = "++";
0154 }
0155 }
0156 } else if(parents.size() > 1) {
0157 for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) {
0158 output.emplace_back();
0159 output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
0160 output.back().name = "++";
0161 }
0162 }
0163
0164
0165 output.emplace_back();
0166 output.back().parents = std::move(parents);
0167 output.back().name = "++";
0168 }
0169 }
0170
0171 inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
0172 std::string line;
0173 std::string section = "default";
0174
0175 std::vector<ConfigItem> output;
0176 bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ',');
0177 bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
0178 char aStart = (isINIArray) ? '[' : arrayStart;
0179 char aEnd = (isINIArray) ? ']' : arrayEnd;
0180 char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
0181
0182 while(getline(input, line)) {
0183 std::vector<std::string> items_buffer;
0184 std::string name;
0185
0186 detail::trim(line);
0187 std::size_t len = line.length();
0188 if(len > 1 && line.front() == '[' && line.back() == ']') {
0189 if(section != "default") {
0190
0191 output.emplace_back();
0192 output.back().parents = detail::generate_parents(section, name);
0193 output.back().name = "--";
0194 }
0195 section = line.substr(1, len - 2);
0196
0197 if(section.size() > 1 && section.front() == '[' && section.back() == ']') {
0198 section = section.substr(1, section.size() - 2);
0199 }
0200 if(detail::to_lower(section) == "default") {
0201 section = "default";
0202 } else {
0203 detail::checkParentSegments(output, section);
0204 }
0205 continue;
0206 }
0207 if(len == 0) {
0208 continue;
0209 }
0210
0211 if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
0212 continue;
0213 }
0214
0215
0216 auto pos = line.find(valueDelimiter);
0217 if(pos != std::string::npos) {
0218 name = detail::trim_copy(line.substr(0, pos));
0219 std::string item = detail::trim_copy(line.substr(pos + 1));
0220 if(item.size() > 1 && item.front() == aStart) {
0221 for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) {
0222 detail::trim(multiline);
0223 item += multiline;
0224 }
0225 items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
0226 } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
0227 items_buffer = detail::split_up(item, aSep);
0228 } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
0229 items_buffer = detail::split_up(item);
0230 } else {
0231 items_buffer = {item};
0232 }
0233 } else {
0234 name = detail::trim_copy(line);
0235 items_buffer = {"true"};
0236 }
0237 if(name.find('.') == std::string::npos) {
0238 detail::remove_quotes(name);
0239 }
0240
0241 for(auto &it : items_buffer) {
0242 detail::remove_quotes(it);
0243 }
0244
0245 std::vector<std::string> parents = detail::generate_parents(section, name);
0246
0247 if(!output.empty() && name == output.back().name && parents == output.back().parents) {
0248 output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end());
0249 } else {
0250 output.emplace_back();
0251 output.back().parents = std::move(parents);
0252 output.back().name = std::move(name);
0253 output.back().inputs = std::move(items_buffer);
0254 }
0255 }
0256 if(section != "default") {
0257
0258 std::string ename;
0259 output.emplace_back();
0260 output.back().parents = detail::generate_parents(section, ename);
0261 output.back().name = "--";
0262 while(output.back().parents.size() > 1) {
0263 output.push_back(output.back());
0264 output.back().parents.pop_back();
0265 }
0266 }
0267 return output;
0268 }
0269
0270 inline std::string
0271 ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
0272 std::stringstream out;
0273 std::string commentLead;
0274 commentLead.push_back(commentChar);
0275 commentLead.push_back(' ');
0276
0277 std::vector<std::string> groups = app->get_groups();
0278 bool defaultUsed = false;
0279 groups.insert(groups.begin(), std::string("Options"));
0280 if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) {
0281 out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n';
0282 }
0283 for(auto &group : groups) {
0284 if(group == "Options" || group.empty()) {
0285 if(defaultUsed) {
0286 continue;
0287 }
0288 defaultUsed = true;
0289 }
0290 if(write_description && group != "Options" && !group.empty()) {
0291 out << '\n' << commentLead << group << " Options\n";
0292 }
0293 for(const Option *opt : app->get_options({})) {
0294
0295
0296 if(opt->get_configurable()) {
0297 if(opt->get_group() != group) {
0298 if(!(group == "Options" && opt->get_group().empty())) {
0299 continue;
0300 }
0301 }
0302 std::string name = prefix + opt->get_single_name();
0303 std::string value = detail::ini_join(
0304 opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote);
0305
0306 if(value.empty() && default_also) {
0307 if(!opt->get_default_str().empty()) {
0308 value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote);
0309 } else if(opt->get_expected_min() == 0) {
0310 value = "false";
0311 } else if(opt->get_run_callback_for_default()) {
0312 value = "\"\"";
0313 }
0314 }
0315
0316 if(!value.empty()) {
0317 if(write_description && opt->has_description()) {
0318 out << '\n';
0319 out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
0320 }
0321 out << name << valueDelimiter << value << '\n';
0322 }
0323 }
0324 }
0325 }
0326 auto subcommands = app->get_subcommands({});
0327 for(const App *subcom : subcommands) {
0328 if(subcom->get_name().empty()) {
0329 if(write_description && !subcom->get_group().empty()) {
0330 out << '\n' << commentLead << subcom->get_group() << " Options\n";
0331 }
0332 out << to_config(subcom, default_also, write_description, prefix);
0333 }
0334 }
0335
0336 for(const App *subcom : subcommands) {
0337 if(!subcom->get_name().empty()) {
0338 if(subcom->get_configurable() && app->got_subcommand(subcom)) {
0339 if(!prefix.empty() || app->get_parent() == nullptr) {
0340 out << '[' << prefix << subcom->get_name() << "]\n";
0341 } else {
0342 std::string subname = app->get_name() + "." + subcom->get_name();
0343 auto p = app->get_parent();
0344 while(p->get_parent() != nullptr) {
0345 subname = p->get_name() + "." + subname;
0346 p = p->get_parent();
0347 }
0348 out << '[' << subname << "]\n";
0349 }
0350 out << to_config(subcom, default_also, write_description, "");
0351 } else {
0352 out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + ".");
0353 }
0354 }
0355 }
0356
0357 return out.str();
0358 }
0359
0360
0361 }