Commit 003e82579ea7fb460035e1343cfcafe60895b210
Committed by
Henry Schreiner
1 parent
3e5173d4
[precompile] Split Config.hpp
Showing
4 changed files
with
413 additions
and
361 deletions
CLI11.hpp.in
include/CLI/Config.hpp
| @@ -24,373 +24,25 @@ namespace CLI { | @@ -24,373 +24,25 @@ namespace CLI { | ||
| 24 | // [CLI11:config_hpp:verbatim] | 24 | // [CLI11:config_hpp:verbatim] |
| 25 | namespace detail { | 25 | namespace detail { |
| 26 | 26 | ||
| 27 | -inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') { | ||
| 28 | - if(arg.empty()) { | ||
| 29 | - return std::string(2, stringQuote); | ||
| 30 | - } | ||
| 31 | - // some specifically supported strings | ||
| 32 | - if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") { | ||
| 33 | - return arg; | ||
| 34 | - } | ||
| 35 | - // floating point conversion can convert some hex codes, but don't try that here | ||
| 36 | - if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { | ||
| 37 | - double val = 0.0; | ||
| 38 | - if(detail::lexical_cast(arg, val)) { | ||
| 39 | - return arg; | ||
| 40 | - } | ||
| 41 | - } | ||
| 42 | - // just quote a single non numeric character | ||
| 43 | - if(arg.size() == 1) { | ||
| 44 | - return std::string(1, characterQuote) + arg + characterQuote; | ||
| 45 | - } | ||
| 46 | - // handle hex, binary or octal arguments | ||
| 47 | - if(arg.front() == '0') { | ||
| 48 | - if(arg[1] == 'x') { | ||
| 49 | - if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { | ||
| 50 | - return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f'); | ||
| 51 | - })) { | ||
| 52 | - return arg; | ||
| 53 | - } | ||
| 54 | - } else if(arg[1] == 'o') { | ||
| 55 | - if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) { | ||
| 56 | - return arg; | ||
| 57 | - } | ||
| 58 | - } else if(arg[1] == 'b') { | ||
| 59 | - if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) { | ||
| 60 | - return arg; | ||
| 61 | - } | ||
| 62 | - } | ||
| 63 | - } | ||
| 64 | - if(arg.find_first_of(stringQuote) == std::string::npos) { | ||
| 65 | - return std::string(1, stringQuote) + arg + stringQuote; | ||
| 66 | - } | ||
| 67 | - return characterQuote + arg + characterQuote; | ||
| 68 | -} | 27 | +std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\''); |
| 69 | 28 | ||
| 70 | /// Comma separated join, adds quotes if needed | 29 | /// Comma separated join, adds quotes if needed |
| 71 | -inline std::string ini_join(const std::vector<std::string> &args, | ||
| 72 | - char sepChar = ',', | ||
| 73 | - char arrayStart = '[', | ||
| 74 | - char arrayEnd = ']', | ||
| 75 | - char stringQuote = '"', | ||
| 76 | - char characterQuote = '\'') { | ||
| 77 | - std::string joined; | ||
| 78 | - if(args.size() > 1 && arrayStart != '\0') { | ||
| 79 | - joined.push_back(arrayStart); | ||
| 80 | - } | ||
| 81 | - std::size_t start = 0; | ||
| 82 | - for(const auto &arg : args) { | ||
| 83 | - if(start++ > 0) { | ||
| 84 | - joined.push_back(sepChar); | ||
| 85 | - if(!std::isspace<char>(sepChar, std::locale())) { | ||
| 86 | - joined.push_back(' '); | ||
| 87 | - } | ||
| 88 | - } | ||
| 89 | - joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote)); | ||
| 90 | - } | ||
| 91 | - if(args.size() > 1 && arrayEnd != '\0') { | ||
| 92 | - joined.push_back(arrayEnd); | ||
| 93 | - } | ||
| 94 | - return joined; | ||
| 95 | -} | 30 | +std::string ini_join(const std::vector<std::string> &args, |
| 31 | + char sepChar = ',', | ||
| 32 | + char arrayStart = '[', | ||
| 33 | + char arrayEnd = ']', | ||
| 34 | + char stringQuote = '"', | ||
| 35 | + char characterQuote = '\''); | ||
| 96 | 36 | ||
| 97 | -inline std::vector<std::string> generate_parents(const std::string §ion, std::string &name, char parentSeparator) { | ||
| 98 | - std::vector<std::string> parents; | ||
| 99 | - if(detail::to_lower(section) != "default") { | ||
| 100 | - if(section.find(parentSeparator) != std::string::npos) { | ||
| 101 | - parents = detail::split(section, parentSeparator); | ||
| 102 | - } else { | ||
| 103 | - parents = {section}; | ||
| 104 | - } | ||
| 105 | - } | ||
| 106 | - if(name.find(parentSeparator) != std::string::npos) { | ||
| 107 | - std::vector<std::string> plist = detail::split(name, parentSeparator); | ||
| 108 | - name = plist.back(); | ||
| 109 | - detail::remove_quotes(name); | ||
| 110 | - plist.pop_back(); | ||
| 111 | - parents.insert(parents.end(), plist.begin(), plist.end()); | ||
| 112 | - } | ||
| 113 | - | ||
| 114 | - // clean up quotes on the parents | ||
| 115 | - for(auto &parent : parents) { | ||
| 116 | - detail::remove_quotes(parent); | ||
| 117 | - } | ||
| 118 | - return parents; | ||
| 119 | -} | 37 | +std::vector<std::string> generate_parents(const std::string §ion, std::string &name, char parentSeparator); |
| 120 | 38 | ||
| 121 | /// assuming non default segments do a check on the close and open of the segments in a configItem structure | 39 | /// assuming non default segments do a check on the close and open of the segments in a configItem structure |
| 122 | -inline void | ||
| 123 | -checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection, char parentSeparator) { | ||
| 124 | - | ||
| 125 | - std::string estring; | ||
| 126 | - auto parents = detail::generate_parents(currentSection, estring, parentSeparator); | ||
| 127 | - if(!output.empty() && output.back().name == "--") { | ||
| 128 | - std::size_t msize = (parents.size() > 1U) ? parents.size() : 2; | ||
| 129 | - while(output.back().parents.size() >= msize) { | ||
| 130 | - output.push_back(output.back()); | ||
| 131 | - output.back().parents.pop_back(); | ||
| 132 | - } | ||
| 133 | - | ||
| 134 | - if(parents.size() > 1) { | ||
| 135 | - std::size_t common = 0; | ||
| 136 | - std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1); | ||
| 137 | - for(std::size_t ii = 0; ii < mpair; ++ii) { | ||
| 138 | - if(output.back().parents[ii] != parents[ii]) { | ||
| 139 | - break; | ||
| 140 | - } | ||
| 141 | - ++common; | ||
| 142 | - } | ||
| 143 | - if(common == mpair) { | ||
| 144 | - output.pop_back(); | ||
| 145 | - } else { | ||
| 146 | - while(output.back().parents.size() > common + 1) { | ||
| 147 | - output.push_back(output.back()); | ||
| 148 | - output.back().parents.pop_back(); | ||
| 149 | - } | ||
| 150 | - } | ||
| 151 | - for(std::size_t ii = common; ii < parents.size() - 1; ++ii) { | ||
| 152 | - output.emplace_back(); | ||
| 153 | - output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1); | ||
| 154 | - output.back().name = "++"; | ||
| 155 | - } | ||
| 156 | - } | ||
| 157 | - } else if(parents.size() > 1) { | ||
| 158 | - for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) { | ||
| 159 | - output.emplace_back(); | ||
| 160 | - output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1); | ||
| 161 | - output.back().name = "++"; | ||
| 162 | - } | ||
| 163 | - } | ||
| 164 | - | ||
| 165 | - // insert a section end which is just an empty items_buffer | ||
| 166 | - output.emplace_back(); | ||
| 167 | - output.back().parents = std::move(parents); | ||
| 168 | - output.back().name = "++"; | ||
| 169 | -} | 40 | +void checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection, char parentSeparator); |
| 170 | } // namespace detail | 41 | } // namespace detail |
| 171 | 42 | ||
| 172 | -inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const { | ||
| 173 | - std::string line; | ||
| 174 | - std::string currentSection = "default"; | ||
| 175 | - std::string previousSection = "default"; | ||
| 176 | - std::vector<ConfigItem> output; | ||
| 177 | - bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ','); | ||
| 178 | - bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd; | ||
| 179 | - bool inSection{false}; | ||
| 180 | - char aStart = (isINIArray) ? '[' : arrayStart; | ||
| 181 | - char aEnd = (isINIArray) ? ']' : arrayEnd; | ||
| 182 | - char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator; | ||
| 183 | - int currentSectionIndex{0}; | ||
| 184 | - while(getline(input, line)) { | ||
| 185 | - std::vector<std::string> items_buffer; | ||
| 186 | - std::string name; | ||
| 187 | - | ||
| 188 | - detail::trim(line); | ||
| 189 | - std::size_t len = line.length(); | ||
| 190 | - // lines have to be at least 3 characters to have any meaning to CLI just skip the rest | ||
| 191 | - if(len < 3) { | ||
| 192 | - continue; | ||
| 193 | - } | ||
| 194 | - if(line.front() == '[' && line.back() == ']') { | ||
| 195 | - if(currentSection != "default") { | ||
| 196 | - // insert a section end which is just an empty items_buffer | ||
| 197 | - output.emplace_back(); | ||
| 198 | - output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar); | ||
| 199 | - output.back().name = "--"; | ||
| 200 | - } | ||
| 201 | - currentSection = line.substr(1, len - 2); | ||
| 202 | - // deal with double brackets for TOML | ||
| 203 | - if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') { | ||
| 204 | - currentSection = currentSection.substr(1, currentSection.size() - 2); | ||
| 205 | - } | ||
| 206 | - if(detail::to_lower(currentSection) == "default") { | ||
| 207 | - currentSection = "default"; | ||
| 208 | - } else { | ||
| 209 | - detail::checkParentSegments(output, currentSection, parentSeparatorChar); | ||
| 210 | - } | ||
| 211 | - inSection = false; | ||
| 212 | - if(currentSection == previousSection) { | ||
| 213 | - ++currentSectionIndex; | ||
| 214 | - } else { | ||
| 215 | - currentSectionIndex = 0; | ||
| 216 | - previousSection = currentSection; | ||
| 217 | - } | ||
| 218 | - continue; | ||
| 219 | - } | ||
| 220 | - | ||
| 221 | - // comment lines | ||
| 222 | - if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) { | ||
| 223 | - continue; | ||
| 224 | - } | ||
| 225 | - | ||
| 226 | - // Find = in string, split and recombine | ||
| 227 | - auto pos = line.find(valueDelimiter); | ||
| 228 | - if(pos != std::string::npos) { | ||
| 229 | - name = detail::trim_copy(line.substr(0, pos)); | ||
| 230 | - std::string item = detail::trim_copy(line.substr(pos + 1)); | ||
| 231 | - auto cloc = item.find(commentChar); | ||
| 232 | - if(cloc != std::string::npos) { | ||
| 233 | - item.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument) | ||
| 234 | - detail::trim(item); | ||
| 235 | - } | ||
| 236 | - if(item.size() > 1 && item.front() == aStart) { | ||
| 237 | - for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) { | ||
| 238 | - detail::trim(multiline); | ||
| 239 | - item += multiline; | ||
| 240 | - } | ||
| 241 | - items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep); | ||
| 242 | - } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) { | ||
| 243 | - items_buffer = detail::split_up(item, aSep); | ||
| 244 | - } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) { | ||
| 245 | - items_buffer = detail::split_up(item); | ||
| 246 | - } else { | ||
| 247 | - items_buffer = {item}; | ||
| 248 | - } | ||
| 249 | - } else { | ||
| 250 | - name = detail::trim_copy(line); | ||
| 251 | - auto cloc = name.find(commentChar); | ||
| 252 | - if(cloc != std::string::npos) { | ||
| 253 | - name.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument) | ||
| 254 | - detail::trim(name); | ||
| 255 | - } | ||
| 256 | - | ||
| 257 | - items_buffer = {"true"}; | ||
| 258 | - } | ||
| 259 | - if(name.find(parentSeparatorChar) == std::string::npos) { | ||
| 260 | - detail::remove_quotes(name); | ||
| 261 | - } | ||
| 262 | - // clean up quotes on the items | ||
| 263 | - for(auto &it : items_buffer) { | ||
| 264 | - detail::remove_quotes(it); | ||
| 265 | - } | ||
| 266 | - | ||
| 267 | - std::vector<std::string> parents = detail::generate_parents(currentSection, name, parentSeparatorChar); | ||
| 268 | - if(parents.size() > maximumLayers) { | ||
| 269 | - continue; | ||
| 270 | - } | ||
| 271 | - if(!configSection.empty() && !inSection) { | ||
| 272 | - if(parents.empty() || parents.front() != configSection) { | ||
| 273 | - continue; | ||
| 274 | - } | ||
| 275 | - if(configIndex >= 0 && currentSectionIndex != configIndex) { | ||
| 276 | - continue; | ||
| 277 | - } | ||
| 278 | - parents.erase(parents.begin()); | ||
| 279 | - inSection = true; | ||
| 280 | - } | ||
| 281 | - if(!output.empty() && name == output.back().name && parents == output.back().parents) { | ||
| 282 | - output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end()); | ||
| 283 | - } else { | ||
| 284 | - output.emplace_back(); | ||
| 285 | - output.back().parents = std::move(parents); | ||
| 286 | - output.back().name = std::move(name); | ||
| 287 | - output.back().inputs = std::move(items_buffer); | ||
| 288 | - } | ||
| 289 | - } | ||
| 290 | - if(currentSection != "default") { | ||
| 291 | - // insert a section end which is just an empty items_buffer | ||
| 292 | - std::string ename; | ||
| 293 | - output.emplace_back(); | ||
| 294 | - output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar); | ||
| 295 | - output.back().name = "--"; | ||
| 296 | - while(output.back().parents.size() > 1) { | ||
| 297 | - output.push_back(output.back()); | ||
| 298 | - output.back().parents.pop_back(); | ||
| 299 | - } | ||
| 300 | - } | ||
| 301 | - return output; | ||
| 302 | -} | ||
| 303 | - | ||
| 304 | -inline std::string | ||
| 305 | -ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const { | ||
| 306 | - std::stringstream out; | ||
| 307 | - std::string commentLead; | ||
| 308 | - commentLead.push_back(commentChar); | ||
| 309 | - commentLead.push_back(' '); | ||
| 310 | - | ||
| 311 | - std::vector<std::string> groups = app->get_groups(); | ||
| 312 | - bool defaultUsed = false; | ||
| 313 | - groups.insert(groups.begin(), std::string("Options")); | ||
| 314 | - if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) { | ||
| 315 | - out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n'; | ||
| 316 | - } | ||
| 317 | - for(auto &group : groups) { | ||
| 318 | - if(group == "Options" || group.empty()) { | ||
| 319 | - if(defaultUsed) { | ||
| 320 | - continue; | ||
| 321 | - } | ||
| 322 | - defaultUsed = true; | ||
| 323 | - } | ||
| 324 | - if(write_description && group != "Options" && !group.empty()) { | ||
| 325 | - out << '\n' << commentLead << group << " Options\n"; | ||
| 326 | - } | ||
| 327 | - for(const Option *opt : app->get_options({})) { | ||
| 328 | - | ||
| 329 | - // Only process options that are configurable | ||
| 330 | - if(opt->get_configurable()) { | ||
| 331 | - if(opt->get_group() != group) { | ||
| 332 | - if(!(group == "Options" && opt->get_group().empty())) { | ||
| 333 | - continue; | ||
| 334 | - } | ||
| 335 | - } | ||
| 336 | - std::string name = prefix + opt->get_single_name(); | ||
| 337 | - std::string value = detail::ini_join( | ||
| 338 | - opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote); | ||
| 339 | - | ||
| 340 | - if(value.empty() && default_also) { | ||
| 341 | - if(!opt->get_default_str().empty()) { | ||
| 342 | - value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote); | ||
| 343 | - } else if(opt->get_expected_min() == 0) { | ||
| 344 | - value = "false"; | ||
| 345 | - } else if(opt->get_run_callback_for_default()) { | ||
| 346 | - value = "\"\""; // empty string default value | ||
| 347 | - } | ||
| 348 | - } | ||
| 349 | - | ||
| 350 | - if(!value.empty()) { | ||
| 351 | - if(write_description && opt->has_description()) { | ||
| 352 | - out << '\n'; | ||
| 353 | - out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n'; | ||
| 354 | - } | ||
| 355 | - out << name << valueDelimiter << value << '\n'; | ||
| 356 | - } | ||
| 357 | - } | ||
| 358 | - } | ||
| 359 | - } | ||
| 360 | - auto subcommands = app->get_subcommands({}); | ||
| 361 | - for(const App *subcom : subcommands) { | ||
| 362 | - if(subcom->get_name().empty()) { | ||
| 363 | - if(write_description && !subcom->get_group().empty()) { | ||
| 364 | - out << '\n' << commentLead << subcom->get_group() << " Options\n"; | ||
| 365 | - } | ||
| 366 | - out << to_config(subcom, default_also, write_description, prefix); | ||
| 367 | - } | ||
| 368 | - } | ||
| 369 | - | ||
| 370 | - for(const App *subcom : subcommands) { | ||
| 371 | - if(!subcom->get_name().empty()) { | ||
| 372 | - if(subcom->get_configurable() && app->got_subcommand(subcom)) { | ||
| 373 | - if(!prefix.empty() || app->get_parent() == nullptr) { | ||
| 374 | - out << '[' << prefix << subcom->get_name() << "]\n"; | ||
| 375 | - } else { | ||
| 376 | - std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name(); | ||
| 377 | - const auto *p = app->get_parent(); | ||
| 378 | - while(p->get_parent() != nullptr) { | ||
| 379 | - subname = p->get_name() + parentSeparatorChar + subname; | ||
| 380 | - p = p->get_parent(); | ||
| 381 | - } | ||
| 382 | - out << '[' << subname << "]\n"; | ||
| 383 | - } | ||
| 384 | - out << to_config(subcom, default_also, write_description, ""); | ||
| 385 | - } else { | ||
| 386 | - out << to_config( | ||
| 387 | - subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar); | ||
| 388 | - } | ||
| 389 | - } | ||
| 390 | - } | ||
| 391 | - | ||
| 392 | - return out.str(); | ||
| 393 | -} | ||
| 394 | - | ||
| 395 | // [CLI11:config_hpp:end] | 43 | // [CLI11:config_hpp:end] |
| 396 | } // namespace CLI | 44 | } // namespace CLI |
| 45 | + | ||
| 46 | +#ifndef CLI11_COMPILE | ||
| 47 | +#include "impl/Config_inl.hpp" | ||
| 48 | +#endif |
include/CLI/impl/Config_inl.hpp
0 → 100644
| 1 | +// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner | ||
| 2 | +// under NSF AWARD 1414736 and by the respective contributors. | ||
| 3 | +// All rights reserved. | ||
| 4 | +// | ||
| 5 | +// SPDX-License-Identifier: BSD-3-Clause | ||
| 6 | + | ||
| 7 | +#pragma once | ||
| 8 | + | ||
| 9 | +// This include is only needed for IDEs to discover symbols | ||
| 10 | +#include <CLI/Config.hpp> | ||
| 11 | + | ||
| 12 | +// [CLI11:public_includes:set] | ||
| 13 | +#include <algorithm> | ||
| 14 | +#include <string> | ||
| 15 | +#include <utility> | ||
| 16 | +#include <vector> | ||
| 17 | +// [CLI11:public_includes:end] | ||
| 18 | + | ||
| 19 | +namespace CLI { | ||
| 20 | +// [CLI11:config_inl_hpp:verbatim] | ||
| 21 | + | ||
| 22 | +namespace detail { | ||
| 23 | + | ||
| 24 | +CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char stringQuote, char characterQuote) { | ||
| 25 | + if(arg.empty()) { | ||
| 26 | + return std::string(2, stringQuote); | ||
| 27 | + } | ||
| 28 | + // some specifically supported strings | ||
| 29 | + if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") { | ||
| 30 | + return arg; | ||
| 31 | + } | ||
| 32 | + // floating point conversion can convert some hex codes, but don't try that here | ||
| 33 | + if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { | ||
| 34 | + double val = 0.0; | ||
| 35 | + if(detail::lexical_cast(arg, val)) { | ||
| 36 | + return arg; | ||
| 37 | + } | ||
| 38 | + } | ||
| 39 | + // just quote a single non numeric character | ||
| 40 | + if(arg.size() == 1) { | ||
| 41 | + return std::string(1, characterQuote) + arg + characterQuote; | ||
| 42 | + } | ||
| 43 | + // handle hex, binary or octal arguments | ||
| 44 | + if(arg.front() == '0') { | ||
| 45 | + if(arg[1] == 'x') { | ||
| 46 | + if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { | ||
| 47 | + return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f'); | ||
| 48 | + })) { | ||
| 49 | + return arg; | ||
| 50 | + } | ||
| 51 | + } else if(arg[1] == 'o') { | ||
| 52 | + if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) { | ||
| 53 | + return arg; | ||
| 54 | + } | ||
| 55 | + } else if(arg[1] == 'b') { | ||
| 56 | + if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) { | ||
| 57 | + return arg; | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + if(arg.find_first_of(stringQuote) == std::string::npos) { | ||
| 62 | + return std::string(1, stringQuote) + arg + stringQuote; | ||
| 63 | + } | ||
| 64 | + return characterQuote + arg + characterQuote; | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +CLI11_INLINE std::string ini_join(const std::vector<std::string> &args, | ||
| 68 | + char sepChar, | ||
| 69 | + char arrayStart, | ||
| 70 | + char arrayEnd, | ||
| 71 | + char stringQuote, | ||
| 72 | + char characterQuote) { | ||
| 73 | + std::string joined; | ||
| 74 | + if(args.size() > 1 && arrayStart != '\0') { | ||
| 75 | + joined.push_back(arrayStart); | ||
| 76 | + } | ||
| 77 | + std::size_t start = 0; | ||
| 78 | + for(const auto &arg : args) { | ||
| 79 | + if(start++ > 0) { | ||
| 80 | + joined.push_back(sepChar); | ||
| 81 | + if(!std::isspace<char>(sepChar, std::locale())) { | ||
| 82 | + joined.push_back(' '); | ||
| 83 | + } | ||
| 84 | + } | ||
| 85 | + joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote)); | ||
| 86 | + } | ||
| 87 | + if(args.size() > 1 && arrayEnd != '\0') { | ||
| 88 | + joined.push_back(arrayEnd); | ||
| 89 | + } | ||
| 90 | + return joined; | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +CLI11_INLINE std::vector<std::string> | ||
| 94 | +generate_parents(const std::string §ion, std::string &name, char parentSeparator) { | ||
| 95 | + std::vector<std::string> parents; | ||
| 96 | + if(detail::to_lower(section) != "default") { | ||
| 97 | + if(section.find(parentSeparator) != std::string::npos) { | ||
| 98 | + parents = detail::split(section, parentSeparator); | ||
| 99 | + } else { | ||
| 100 | + parents = {section}; | ||
| 101 | + } | ||
| 102 | + } | ||
| 103 | + if(name.find(parentSeparator) != std::string::npos) { | ||
| 104 | + std::vector<std::string> plist = detail::split(name, parentSeparator); | ||
| 105 | + name = plist.back(); | ||
| 106 | + detail::remove_quotes(name); | ||
| 107 | + plist.pop_back(); | ||
| 108 | + parents.insert(parents.end(), plist.begin(), plist.end()); | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + // clean up quotes on the parents | ||
| 112 | + for(auto &parent : parents) { | ||
| 113 | + detail::remove_quotes(parent); | ||
| 114 | + } | ||
| 115 | + return parents; | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +CLI11_INLINE void | ||
| 119 | +checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection, char parentSeparator) { | ||
| 120 | + | ||
| 121 | + std::string estring; | ||
| 122 | + auto parents = detail::generate_parents(currentSection, estring, parentSeparator); | ||
| 123 | + if(!output.empty() && output.back().name == "--") { | ||
| 124 | + std::size_t msize = (parents.size() > 1U) ? parents.size() : 2; | ||
| 125 | + while(output.back().parents.size() >= msize) { | ||
| 126 | + output.push_back(output.back()); | ||
| 127 | + output.back().parents.pop_back(); | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + if(parents.size() > 1) { | ||
| 131 | + std::size_t common = 0; | ||
| 132 | + std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1); | ||
| 133 | + for(std::size_t ii = 0; ii < mpair; ++ii) { | ||
| 134 | + if(output.back().parents[ii] != parents[ii]) { | ||
| 135 | + break; | ||
| 136 | + } | ||
| 137 | + ++common; | ||
| 138 | + } | ||
| 139 | + if(common == mpair) { | ||
| 140 | + output.pop_back(); | ||
| 141 | + } else { | ||
| 142 | + while(output.back().parents.size() > common + 1) { | ||
| 143 | + output.push_back(output.back()); | ||
| 144 | + output.back().parents.pop_back(); | ||
| 145 | + } | ||
| 146 | + } | ||
| 147 | + for(std::size_t ii = common; ii < parents.size() - 1; ++ii) { | ||
| 148 | + output.emplace_back(); | ||
| 149 | + output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1); | ||
| 150 | + output.back().name = "++"; | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | + } else if(parents.size() > 1) { | ||
| 154 | + for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) { | ||
| 155 | + output.emplace_back(); | ||
| 156 | + output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1); | ||
| 157 | + output.back().name = "++"; | ||
| 158 | + } | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + // insert a section end which is just an empty items_buffer | ||
| 162 | + output.emplace_back(); | ||
| 163 | + output.back().parents = std::move(parents); | ||
| 164 | + output.back().name = "++"; | ||
| 165 | +} | ||
| 166 | +} // namespace detail | ||
| 167 | + | ||
| 168 | +inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const { | ||
| 169 | + std::string line; | ||
| 170 | + std::string currentSection = "default"; | ||
| 171 | + std::string previousSection = "default"; | ||
| 172 | + std::vector<ConfigItem> output; | ||
| 173 | + bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ','); | ||
| 174 | + bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd; | ||
| 175 | + bool inSection{false}; | ||
| 176 | + char aStart = (isINIArray) ? '[' : arrayStart; | ||
| 177 | + char aEnd = (isINIArray) ? ']' : arrayEnd; | ||
| 178 | + char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator; | ||
| 179 | + int currentSectionIndex{0}; | ||
| 180 | + while(getline(input, line)) { | ||
| 181 | + std::vector<std::string> items_buffer; | ||
| 182 | + std::string name; | ||
| 183 | + | ||
| 184 | + detail::trim(line); | ||
| 185 | + std::size_t len = line.length(); | ||
| 186 | + // lines have to be at least 3 characters to have any meaning to CLI just skip the rest | ||
| 187 | + if(len < 3) { | ||
| 188 | + continue; | ||
| 189 | + } | ||
| 190 | + if(line.front() == '[' && line.back() == ']') { | ||
| 191 | + if(currentSection != "default") { | ||
| 192 | + // insert a section end which is just an empty items_buffer | ||
| 193 | + output.emplace_back(); | ||
| 194 | + output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar); | ||
| 195 | + output.back().name = "--"; | ||
| 196 | + } | ||
| 197 | + currentSection = line.substr(1, len - 2); | ||
| 198 | + // deal with double brackets for TOML | ||
| 199 | + if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') { | ||
| 200 | + currentSection = currentSection.substr(1, currentSection.size() - 2); | ||
| 201 | + } | ||
| 202 | + if(detail::to_lower(currentSection) == "default") { | ||
| 203 | + currentSection = "default"; | ||
| 204 | + } else { | ||
| 205 | + detail::checkParentSegments(output, currentSection, parentSeparatorChar); | ||
| 206 | + } | ||
| 207 | + inSection = false; | ||
| 208 | + if(currentSection == previousSection) { | ||
| 209 | + ++currentSectionIndex; | ||
| 210 | + } else { | ||
| 211 | + currentSectionIndex = 0; | ||
| 212 | + previousSection = currentSection; | ||
| 213 | + } | ||
| 214 | + continue; | ||
| 215 | + } | ||
| 216 | + | ||
| 217 | + // comment lines | ||
| 218 | + if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) { | ||
| 219 | + continue; | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + // Find = in string, split and recombine | ||
| 223 | + auto pos = line.find(valueDelimiter); | ||
| 224 | + if(pos != std::string::npos) { | ||
| 225 | + name = detail::trim_copy(line.substr(0, pos)); | ||
| 226 | + std::string item = detail::trim_copy(line.substr(pos + 1)); | ||
| 227 | + auto cloc = item.find(commentChar); | ||
| 228 | + if(cloc != std::string::npos) { | ||
| 229 | + item.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument) | ||
| 230 | + detail::trim(item); | ||
| 231 | + } | ||
| 232 | + if(item.size() > 1 && item.front() == aStart) { | ||
| 233 | + for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) { | ||
| 234 | + detail::trim(multiline); | ||
| 235 | + item += multiline; | ||
| 236 | + } | ||
| 237 | + items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep); | ||
| 238 | + } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) { | ||
| 239 | + items_buffer = detail::split_up(item, aSep); | ||
| 240 | + } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) { | ||
| 241 | + items_buffer = detail::split_up(item); | ||
| 242 | + } else { | ||
| 243 | + items_buffer = {item}; | ||
| 244 | + } | ||
| 245 | + } else { | ||
| 246 | + name = detail::trim_copy(line); | ||
| 247 | + auto cloc = name.find(commentChar); | ||
| 248 | + if(cloc != std::string::npos) { | ||
| 249 | + name.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument) | ||
| 250 | + detail::trim(name); | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + items_buffer = {"true"}; | ||
| 254 | + } | ||
| 255 | + if(name.find(parentSeparatorChar) == std::string::npos) { | ||
| 256 | + detail::remove_quotes(name); | ||
| 257 | + } | ||
| 258 | + // clean up quotes on the items | ||
| 259 | + for(auto &it : items_buffer) { | ||
| 260 | + detail::remove_quotes(it); | ||
| 261 | + } | ||
| 262 | + | ||
| 263 | + std::vector<std::string> parents = detail::generate_parents(currentSection, name, parentSeparatorChar); | ||
| 264 | + if(parents.size() > maximumLayers) { | ||
| 265 | + continue; | ||
| 266 | + } | ||
| 267 | + if(!configSection.empty() && !inSection) { | ||
| 268 | + if(parents.empty() || parents.front() != configSection) { | ||
| 269 | + continue; | ||
| 270 | + } | ||
| 271 | + if(configIndex >= 0 && currentSectionIndex != configIndex) { | ||
| 272 | + continue; | ||
| 273 | + } | ||
| 274 | + parents.erase(parents.begin()); | ||
| 275 | + inSection = true; | ||
| 276 | + } | ||
| 277 | + if(!output.empty() && name == output.back().name && parents == output.back().parents) { | ||
| 278 | + output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end()); | ||
| 279 | + } else { | ||
| 280 | + output.emplace_back(); | ||
| 281 | + output.back().parents = std::move(parents); | ||
| 282 | + output.back().name = std::move(name); | ||
| 283 | + output.back().inputs = std::move(items_buffer); | ||
| 284 | + } | ||
| 285 | + } | ||
| 286 | + if(currentSection != "default") { | ||
| 287 | + // insert a section end which is just an empty items_buffer | ||
| 288 | + std::string ename; | ||
| 289 | + output.emplace_back(); | ||
| 290 | + output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar); | ||
| 291 | + output.back().name = "--"; | ||
| 292 | + while(output.back().parents.size() > 1) { | ||
| 293 | + output.push_back(output.back()); | ||
| 294 | + output.back().parents.pop_back(); | ||
| 295 | + } | ||
| 296 | + } | ||
| 297 | + return output; | ||
| 298 | +} | ||
| 299 | + | ||
| 300 | +CLI11_INLINE std::string | ||
| 301 | +ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const { | ||
| 302 | + std::stringstream out; | ||
| 303 | + std::string commentLead; | ||
| 304 | + commentLead.push_back(commentChar); | ||
| 305 | + commentLead.push_back(' '); | ||
| 306 | + | ||
| 307 | + std::vector<std::string> groups = app->get_groups(); | ||
| 308 | + bool defaultUsed = false; | ||
| 309 | + groups.insert(groups.begin(), std::string("Options")); | ||
| 310 | + if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) { | ||
| 311 | + out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n'; | ||
| 312 | + } | ||
| 313 | + for(auto &group : groups) { | ||
| 314 | + if(group == "Options" || group.empty()) { | ||
| 315 | + if(defaultUsed) { | ||
| 316 | + continue; | ||
| 317 | + } | ||
| 318 | + defaultUsed = true; | ||
| 319 | + } | ||
| 320 | + if(write_description && group != "Options" && !group.empty()) { | ||
| 321 | + out << '\n' << commentLead << group << " Options\n"; | ||
| 322 | + } | ||
| 323 | + for(const Option *opt : app->get_options({})) { | ||
| 324 | + | ||
| 325 | + // Only process options that are configurable | ||
| 326 | + if(opt->get_configurable()) { | ||
| 327 | + if(opt->get_group() != group) { | ||
| 328 | + if(!(group == "Options" && opt->get_group().empty())) { | ||
| 329 | + continue; | ||
| 330 | + } | ||
| 331 | + } | ||
| 332 | + std::string name = prefix + opt->get_single_name(); | ||
| 333 | + std::string value = detail::ini_join( | ||
| 334 | + opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote); | ||
| 335 | + | ||
| 336 | + if(value.empty() && default_also) { | ||
| 337 | + if(!opt->get_default_str().empty()) { | ||
| 338 | + value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote); | ||
| 339 | + } else if(opt->get_expected_min() == 0) { | ||
| 340 | + value = "false"; | ||
| 341 | + } else if(opt->get_run_callback_for_default()) { | ||
| 342 | + value = "\"\""; // empty string default value | ||
| 343 | + } | ||
| 344 | + } | ||
| 345 | + | ||
| 346 | + if(!value.empty()) { | ||
| 347 | + if(write_description && opt->has_description()) { | ||
| 348 | + out << '\n'; | ||
| 349 | + out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n'; | ||
| 350 | + } | ||
| 351 | + out << name << valueDelimiter << value << '\n'; | ||
| 352 | + } | ||
| 353 | + } | ||
| 354 | + } | ||
| 355 | + } | ||
| 356 | + auto subcommands = app->get_subcommands({}); | ||
| 357 | + for(const App *subcom : subcommands) { | ||
| 358 | + if(subcom->get_name().empty()) { | ||
| 359 | + if(write_description && !subcom->get_group().empty()) { | ||
| 360 | + out << '\n' << commentLead << subcom->get_group() << " Options\n"; | ||
| 361 | + } | ||
| 362 | + out << to_config(subcom, default_also, write_description, prefix); | ||
| 363 | + } | ||
| 364 | + } | ||
| 365 | + | ||
| 366 | + for(const App *subcom : subcommands) { | ||
| 367 | + if(!subcom->get_name().empty()) { | ||
| 368 | + if(subcom->get_configurable() && app->got_subcommand(subcom)) { | ||
| 369 | + if(!prefix.empty() || app->get_parent() == nullptr) { | ||
| 370 | + out << '[' << prefix << subcom->get_name() << "]\n"; | ||
| 371 | + } else { | ||
| 372 | + std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name(); | ||
| 373 | + const auto *p = app->get_parent(); | ||
| 374 | + while(p->get_parent() != nullptr) { | ||
| 375 | + subname = p->get_name() + parentSeparatorChar + subname; | ||
| 376 | + p = p->get_parent(); | ||
| 377 | + } | ||
| 378 | + out << '[' << subname << "]\n"; | ||
| 379 | + } | ||
| 380 | + out << to_config(subcom, default_also, write_description, ""); | ||
| 381 | + } else { | ||
| 382 | + out << to_config( | ||
| 383 | + subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar); | ||
| 384 | + } | ||
| 385 | + } | ||
| 386 | + } | ||
| 387 | + | ||
| 388 | + return out.str(); | ||
| 389 | +} | ||
| 390 | +// [CLI11:config_inl_hpp:end] | ||
| 391 | +} // namespace CLI |
src/Config.cpp
0 → 100644