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