diff --git a/CLI11.hpp.in b/CLI11.hpp.in index fdc95fe..979f7cf 100644 --- a/CLI11.hpp.in +++ b/CLI11.hpp.in @@ -68,6 +68,8 @@ namespace {namespace} {{ {config_hpp} +{config_inl_hpp} + {formatter_hpp} }} // namespace {namespace} diff --git a/include/CLI/Config.hpp b/include/CLI/Config.hpp index 98249d6..685981c 100644 --- a/include/CLI/Config.hpp +++ b/include/CLI/Config.hpp @@ -24,373 +24,25 @@ namespace CLI { // [CLI11:config_hpp:verbatim] namespace detail { -inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') { - if(arg.empty()) { - return std::string(2, stringQuote); - } - // some specifically supported strings - if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") { - return arg; - } - // floating point conversion can convert some hex codes, but don't try that here - if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { - double val = 0.0; - if(detail::lexical_cast(arg, val)) { - return arg; - } - } - // just quote a single non numeric character - if(arg.size() == 1) { - return std::string(1, characterQuote) + arg + characterQuote; - } - // handle hex, binary or octal arguments - if(arg.front() == '0') { - if(arg[1] == 'x') { - if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { - return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f'); - })) { - return arg; - } - } else if(arg[1] == 'o') { - if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) { - return arg; - } - } else if(arg[1] == 'b') { - if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) { - return arg; - } - } - } - if(arg.find_first_of(stringQuote) == std::string::npos) { - return std::string(1, stringQuote) + arg + stringQuote; - } - return characterQuote + arg + characterQuote; -} +std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\''); /// Comma separated join, adds quotes if needed -inline std::string ini_join(const std::vector &args, - char sepChar = ',', - char arrayStart = '[', - char arrayEnd = ']', - char stringQuote = '"', - char characterQuote = '\'') { - std::string joined; - if(args.size() > 1 && arrayStart != '\0') { - joined.push_back(arrayStart); - } - std::size_t start = 0; - for(const auto &arg : args) { - if(start++ > 0) { - joined.push_back(sepChar); - if(!std::isspace(sepChar, std::locale())) { - joined.push_back(' '); - } - } - joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote)); - } - if(args.size() > 1 && arrayEnd != '\0') { - joined.push_back(arrayEnd); - } - return joined; -} +std::string ini_join(const std::vector &args, + char sepChar = ',', + char arrayStart = '[', + char arrayEnd = ']', + char stringQuote = '"', + char characterQuote = '\''); -inline std::vector generate_parents(const std::string §ion, std::string &name, char parentSeparator) { - std::vector parents; - if(detail::to_lower(section) != "default") { - if(section.find(parentSeparator) != std::string::npos) { - parents = detail::split(section, parentSeparator); - } else { - parents = {section}; - } - } - if(name.find(parentSeparator) != std::string::npos) { - std::vector plist = detail::split(name, parentSeparator); - name = plist.back(); - detail::remove_quotes(name); - plist.pop_back(); - parents.insert(parents.end(), plist.begin(), plist.end()); - } - - // clean up quotes on the parents - for(auto &parent : parents) { - detail::remove_quotes(parent); - } - return parents; -} +std::vector generate_parents(const std::string §ion, std::string &name, char parentSeparator); /// assuming non default segments do a check on the close and open of the segments in a configItem structure -inline void -checkParentSegments(std::vector &output, const std::string ¤tSection, char parentSeparator) { - - std::string estring; - auto parents = detail::generate_parents(currentSection, estring, parentSeparator); - if(!output.empty() && output.back().name == "--") { - std::size_t msize = (parents.size() > 1U) ? parents.size() : 2; - while(output.back().parents.size() >= msize) { - output.push_back(output.back()); - output.back().parents.pop_back(); - } - - if(parents.size() > 1) { - std::size_t common = 0; - std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1); - for(std::size_t ii = 0; ii < mpair; ++ii) { - if(output.back().parents[ii] != parents[ii]) { - break; - } - ++common; - } - if(common == mpair) { - output.pop_back(); - } else { - while(output.back().parents.size() > common + 1) { - output.push_back(output.back()); - output.back().parents.pop_back(); - } - } - for(std::size_t ii = common; ii < parents.size() - 1; ++ii) { - output.emplace_back(); - output.back().parents.assign(parents.begin(), parents.begin() + static_cast(ii) + 1); - output.back().name = "++"; - } - } - } else if(parents.size() > 1) { - for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) { - output.emplace_back(); - output.back().parents.assign(parents.begin(), parents.begin() + static_cast(ii) + 1); - output.back().name = "++"; - } - } - - // insert a section end which is just an empty items_buffer - output.emplace_back(); - output.back().parents = std::move(parents); - output.back().name = "++"; -} +void checkParentSegments(std::vector &output, const std::string ¤tSection, char parentSeparator); } // namespace detail -inline std::vector ConfigBase::from_config(std::istream &input) const { - std::string line; - std::string currentSection = "default"; - std::string previousSection = "default"; - std::vector output; - bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ','); - bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd; - bool inSection{false}; - char aStart = (isINIArray) ? '[' : arrayStart; - char aEnd = (isINIArray) ? ']' : arrayEnd; - char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator; - int currentSectionIndex{0}; - while(getline(input, line)) { - std::vector items_buffer; - std::string name; - - detail::trim(line); - std::size_t len = line.length(); - // lines have to be at least 3 characters to have any meaning to CLI just skip the rest - if(len < 3) { - continue; - } - if(line.front() == '[' && line.back() == ']') { - if(currentSection != "default") { - // insert a section end which is just an empty items_buffer - output.emplace_back(); - output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar); - output.back().name = "--"; - } - currentSection = line.substr(1, len - 2); - // deal with double brackets for TOML - if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') { - currentSection = currentSection.substr(1, currentSection.size() - 2); - } - if(detail::to_lower(currentSection) == "default") { - currentSection = "default"; - } else { - detail::checkParentSegments(output, currentSection, parentSeparatorChar); - } - inSection = false; - if(currentSection == previousSection) { - ++currentSectionIndex; - } else { - currentSectionIndex = 0; - previousSection = currentSection; - } - continue; - } - - // comment lines - if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) { - continue; - } - - // Find = in string, split and recombine - auto pos = line.find(valueDelimiter); - if(pos != std::string::npos) { - name = detail::trim_copy(line.substr(0, pos)); - std::string item = detail::trim_copy(line.substr(pos + 1)); - auto cloc = item.find(commentChar); - if(cloc != std::string::npos) { - item.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument) - detail::trim(item); - } - if(item.size() > 1 && item.front() == aStart) { - for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) { - detail::trim(multiline); - item += multiline; - } - items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep); - } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) { - items_buffer = detail::split_up(item, aSep); - } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) { - items_buffer = detail::split_up(item); - } else { - items_buffer = {item}; - } - } else { - name = detail::trim_copy(line); - auto cloc = name.find(commentChar); - if(cloc != std::string::npos) { - name.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument) - detail::trim(name); - } - - items_buffer = {"true"}; - } - if(name.find(parentSeparatorChar) == std::string::npos) { - detail::remove_quotes(name); - } - // clean up quotes on the items - for(auto &it : items_buffer) { - detail::remove_quotes(it); - } - - std::vector parents = detail::generate_parents(currentSection, name, parentSeparatorChar); - if(parents.size() > maximumLayers) { - continue; - } - if(!configSection.empty() && !inSection) { - if(parents.empty() || parents.front() != configSection) { - continue; - } - if(configIndex >= 0 && currentSectionIndex != configIndex) { - continue; - } - parents.erase(parents.begin()); - inSection = true; - } - if(!output.empty() && name == output.back().name && parents == output.back().parents) { - output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end()); - } else { - output.emplace_back(); - output.back().parents = std::move(parents); - output.back().name = std::move(name); - output.back().inputs = std::move(items_buffer); - } - } - if(currentSection != "default") { - // insert a section end which is just an empty items_buffer - std::string ename; - output.emplace_back(); - output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar); - output.back().name = "--"; - while(output.back().parents.size() > 1) { - output.push_back(output.back()); - output.back().parents.pop_back(); - } - } - return output; -} - -inline std::string -ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const { - std::stringstream out; - std::string commentLead; - commentLead.push_back(commentChar); - commentLead.push_back(' '); - - std::vector groups = app->get_groups(); - bool defaultUsed = false; - groups.insert(groups.begin(), std::string("Options")); - if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) { - out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n'; - } - for(auto &group : groups) { - if(group == "Options" || group.empty()) { - if(defaultUsed) { - continue; - } - defaultUsed = true; - } - if(write_description && group != "Options" && !group.empty()) { - out << '\n' << commentLead << group << " Options\n"; - } - for(const Option *opt : app->get_options({})) { - - // Only process options that are configurable - if(opt->get_configurable()) { - if(opt->get_group() != group) { - if(!(group == "Options" && opt->get_group().empty())) { - continue; - } - } - std::string name = prefix + opt->get_single_name(); - std::string value = detail::ini_join( - opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote); - - if(value.empty() && default_also) { - if(!opt->get_default_str().empty()) { - value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote); - } else if(opt->get_expected_min() == 0) { - value = "false"; - } else if(opt->get_run_callback_for_default()) { - value = "\"\""; // empty string default value - } - } - - if(!value.empty()) { - if(write_description && opt->has_description()) { - out << '\n'; - out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n'; - } - out << name << valueDelimiter << value << '\n'; - } - } - } - } - auto subcommands = app->get_subcommands({}); - for(const App *subcom : subcommands) { - if(subcom->get_name().empty()) { - if(write_description && !subcom->get_group().empty()) { - out << '\n' << commentLead << subcom->get_group() << " Options\n"; - } - out << to_config(subcom, default_also, write_description, prefix); - } - } - - for(const App *subcom : subcommands) { - if(!subcom->get_name().empty()) { - if(subcom->get_configurable() && app->got_subcommand(subcom)) { - if(!prefix.empty() || app->get_parent() == nullptr) { - out << '[' << prefix << subcom->get_name() << "]\n"; - } else { - std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name(); - const auto *p = app->get_parent(); - while(p->get_parent() != nullptr) { - subname = p->get_name() + parentSeparatorChar + subname; - p = p->get_parent(); - } - out << '[' << subname << "]\n"; - } - out << to_config(subcom, default_also, write_description, ""); - } else { - out << to_config( - subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar); - } - } - } - - return out.str(); -} - // [CLI11:config_hpp:end] } // namespace CLI + +#ifndef CLI11_COMPILE +#include "impl/Config_inl.hpp" +#endif diff --git a/include/CLI/impl/Config_inl.hpp b/include/CLI/impl/Config_inl.hpp new file mode 100644 index 0000000..0f9695f --- /dev/null +++ b/include/CLI/impl/Config_inl.hpp @@ -0,0 +1,391 @@ +// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#pragma once + +// This include is only needed for IDEs to discover symbols +#include + +// [CLI11:public_includes:set] +#include +#include +#include +#include +// [CLI11:public_includes:end] + +namespace CLI { +// [CLI11:config_inl_hpp:verbatim] + +namespace detail { + +CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char stringQuote, char characterQuote) { + if(arg.empty()) { + return std::string(2, stringQuote); + } + // some specifically supported strings + if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") { + return arg; + } + // floating point conversion can convert some hex codes, but don't try that here + if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { + double val = 0.0; + if(detail::lexical_cast(arg, val)) { + return arg; + } + } + // just quote a single non numeric character + if(arg.size() == 1) { + return std::string(1, characterQuote) + arg + characterQuote; + } + // handle hex, binary or octal arguments + if(arg.front() == '0') { + if(arg[1] == 'x') { + if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { + return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f'); + })) { + return arg; + } + } else if(arg[1] == 'o') { + if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) { + return arg; + } + } else if(arg[1] == 'b') { + if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) { + return arg; + } + } + } + if(arg.find_first_of(stringQuote) == std::string::npos) { + return std::string(1, stringQuote) + arg + stringQuote; + } + return characterQuote + arg + characterQuote; +} + +CLI11_INLINE std::string ini_join(const std::vector &args, + char sepChar, + char arrayStart, + char arrayEnd, + char stringQuote, + char characterQuote) { + std::string joined; + if(args.size() > 1 && arrayStart != '\0') { + joined.push_back(arrayStart); + } + std::size_t start = 0; + for(const auto &arg : args) { + if(start++ > 0) { + joined.push_back(sepChar); + if(!std::isspace(sepChar, std::locale())) { + joined.push_back(' '); + } + } + joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote)); + } + if(args.size() > 1 && arrayEnd != '\0') { + joined.push_back(arrayEnd); + } + return joined; +} + +CLI11_INLINE std::vector +generate_parents(const std::string §ion, std::string &name, char parentSeparator) { + std::vector parents; + if(detail::to_lower(section) != "default") { + if(section.find(parentSeparator) != std::string::npos) { + parents = detail::split(section, parentSeparator); + } else { + parents = {section}; + } + } + if(name.find(parentSeparator) != std::string::npos) { + std::vector plist = detail::split(name, parentSeparator); + name = plist.back(); + detail::remove_quotes(name); + plist.pop_back(); + parents.insert(parents.end(), plist.begin(), plist.end()); + } + + // clean up quotes on the parents + for(auto &parent : parents) { + detail::remove_quotes(parent); + } + return parents; +} + +CLI11_INLINE void +checkParentSegments(std::vector &output, const std::string ¤tSection, char parentSeparator) { + + std::string estring; + auto parents = detail::generate_parents(currentSection, estring, parentSeparator); + if(!output.empty() && output.back().name == "--") { + std::size_t msize = (parents.size() > 1U) ? parents.size() : 2; + while(output.back().parents.size() >= msize) { + output.push_back(output.back()); + output.back().parents.pop_back(); + } + + if(parents.size() > 1) { + std::size_t common = 0; + std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1); + for(std::size_t ii = 0; ii < mpair; ++ii) { + if(output.back().parents[ii] != parents[ii]) { + break; + } + ++common; + } + if(common == mpair) { + output.pop_back(); + } else { + while(output.back().parents.size() > common + 1) { + output.push_back(output.back()); + output.back().parents.pop_back(); + } + } + for(std::size_t ii = common; ii < parents.size() - 1; ++ii) { + output.emplace_back(); + output.back().parents.assign(parents.begin(), parents.begin() + static_cast(ii) + 1); + output.back().name = "++"; + } + } + } else if(parents.size() > 1) { + for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) { + output.emplace_back(); + output.back().parents.assign(parents.begin(), parents.begin() + static_cast(ii) + 1); + output.back().name = "++"; + } + } + + // insert a section end which is just an empty items_buffer + output.emplace_back(); + output.back().parents = std::move(parents); + output.back().name = "++"; +} +} // namespace detail + +inline std::vector ConfigBase::from_config(std::istream &input) const { + std::string line; + std::string currentSection = "default"; + std::string previousSection = "default"; + std::vector output; + bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ','); + bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd; + bool inSection{false}; + char aStart = (isINIArray) ? '[' : arrayStart; + char aEnd = (isINIArray) ? ']' : arrayEnd; + char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator; + int currentSectionIndex{0}; + while(getline(input, line)) { + std::vector items_buffer; + std::string name; + + detail::trim(line); + std::size_t len = line.length(); + // lines have to be at least 3 characters to have any meaning to CLI just skip the rest + if(len < 3) { + continue; + } + if(line.front() == '[' && line.back() == ']') { + if(currentSection != "default") { + // insert a section end which is just an empty items_buffer + output.emplace_back(); + output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar); + output.back().name = "--"; + } + currentSection = line.substr(1, len - 2); + // deal with double brackets for TOML + if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') { + currentSection = currentSection.substr(1, currentSection.size() - 2); + } + if(detail::to_lower(currentSection) == "default") { + currentSection = "default"; + } else { + detail::checkParentSegments(output, currentSection, parentSeparatorChar); + } + inSection = false; + if(currentSection == previousSection) { + ++currentSectionIndex; + } else { + currentSectionIndex = 0; + previousSection = currentSection; + } + continue; + } + + // comment lines + if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) { + continue; + } + + // Find = in string, split and recombine + auto pos = line.find(valueDelimiter); + if(pos != std::string::npos) { + name = detail::trim_copy(line.substr(0, pos)); + std::string item = detail::trim_copy(line.substr(pos + 1)); + auto cloc = item.find(commentChar); + if(cloc != std::string::npos) { + item.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument) + detail::trim(item); + } + if(item.size() > 1 && item.front() == aStart) { + for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) { + detail::trim(multiline); + item += multiline; + } + items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep); + } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) { + items_buffer = detail::split_up(item, aSep); + } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) { + items_buffer = detail::split_up(item); + } else { + items_buffer = {item}; + } + } else { + name = detail::trim_copy(line); + auto cloc = name.find(commentChar); + if(cloc != std::string::npos) { + name.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument) + detail::trim(name); + } + + items_buffer = {"true"}; + } + if(name.find(parentSeparatorChar) == std::string::npos) { + detail::remove_quotes(name); + } + // clean up quotes on the items + for(auto &it : items_buffer) { + detail::remove_quotes(it); + } + + std::vector parents = detail::generate_parents(currentSection, name, parentSeparatorChar); + if(parents.size() > maximumLayers) { + continue; + } + if(!configSection.empty() && !inSection) { + if(parents.empty() || parents.front() != configSection) { + continue; + } + if(configIndex >= 0 && currentSectionIndex != configIndex) { + continue; + } + parents.erase(parents.begin()); + inSection = true; + } + if(!output.empty() && name == output.back().name && parents == output.back().parents) { + output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end()); + } else { + output.emplace_back(); + output.back().parents = std::move(parents); + output.back().name = std::move(name); + output.back().inputs = std::move(items_buffer); + } + } + if(currentSection != "default") { + // insert a section end which is just an empty items_buffer + std::string ename; + output.emplace_back(); + output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar); + output.back().name = "--"; + while(output.back().parents.size() > 1) { + output.push_back(output.back()); + output.back().parents.pop_back(); + } + } + return output; +} + +CLI11_INLINE std::string +ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const { + std::stringstream out; + std::string commentLead; + commentLead.push_back(commentChar); + commentLead.push_back(' '); + + std::vector groups = app->get_groups(); + bool defaultUsed = false; + groups.insert(groups.begin(), std::string("Options")); + if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) { + out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n'; + } + for(auto &group : groups) { + if(group == "Options" || group.empty()) { + if(defaultUsed) { + continue; + } + defaultUsed = true; + } + if(write_description && group != "Options" && !group.empty()) { + out << '\n' << commentLead << group << " Options\n"; + } + for(const Option *opt : app->get_options({})) { + + // Only process options that are configurable + if(opt->get_configurable()) { + if(opt->get_group() != group) { + if(!(group == "Options" && opt->get_group().empty())) { + continue; + } + } + std::string name = prefix + opt->get_single_name(); + std::string value = detail::ini_join( + opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote); + + if(value.empty() && default_also) { + if(!opt->get_default_str().empty()) { + value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote); + } else if(opt->get_expected_min() == 0) { + value = "false"; + } else if(opt->get_run_callback_for_default()) { + value = "\"\""; // empty string default value + } + } + + if(!value.empty()) { + if(write_description && opt->has_description()) { + out << '\n'; + out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n'; + } + out << name << valueDelimiter << value << '\n'; + } + } + } + } + auto subcommands = app->get_subcommands({}); + for(const App *subcom : subcommands) { + if(subcom->get_name().empty()) { + if(write_description && !subcom->get_group().empty()) { + out << '\n' << commentLead << subcom->get_group() << " Options\n"; + } + out << to_config(subcom, default_also, write_description, prefix); + } + } + + for(const App *subcom : subcommands) { + if(!subcom->get_name().empty()) { + if(subcom->get_configurable() && app->got_subcommand(subcom)) { + if(!prefix.empty() || app->get_parent() == nullptr) { + out << '[' << prefix << subcom->get_name() << "]\n"; + } else { + std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name(); + const auto *p = app->get_parent(); + while(p->get_parent() != nullptr) { + subname = p->get_name() + parentSeparatorChar + subname; + p = p->get_parent(); + } + out << '[' << subname << "]\n"; + } + out << to_config(subcom, default_also, write_description, ""); + } else { + out << to_config( + subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar); + } + } + } + + return out.str(); +} +// [CLI11:config_inl_hpp:end] +} // namespace CLI diff --git a/src/Config.cpp b/src/Config.cpp new file mode 100644 index 0000000..bd5eda2 --- /dev/null +++ b/src/Config.cpp @@ -0,0 +1,7 @@ +// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include