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