File indexing completed on 2024-11-24 03:56:29

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #pragma once
0008 
0009 #include <stdexcept>
0010 #include <optional>
0011 
0012 #include <QVariantHash>
0013 #include <QString>
0014 #include <QSet>
0015 
0016 namespace app::cli {
0017 
0018 
0019 class ArgumentError : public std::invalid_argument
0020 {
0021 public:
0022     ArgumentError(const QString& what) : std::invalid_argument(what.toStdString()) {}
0023 
0024     QString message() const
0025     {
0026         return QString(what());
0027     }
0028 };
0029 
0030 struct Argument
0031 {
0032     enum Type
0033     {
0034         Flag,
0035         String,
0036         Int,
0037         Size,
0038         ShowHelp,
0039         ShowVersion
0040     };
0041 
0042     QStringList names;
0043     QString description;
0044     Type type = String;
0045     QString arg_name;
0046     QString dest;
0047     int nargs = 0;
0048     QVariant default_value;
0049 
0050     Argument(
0051         const QStringList& names,
0052         const QString& description,
0053         Type type,
0054         const QVariant& default_value = {},
0055         const QString& arg_name = {},
0056         const QString& dest = {},
0057         int nargs = 1
0058     ) : names(names),
0059         description(description),
0060         type(type),
0061         arg_name(arg_name),
0062         dest(dest),
0063         nargs(nargs),
0064         default_value(default_value)
0065     {
0066         if ( this->dest.isEmpty() )
0067             this->dest = get_slug(names);
0068         if ( this->arg_name.isEmpty() )
0069             this->arg_name = this->dest;
0070     }
0071 
0072     Argument(
0073         const QStringList& names,
0074         const QString& description
0075     ) : names(names),
0076         description(description),
0077         type(Flag),
0078         default_value(false)
0079     {
0080         if ( this->dest.isEmpty() )
0081             this->dest = get_slug(names);
0082 
0083         // positional
0084         if ( names.size() == 1 && !names[0].startsWith('-') )
0085         {
0086             type = String;
0087             nargs = 1;
0088             arg_name = names[0];
0089         }
0090     }
0091 
0092     bool is_positional() const;
0093 
0094 
0095     static QString get_slug(const QStringList& names);
0096 
0097     QVariant arg_to_value(const QString& v, bool* ok) const;
0098 
0099     QVariant arg_to_value(const QString& v) const;
0100 
0101     QVariant args_to_value(const QStringList& args, int& index) const;
0102 
0103     QString help_text_name() const;
0104 
0105 };
0106 
0107 void show_message(const QString& msg, bool error = false);
0108 
0109 struct ParsedArguments
0110 {
0111     QVariantMap values;
0112     QSet<QString> defined;
0113     QSet<QString> flags;
0114     std::optional<int> return_value;
0115 
0116     bool has_flag(const QString& name) const
0117     {
0118         return flags.contains(name);
0119     }
0120 
0121     QVariant value(const QString& name) const
0122     {
0123         return values[name];
0124     }
0125 
0126     bool is_defined(const QString& name) const
0127     {
0128         return defined.contains(name);
0129     }
0130 
0131 
0132     void handle_error(const QString& error);
0133     void handle_finish(const QString& message);
0134 };
0135 
0136 class Parser
0137 {
0138 private:
0139     enum RefType
0140     {
0141         Option,
0142         Positional
0143     };
0144 
0145     struct ArgumentGroup
0146     {
0147         QString name;
0148         std::vector<std::pair<RefType, int>> args = {};
0149     };
0150 
0151 public:
0152     explicit Parser(const QString& description) : description(description) {}
0153 
0154     app::cli::Parser& add_argument(Argument arg);
0155     ParsedArguments parse(const QStringList& args, int offset = 1) const;
0156     app::cli::Parser& add_group(const QString& name);
0157 
0158     const Argument* option_from_arg(const QString& arg) const;
0159 
0160     QString version_text() const;
0161     QString help_text() const;
0162 
0163 private:
0164     QString wrap_text(const QString& names, int name_max, const QString& description) const;
0165 
0166     QString description;
0167     std::vector<Argument> options = {};
0168     std::vector<Argument> positional = {};
0169     std::vector<ArgumentGroup> groups = {};
0170 };
0171 
0172 } // namespace app::cli