1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-30 04:33:53 +00:00
CLI11/include/CLI/ConfigFwd.hpp
Philip Top 0f5bf21e91
add some reduction methods to the options on the fuzz tests (#930)
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>
2023-12-18 05:21:32 -08:00

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 &sectionRef() { return configSection; }
/// get the section
CLI11_NODISCARD const std::string &section() const { return configSection; }
/// specify a particular section of the configuration file to use
ConfigBase *section(const std::string &sectionName) {
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