CLI11  2.1.2
Config.hpp
Go to the documentation of this file.
1 // Copyright (c) 2017-2021, 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 
22 namespace CLI {
23 // [CLI11:config_hpp:verbatim]
24 namespace detail {
25 
26 inline 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 
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(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 
97 inline 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();
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 }
120 
122 inline void
123 checkParentSegments(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 
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);
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) {
260  detail::remove_quotes(name);
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 
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  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:69
bool get_configurable() const
Check the status of the allow windows style options.
Definition: App.hpp:1672
App * get_parent()
Get the parent of this subcommand (or nullptr if main app)
Definition: App.hpp:1743
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:1562
std::vector< std::string > get_groups() const
Get the groups available directly from this option (in order)
Definition: App.hpp:1807
std::string get_description() const
Get the app or subcommand description.
Definition: App.hpp:1553
bool got_subcommand(const App *subcom) const
Check to see if given subcommand was selected.
Definition: App.hpp:1385
std::vector< App * > get_subcommands() const
Definition: App.hpp:1347
const std::string & get_name() const
Get the name of the current app.
Definition: App.hpp:1749
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:101
char arraySeparator
the character used to separate elements in an array
Definition: ConfigFwd.hpp:87
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:93
char stringQuote
the character to use around strings
Definition: ConfigFwd.hpp:91
uint8_t maximumLayers
the maximum number of layers to allow
Definition: ConfigFwd.hpp:95
char valueDelimiter
the character used separate the name from the value
Definition: ConfigFwd.hpp:89
char arrayStart
the character used to start an array '\0' is a default to not use
Definition: ConfigFwd.hpp:83
char parentSeparatorChar
the separator used to separator parent layers
Definition: ConfigFwd.hpp:97
char arrayEnd
the character used to end an array '\0' is a default to not use
Definition: ConfigFwd.hpp:85
int16_t configIndex
Specify the configuration index to use for arrayed sections.
Definition: ConfigFwd.hpp:99
char commentChar
the character used for comments
Definition: ConfigFwd.hpp:81
Definition: Option.hpp:237
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::string convert_arg_for_ini(const std::string &arg, char stringQuote='"', char characterQuote = '\'')
Definition: Config.hpp:26
std::string & remove_quotes(std::string &str)
remove quotes at the front and back of a string either '"' or '\''
Definition: StringTools.hpp:150
std::vector< std::string > generate_parents(const std::string &section, std::string &name, char parentSeparator)
Definition: Config.hpp:97
std::string trim_copy(const std::string &str)
Make a copy of the string and then trim it.
Definition: StringTools.hpp:144
std::string fix_newlines(const std::string &leader, std::string input)
Definition: StringTools.hpp:164
std::vector< std::string > split(const std::string &s, char delim)
Split a string by a delim.
Definition: StringTools.hpp:46
std::vector< std::string > split_up(std::string str, char delimiter='\0')
Definition: StringTools.hpp:346
std::string & trim(std::string &str)
Trim whitespace from string.
Definition: StringTools.hpp:138
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:862
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