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 <vector>
15 // [CLI11:public_includes:end]
16 
17 #include "Error.hpp"
18 #include "StringTools.hpp"
19 
20 namespace CLI {
21 // [CLI11:config_fwd_hpp:verbatim]
22 
23 class App;
24 
25 /// Holds values to load into Options
26 struct ConfigItem {
27     /// This is the list of parents
28     std::vector<std::string> parents{};
29 
30     /// This is the name
31     std::string name{};
32 
33     /// Listing of inputs
34     std::vector<std::string> inputs{};
35 
36     /// The list of parents and name joined by "."
fullnameCLI::ConfigItem37     std::string fullname() const {
38         std::vector<std::string> tmp = parents;
39         tmp.emplace_back(name);
40         return detail::join(tmp, ".");
41     }
42 };
43 
44 /// This class provides a converter for configuration files.
45 class Config {
46   protected:
47     std::vector<ConfigItem> items{};
48 
49   public:
50     /// Convert an app into a configuration
51     virtual std::string to_config(const App *, bool, bool, std::string) const = 0;
52 
53     /// Convert a configuration into an app
54     virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
55 
56     /// Get a flag value
to_flag(const ConfigItem & item) const57     virtual std::string to_flag(const ConfigItem &item) const {
58         if(item.inputs.size() == 1) {
59             return item.inputs.at(0);
60         }
61         throw ConversionError::TooManyInputsFlag(item.fullname());
62     }
63 
64     /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
from_file(const std::string & name)65     std::vector<ConfigItem> from_file(const std::string &name) {
66         std::ifstream input{name};
67         if(!input.good())
68             throw FileError::Missing(name);
69 
70         return from_config(input);
71     }
72 
73     /// Virtual destructor
74     virtual ~Config() = default;
75 };
76 
77 /// This converter works with INI/TOML files; to write INI files use ConfigINI
78 class ConfigBase : public Config {
79   protected:
80     /// the character used for comments
81     char commentChar = '#';
82     /// the character used to start an array '\0' is a default to not use
83     char arrayStart = '[';
84     /// the character used to end an array '\0' is a default to not use
85     char arrayEnd = ']';
86     /// the character used to separate elements in an array
87     char arraySeparator = ',';
88     /// the character used separate the name from the value
89     char valueDelimiter = '=';
90     /// the character to use around strings
91     char stringQuote = '"';
92     /// the character to use around single characters
93     char characterQuote = '\'';
94     /// the maximum number of layers to allow
95     uint8_t maximumLayers{255};
96     /// the separator used to separator parent layers
97     char parentSeparatorChar{'.'};
98     /// Specify the configuration index to use for arrayed sections
99     int16_t configIndex{-1};
100     /// Specify the configuration section that should be used
101     std::string configSection{};
102 
103   public:
104     std::string
105     to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override;
106 
107     std::vector<ConfigItem> from_config(std::istream &input) const override;
108     /// Specify the configuration for comment characters
comment(char cchar)109     ConfigBase *comment(char cchar) {
110         commentChar = cchar;
111         return this;
112     }
113     /// Specify the start and end characters for an array
arrayBounds(char aStart,char aEnd)114     ConfigBase *arrayBounds(char aStart, char aEnd) {
115         arrayStart = aStart;
116         arrayEnd = aEnd;
117         return this;
118     }
119     /// Specify the delimiter character for an array
arrayDelimiter(char aSep)120     ConfigBase *arrayDelimiter(char aSep) {
121         arraySeparator = aSep;
122         return this;
123     }
124     /// Specify the delimiter between a name and value
valueSeparator(char vSep)125     ConfigBase *valueSeparator(char vSep) {
126         valueDelimiter = vSep;
127         return this;
128     }
129     /// Specify the quote characters used around strings and characters
quoteCharacter(char qString,char qChar)130     ConfigBase *quoteCharacter(char qString, char qChar) {
131         stringQuote = qString;
132         characterQuote = qChar;
133         return this;
134     }
135     /// Specify the maximum number of parents
maxLayers(uint8_t layers)136     ConfigBase *maxLayers(uint8_t layers) {
137         maximumLayers = layers;
138         return this;
139     }
140     /// Specify the separator to use for parent layers
parentSeparator(char sep)141     ConfigBase *parentSeparator(char sep) {
142         parentSeparatorChar = sep;
143         return this;
144     }
145     /// get a reference to the configuration section
sectionRef()146     std::string &sectionRef() { return configSection; }
147     /// get the section
section() const148     const std::string &section() const { return configSection; }
149     /// specify a particular section of the configuration file to use
section(const std::string & sectionName)150     ConfigBase *section(const std::string &sectionName) {
151         configSection = sectionName;
152         return this;
153     }
154 
155     /// get a reference to the configuration index
indexRef()156     int16_t &indexRef() { return configIndex; }
157     /// get the section index
index() const158     int16_t index() const { return configIndex; }
159     /// specify a particular index in the section to use (-1) for all sections to use
index(int16_t sectionIndex)160     ConfigBase *index(int16_t sectionIndex) {
161         configIndex = sectionIndex;
162         return this;
163     }
164 };
165 
166 /// the default Config is the TOML file format
167 using ConfigTOML = ConfigBase;
168 
169 /// ConfigINI generates a "standard" INI compliant output
170 class ConfigINI : public ConfigTOML {
171 
172   public:
ConfigINI()173     ConfigINI() {
174         commentChar = ';';
175         arrayStart = '\0';
176         arrayEnd = '\0';
177         arraySeparator = ' ';
178         valueDelimiter = '=';
179     }
180 };
181 // [CLI11:config_fwd_hpp:end]
182 }  // namespace CLI
183