mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-30 04:33:53 +00:00
This adds a round trip test for config file generation to the fuzzer. (the next step after this PR will be a fuzzer that verifies that the round trip actually matches the results. This change ended up requiring quite a few minor changes to fix the ambiguities between the config file generation and config file reader. 1). There was a number of potential conflicts between positional names and regular option names that could be triggered in config files, this required a number of additional checks on the positional naming to ensure no conflicts. 2). flag options with disable flag override can produce output results that are not valid by themselves, resolving this required flag input to be able to handle an array and output the original value set of results. 3). strings with non-printable characters could cause all sorts of chaos in the config files. This was resolved by generating a binary string conversion format and handling multiline comments and characters, and handling escaped characters. Note; I think a better solution is to move to fully supporting string formatting and escaping along with the binary strings from TOML now that TOML 1.0 is finalized. That will not be this PR though, maybe the next one. 4). Lot of ambiguities and edge cases in the string splitter, this was reworked 5). handling of comments was not done well, especially comment characters in the name of the option which is allowed. 6). non printable characters in the option naming. This would be weird in practice but it also cause some big holes in the config file generation, so the restricted character set for option naming was expanded. (don't allow spaces or control characters). --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
186 lines
5.7 KiB
C++
186 lines
5.7 KiB
C++
// Copyright (c) 2017-2023, 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
|
|
|
|
// [CLI11:public_includes:set]
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
// [CLI11:public_includes:end]
|
|
|
|
#include "Error.hpp"
|
|
#include "StringTools.hpp"
|
|
|
|
namespace CLI {
|
|
// [CLI11:config_fwd_hpp:verbatim]
|
|
|
|
class App;
|
|
|
|
/// Holds values to load into Options
|
|
struct ConfigItem {
|
|
/// This is the list of parents
|
|
std::vector<std::string> parents{};
|
|
|
|
/// This is the name
|
|
std::string name{};
|
|
/// Listing of inputs
|
|
std::vector<std::string> inputs{};
|
|
|
|
/// The list of parents and name joined by "."
|
|
CLI11_NODISCARD std::string fullname() const {
|
|
std::vector<std::string> tmp = parents;
|
|
tmp.emplace_back(name);
|
|
return detail::join(tmp, ".");
|
|
}
|
|
};
|
|
|
|
/// This class provides a converter for configuration files.
|
|
class Config {
|
|
protected:
|
|
std::vector<ConfigItem> items{};
|
|
|
|
public:
|
|
/// Convert an app into a configuration
|
|
virtual std::string to_config(const App *, bool, bool, std::string) const = 0;
|
|
|
|
/// Convert a configuration into an app
|
|
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
|
|
|
|
/// Get a flag value
|
|
CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const {
|
|
if(item.inputs.size() == 1) {
|
|
return item.inputs.at(0);
|
|
}
|
|
if(item.inputs.empty()) {
|
|
return "{}";
|
|
}
|
|
throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE
|
|
}
|
|
|
|
/// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
|
|
CLI11_NODISCARD std::vector<ConfigItem> from_file(const std::string &name) const {
|
|
std::ifstream input{name};
|
|
if(!input.good())
|
|
throw FileError::Missing(name);
|
|
|
|
return from_config(input);
|
|
}
|
|
|
|
/// Virtual destructor
|
|
virtual ~Config() = default;
|
|
};
|
|
|
|
/// This converter works with INI/TOML files; to write INI files use ConfigINI
|
|
class ConfigBase : public Config {
|
|
protected:
|
|
/// the character used for comments
|
|
char commentChar = '#';
|
|
/// the character used to start an array '\0' is a default to not use
|
|
char arrayStart = '[';
|
|
/// the character used to end an array '\0' is a default to not use
|
|
char arrayEnd = ']';
|
|
/// the character used to separate elements in an array
|
|
char arraySeparator = ',';
|
|
/// the character used separate the name from the value
|
|
char valueDelimiter = '=';
|
|
/// the character to use around strings
|
|
char stringQuote = '"';
|
|
/// the character to use around single characters
|
|
char characterQuote = '\'';
|
|
/// the maximum number of layers to allow
|
|
uint8_t maximumLayers{255};
|
|
/// the separator used to separator parent layers
|
|
char parentSeparatorChar{'.'};
|
|
/// Specify the configuration index to use for arrayed sections
|
|
int16_t configIndex{-1};
|
|
/// Specify the configuration section that should be used
|
|
std::string configSection{};
|
|
|
|
public:
|
|
std::string
|
|
to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override;
|
|
|
|
std::vector<ConfigItem> from_config(std::istream &input) const override;
|
|
/// Specify the configuration for comment characters
|
|
ConfigBase *comment(char cchar) {
|
|
commentChar = cchar;
|
|
return this;
|
|
}
|
|
/// Specify the start and end characters for an array
|
|
ConfigBase *arrayBounds(char aStart, char aEnd) {
|
|
arrayStart = aStart;
|
|
arrayEnd = aEnd;
|
|
return this;
|
|
}
|
|
/// Specify the delimiter character for an array
|
|
ConfigBase *arrayDelimiter(char aSep) {
|
|
arraySeparator = aSep;
|
|
return this;
|
|
}
|
|
/// Specify the delimiter between a name and value
|
|
ConfigBase *valueSeparator(char vSep) {
|
|
valueDelimiter = vSep;
|
|
return this;
|
|
}
|
|
/// Specify the quote characters used around strings and characters
|
|
ConfigBase *quoteCharacter(char qString, char qChar) {
|
|
stringQuote = qString;
|
|
characterQuote = qChar;
|
|
return this;
|
|
}
|
|
/// Specify the maximum number of parents
|
|
ConfigBase *maxLayers(uint8_t layers) {
|
|
maximumLayers = layers;
|
|
return this;
|
|
}
|
|
/// Specify the separator to use for parent layers
|
|
ConfigBase *parentSeparator(char sep) {
|
|
parentSeparatorChar = sep;
|
|
return this;
|
|
}
|
|
/// get a reference to the configuration section
|
|
std::string §ionRef() { return configSection; }
|
|
/// get the section
|
|
CLI11_NODISCARD const std::string §ion() const { return configSection; }
|
|
/// specify a particular section of the configuration file to use
|
|
ConfigBase *section(const std::string §ionName) {
|
|
configSection = sectionName;
|
|
return this;
|
|
}
|
|
|
|
/// get a reference to the configuration index
|
|
int16_t &indexRef() { return configIndex; }
|
|
/// get the section index
|
|
CLI11_NODISCARD int16_t index() const { return configIndex; }
|
|
/// specify a particular index in the section to use (-1) for all sections to use
|
|
ConfigBase *index(int16_t sectionIndex) {
|
|
configIndex = sectionIndex;
|
|
return this;
|
|
}
|
|
};
|
|
|
|
/// the default Config is the TOML file format
|
|
using ConfigTOML = ConfigBase;
|
|
|
|
/// ConfigINI generates a "standard" INI compliant output
|
|
class ConfigINI : public ConfigTOML {
|
|
|
|
public:
|
|
ConfigINI() {
|
|
commentChar = ';';
|
|
arrayStart = '\0';
|
|
arrayEnd = '\0';
|
|
arraySeparator = ' ';
|
|
valueDelimiter = '=';
|
|
}
|
|
};
|
|
// [CLI11:config_fwd_hpp:end]
|
|
} // namespace CLI
|