RuntimeConfig API Feedback



  • Hallo zusammen,

    ich habe ein kleines "Modul" das einfach das Thema runtime configuration für kleine bis mittelgroße Programme lösen soll. Dies beinhaltet das Erstellen eines command line interfaces sowie einlesen einfacher INI-Style Konfigurationsdateien.

    Die Flexibilität ist begrenzt, aber es soll einfach zu verwenden sein und Parameter sowohl aus Konfigdateien als auch von der Kommandozeile entgegennehmen. Bekanntlicherweise ist es ja sehr nervig, wenn man das ganze Programm neu kompilieren muss, nur weil man ein paar Parameter ändern wollte.

    Mich würde interessieren, was ihr dazu meint (Design, Nützlichkeit, ...), nur aus Interesse:

    /// Holds key/value pairs (entries), optionally arranged into sections.
    /// This class is not very useful on its own and is meant to be used together with ConfigParser and/or ArgParser.
    class AppConfig {
    public:
        using Value = std::variant<std::string, double, bool>;
    
        struct Entry {
            Entry(std::string_view key, const Value& value)
                : key{key}, value{value} { }
            std::string key;
            Value value;
        };
    
        void addEntry(std::string_view key, const Value& value, std::string_view section = "");
    
        template<class T>
          T number(std::string_view key, std::string_view section = "", std::optional<T> defaultValue = {}) const;
        bool flag(std::string_view key, std::string_view section = "", std::optional<bool> defaultValue = {}) const;
        std::string_view text(std::string_view key, std::string_view section = "", std::optional<std::string_view> defaultValue = {}) const;
    
        bool hasEntry(std::string_view key, std::string_view section = "") const;
        bool hasSection(std::string_view section) const;
    };
    
    
    /// Parses configuration files in the INI format and stores the properties in an AppConfig.
    class ConfigParser {
    public:
        void parse(const std::string& configFile, AppConfig& cfg);
    };
    
    
    /// Parses command line arguments and applies them to an AppConfig.
    class ArgParser {
    public:
        explicit ArgParser(std::string_view description = "");
    
        void parse(int argc, const char* const * argv, AppConfig& cfg);
    
        void addArgument(std::string_view longName, char shortName = '\0');
        void addFlag(std::string_view longName, char shortName = '\0');
    };
    
    // Usage example:
    int main(int argc, char* argv[])
    try {
        AppConfig config;
        ConfigParser configParser;
        ArgParser argParser;
    
        argParser.addArgument("--iterations", 'n');
        argParser.addFlag("--verbose", 'v');
    
        configParser.parse("conf.ini", config);
        argParser.parse(argc, argv, config);
    
        // Access some values
        std::cout << "Iterations: " << config.number<int>("iterations", "", 100) << '\n';
    
        if (config.flag("verbose", "", false)) {
            if (config.hasEntry("path", "Section2")) {
                std::cout << "Path: " << config.text("path", "Section2") << '\n';
            } else {
                std::cout << "No path specified\n";    
            }
        }
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return 1;
    }
    
    // Example conf.ini:
    ; Number of iterations
    iterations=42
    [Section2]
    path=C:\foo\bar  ; Dummy variable
    

    Ich habe eben schon öfters bemerkt, dass ich so eine Lösung brauche, einfache Werte zur Laufzeit auszulesen. Ich weiß, dass es einzelne Lösungen gibt, die INI files oder Befehlszeilenargumente parsen. Aber ein so kombiniertes Interface und dazu einfach implementiert (ArgParser ~ 100 LOC, ConfigParser ~ 30 LOC, AppConfig ~ 100 LOC) kenne ich nicht. Wenn es jemand interessiert poste ich auch gerne die Implementierung zum Review.

    LG



  • Woran ich jetzt grad als erstes denken musste war, dass das größte Problem wohl ist, sowas zu finden und einfach einzubinden... Hab ich privat jetzt noch nicht gebraucht, aber in der Arbeit paar mal und dann hat ich mir immer gedacht, hmm, gibts da schon was in boost, oder in Qt, oder haben wir da vielleicht was geschrieben, oder hat vielleicht schon mal jemand eine Implementierung reingeholt... Und wenn man etwas googelt, dann findet man wahrscheinlich auch dutzende Implementierungen und steht vor der Qual der Wahl 😉

    Zu der Idee, Config und Kommandozeilenparser zu kombinieren, kann ich grad nichts sagen. Ist vermutlich keine schlechte Idee. Default Werte in der Config + overwrites.
    Schaut ganz annehmbar aus, mir fällt spontan nichts negatives auf. Was ich aber vielleicht anders gemacht hätte, ich hätte Parametern wahrscheinlich Ids gegeben. id = parser.addArgument(...), damit man beim Abfragen nicht wieder den Namen angeben muss. Ist aber alles nicht durchdacht, nur eine spontane Beobachtung/Idee.



  • OK, also es scheint mit dem property tree in boost relativ einfach zu gehen. Eine tolle Inspiration hierbei finde ich auch section und entry name im selben String anzugeben (getrennt durch ein reserviertes Zeichen, z. B. "Section.Key"). In Qt gibt es ja auch QSettings.

    Außerdem gibt es auch boost::program_options und QCommandLineParser gibt es auch.

    Eine weitere schwäche meiner API wäre, dass man darüber keine Einstellungen schreiben kann...

    Ich finde die Idee mit einer ID auch interessant, allerdings in Kombination mit zusätzlichen features. Die ID muss ich ja auch in eine Variable speichern oder ein enum einführen etc. und den Namen dessen muss man genauso tippen, nur nicht zwischen zwei "" (wobei ich bei der Verwendung verschiedener sections doch eine gewisse Vereinfachung sehen kann).



  • @HarteWare sagte in RuntimeConfig API Feedback:

    Außerdem gibt es auch boost::program_options und QCommandLineParser gibt es auch.

    Wo du das sagst, kommt mir das auch bekannt vor 😉 Wär mir aber spontan nicht eingefallen, hätt ich suchen müssen.

    Naja, bei Features kann man sich alles mögliche vorstellen. z.B. zusammenstöpseln von "Datenquellen". Vielleicht will ich das ja auch in einem REST Service verwenden, die Parameter kommen über HTTP, die Default-Werte stehen in der Config. Das kann dann schnell ausarten. Deswegen fand ich sowas selber kaum noch an, weil das bei mir so gut wie immer ausartet 😉


Log in to reply