CLI11 2.2.0
Loading...
Searching...
No Matches
Config.hpp
Go to the documentation of this file.
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// [CLI11:public_includes:set]
10#include <algorithm>
11#include <fstream>
12#include <iostream>
13#include <string>
14#include <utility>
15#include <vector>
16// [CLI11:public_includes:set]
17
18#include "App.hpp"
19#include "ConfigFwd.hpp"
20#include "StringTools.hpp"
21
22namespace CLI {
23// [CLI11:config_hpp:verbatim]
24namespace detail {
25
26inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') {
27 if(arg.empty()) {
28 return std::string(2, stringQuote);
29 }
30 // some specifically supported strings
31 if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
32 return arg;
33 }
34 // floating point conversion can convert some hex codes, but don't try that here
35 if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
36 double val;
37 if(detail::lexical_cast(arg, val)) {
38 return arg;
39 }
40 }
41 // just quote a single non numeric character
42 if(arg.size() == 1) {
43 return std::string(1, characterQuote) + arg + characterQuote;
44 }
45 // handle hex, binary or octal arguments
46 if(arg.front() == '0') {
47 if(arg[1] == 'x') {
48 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) {
49 return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f');
50 })) {
51 return arg;
52 }
53 } else if(arg[1] == 'o') {
54 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) {
55 return arg;
56 }
57 } else if(arg[1] == 'b') {
58 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) {
59 return arg;
60 }
61 }
62 }
63 if(arg.find_first_of(stringQuote) == std::string::npos) {
64 return std::string(1, stringQuote) + arg + stringQuote;
65 } else {
66 return characterQuote + arg + characterQuote;
67 }
68}
69
71inline 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(isspace(sepChar) == 0) {
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}
96
97inline std::vector<std::string> generate_parents(const std::string &section, 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();
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}
120
122inline void
123checkParentSegments(std::vector<ConfigItem> &output, const std::string &currentSection, 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}
170} // namespace detail
171
172inline 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);
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);
254 detail::trim(name);
255 }
256
257 items_buffer = {"true"};
258 }
259 if(name.find(parentSeparatorChar) == std::string::npos) {
261 }
262 // clean up quotes on the items
263 for(auto &it : items_buffer) {
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
304inline std::string
305ConfigBase::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 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]
396} // namespace CLI
Creates a command line program, with very few defaults.
Definition App.hpp:85
bool get_configurable() const
Check the status of the allow windows style options.
Definition App.hpp:1677
App * get_parent()
Get the parent of this subcommand (or nullptr if main app)
Definition App.hpp:1750
std::vector< App * > get_subcommands() const
Definition App.hpp:1352
std::vector< const Option * > get_options(const std::function< bool(const Option *)> filter={}) const
Get the list of options (user facing function, so returns raw pointers), has optional filter function...
Definition App.hpp:1567
std::string get_description() const
Get the app or subcommand description.
Definition App.hpp:1558
bool got_subcommand(const App *subcom) const
Check to see if given subcommand was selected.
Definition App.hpp:1390
const std::string & get_name() const
Get the name of the current app.
Definition App.hpp:1756
std::vector< std::string > get_groups() const
Get the groups available directly from this option (in order)
Definition App.hpp:1814
std::vector< ConfigItem > from_config(std::istream &input) const override
Convert a configuration into an app.
Definition Config.hpp:172
std::string configSection
Specify the configuration section that should be used.
Definition ConfigFwd.hpp:104
char arraySeparator
the character used to separate elements in an array
Definition ConfigFwd.hpp:90
std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override
Convert an app into a configuration.
Definition Config.hpp:305
char characterQuote
the character to use around single characters
Definition ConfigFwd.hpp:96
char stringQuote
the character to use around strings
Definition ConfigFwd.hpp:94
uint8_t maximumLayers
the maximum number of layers to allow
Definition ConfigFwd.hpp:98
char valueDelimiter
the character used separate the name from the value
Definition ConfigFwd.hpp:92
char arrayStart
the character used to start an array '\0' is a default to not use
Definition ConfigFwd.hpp:86
char parentSeparatorChar
the separator used to separator parent layers
Definition ConfigFwd.hpp:100
char arrayEnd
the character used to end an array '\0' is a default to not use
Definition ConfigFwd.hpp:88
int16_t configIndex
Specify the configuration index to use for arrayed sections.
Definition ConfigFwd.hpp:102
char commentChar
the character used for comments
Definition ConfigFwd.hpp:84
Definition Option.hpp:238
std::string & remove_quotes(std::string &str)
remove quotes at the front and back of a string either '"' or '\''
Definition StringTools.hpp:150
std::string ini_join(const std::vector< std::string > &args, char sepChar=',', char arrayStart='[', char arrayEnd=']', char stringQuote='"', char characterQuote = '\'')
Comma separated join, adds quotes if needed.
Definition Config.hpp:71
std::vector< std::string > output
Definition StringTools.hpp:354
std::string convert_arg_for_ini(const std::string &arg, char stringQuote='"', char characterQuote = '\'')
Definition Config.hpp:26
std::string trim_copy(const std::string &str)
Make a copy of the string and then trim it.
Definition StringTools.hpp:144
std::string & trim(std::string &str)
Trim whitespace from string.
Definition StringTools.hpp:138
std::string fix_newlines(const std::string &leader, std::string input)
Definition StringTools.hpp:164
std::vector< std::string > generate_parents(const std::string &section, std::string &name, char parentSeparator)
Definition Config.hpp:97
std::vector< std::string > split(const std::string &s, char delim)
Split a string by a delim.
Definition StringTools.hpp:46
std::string to_lower(std::string str)
Return a lower case version of a string.
Definition StringTools.hpp:259
bool lexical_cast(const std::string &input, T &output)
Integer conversion.
Definition TypeTools.hpp:883
void checkParentSegments(std::vector< ConfigItem > &output, const std::string &currentSection, char parentSeparator)
assuming non default segments do a check on the close and open of the segments in a configItem struct...
Definition Config.hpp:123
Definition App.hpp:34