1 /** @file
2 
3   Powerful and easy-to-use command line parsing for ATS
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #pragma once
25 
26 #include <iostream>
27 #include <string>
28 #include <map>
29 #include <vector>
30 #include <functional>
31 #include <string_view>
32 
33 // more than zero arguments
34 constexpr unsigned MORE_THAN_ZERO_ARG_N = ~0;
35 // more than one arguments
36 constexpr unsigned MORE_THAN_ONE_ARG_N = ~0 - 1;
37 // customizable indent for help message
38 constexpr int INDENT_ONE = 32;
39 constexpr int INDENT_TWO = 46;
40 
41 namespace ts
42 {
43 using AP_StrVec = std::vector<std::string>;
44 // The class holding both the ENV and String arguments
45 class ArgumentData
46 {
47 public:
48   // bool to check if certain command/option is called
49   operator bool() const noexcept { return _is_called; }
50   // index accessing []
51   std::string const &
52   operator[](int x) const
53   {
54     return _values.at(x);
55   }
56   // return the Environment variable
57   std::string const &env() const noexcept;
58   // iterator for arguments
59   AP_StrVec::const_iterator begin() const noexcept;
60   AP_StrVec::const_iterator end() const noexcept;
61   // index accessing
62   std::string const &at(unsigned index) const;
63   // access the first index, equivalent to at(0)
64   std::string const &value() const noexcept;
65   // size of _values
66   size_t size() const noexcept;
67   // return true if _values and _env_value are both empty
68   bool empty() const noexcept;
69 
70 private:
71   bool _is_called = false;
72   // the environment variable
73   std::string _env_value;
74   // the arguments stored
75   AP_StrVec _values;
76 
77   friend class Arguments;
78 };
79 
80 // The class holding all the parsed data after ArgParser::parse()
81 class Arguments
82 {
83 public:
84   Arguments();
85   ~Arguments();
86 
87   ArgumentData get(std::string const &name);
88 
89   void append(std::string const &key, ArgumentData const &value);
90   // Append value to the arg to the map of key
91   void append_arg(std::string const &key, std::string const &value);
92   // append env value to the map with key
93   void set_env(std::string const &key, std::string const &value);
94   // Print all we have in the parsed data to the console
95   void show_all_configuration() const;
96   /** Invoke the function associated with the parsed command.
97       @return The return value of the executed command (int).
98   */
99   void invoke();
100   // return true if there is any function to invoke
101   bool has_action() const;
102 
103 private:
104   // A map of all the called parsed args/data
105   // Key: "command/option", value: ENV and args
106   std::map<std::string, ArgumentData> _data_map;
107   // The function associated. invoke() will call this func
108   std::function<void()> _action;
109 
110   friend class ArgParser;
111   friend class ArgumentData;
112 };
113 
114 // Class of the ArgParser
115 class ArgParser
116 {
117   using Function = std::function<void()>;
118 
119 public:
120   // Option structure: e.x. --arg -a
121   // Contains all information about certain option(--switch)
122   struct Option {
123     std::string long_option;   // long option: --arg
124     std::string short_option;  // short option: -a
125     std::string description;   // help description
126     std::string envvar;        // stored ENV variable
127     unsigned arg_num;          // number of argument expected
128     std::string default_value; // default value of option
129     std::string key;           // look-up key
130   };
131 
132   // Class for commands in a nested way
133   class Command
134   {
135   public:
136     // Constructor and destructor
137     Command();
138     ~Command();
139     /** Add an option to current command
140         @return The Option object.
141     */
142     Command &add_option(std::string const &long_option, std::string const &short_option, std::string const &description,
143                         std::string const &envvar = "", unsigned arg_num = 0, std::string const &default_value = "",
144                         std::string const &key = "");
145 
146     /** Two ways of adding a sub-command to current command:
147         @return The new sub-command instance.
148     */
149     Command &add_command(std::string const &cmd_name, std::string const &cmd_description, Function const &f = nullptr,
150                          std::string const &key = "");
151     Command &add_command(std::string const &cmd_name, std::string const &cmd_description, std::string const &cmd_envvar,
152                          unsigned cmd_arg_num, Function const &f = nullptr, std::string const &key = "");
153     /** Add an example usage of current command for the help message
154         @return The Command instance for chained calls.
155     */
156     Command &add_example_usage(std::string const &usage);
157     /** Require subcommand/options for this command
158         @return The Command instance for chained calls.
159     */
160     Command &require_commands();
161     /** set the current command as default
162         @return The Command instance for chained calls.
163     */
164     Command &set_default();
165 
166   protected:
167     // Main constructor called by add_command()
168     Command(std::string const &name, std::string const &description, std::string const &envvar, unsigned arg_num, Function const &f,
169             std::string const &key = "");
170     // Helper method for add_option to check the validity of option
171     void check_option(std::string const &long_option, std::string const &short_option, std::string const &key) const;
172     // Helper method for add_command to check the validity of command
173     void check_command(std::string const &name, std::string const &key) const;
174     // Helper method for ArgParser::help_message
175     void output_command(std::ostream &out, std::string const &prefix) const;
176     // Helper method for ArgParser::help_message
177     void output_option() const;
178     // Helper method for ArgParser::parse
179     bool parse(Arguments &ret, AP_StrVec &args);
180     // The help & version messages
181     void help_message(std::string_view err = "") const;
182     void version_message() const;
183     // Helper method for parse()
184     void append_option_data(Arguments &ret, AP_StrVec &args, int index);
185     // The command name and help message
186     std::string _name;
187     std::string _description;
188 
189     // Expected argument number
190     unsigned _arg_num = 0;
191     // Stored Env variable
192     std::string _envvar;
193     // An example usage can be added for the help message
194     std::string _example_usage;
195     // Function associated with this command
196     Function _f;
197     // look up key
198     std::string _key;
199 
200     // list of all subcommands of current command
201     // Key: command name. Value: Command object
202     std::map<std::string, Command> _subcommand_list;
203     // list of all options of current command
204     // Key: option name. Value: Option object
205     std::map<std::string, Option> _option_list;
206     // Map for fast searching: <short option: long option>
207     std::map<std::string, std::string> _option_map;
208 
209     // require command / option for this parser
210     bool _command_required = false;
211 
212     friend class ArgParser;
213   };
214   // Base class constructors and destructor
215   ArgParser();
216   ArgParser(std::string const &name, std::string const &description, std::string const &envvar, unsigned arg_num,
217             Function const &f);
218   ~ArgParser();
219 
220   /** Add an option to current command with arguments
221       @return The Option object.
222   */
223   Command &add_option(std::string const &long_option, std::string const &short_option, std::string const &description,
224                       std::string const &envvar = "", unsigned arg_num = 0, std::string const &default_value = "",
225                       std::string const &key = "");
226 
227   /** Two ways of adding command to the parser:
228       @return The new command instance.
229   */
230   Command &add_command(std::string const &cmd_name, std::string const &cmd_description, Function const &f = nullptr,
231                        std::string const &key = "");
232   Command &add_command(std::string const &cmd_name, std::string const &cmd_description, std::string const &cmd_envvar,
233                        unsigned cmd_arg_num, Function const &f = nullptr, std::string const &key = "");
234   // give a default command to this parser
235   void set_default_command(std::string const &cmd);
236   /** Main parsing function
237       @return The Arguments object available for program using
238   */
239   Arguments parse(const char **argv);
240   // Add the usage to global_usage for help_message(). Something like: traffic_blabla [--SWITCH [ARG]]
241   void add_global_usage(std::string const &usage);
242   // help message that can be called
243   void help_message(std::string_view err = "") const;
244   /** Require subcommand/options for this command
245       @return The Command instance for chained calls.
246   */
247   Command &require_commands();
248   // set the error message
249   void set_error(std::string e);
250   // get the error message
251   std::string get_error() const;
252 
253 protected:
254   // Converted from 'const char **argv' for the use of parsing and help
255   AP_StrVec _argv;
256   // the top level command object for program use
257   Command _top_level_command;
258   // user-customized error message output
259   std::string _error_msg;
260 
261   friend class Command;
262   friend class Arguments;
263 };
264 
265 } // namespace ts
266