Imported Upstream version 17.23.7
[platform/upstream/libzypp.git] / tools / argparse.h
1 #ifndef ZYPP_TOOLS_ARGPARSE_H
2 #define ZYPP_TOOLS_ARGPARSE_H
3
4 #include <iosfwd>
5 #include <string>
6 #include <exception>
7 #include <unordered_map>
8 #include <unordered_set>
9 #include <vector>
10
11 //#include <regex> seems to be bad with gcc < 4.9
12 #include <zypp/base/Regex.h>
13
14 ///////////////////////////////////////////////////////////////////
15 /// Simple arg parser for tools
16 /// \code
17 ///  argparse::Options options;
18 ///  options.add()
19 ///    ( "help,h",      "Print help and exit." )
20 ///    ( "root",        "Use the system located below ROOTDIR.", argparse::Option::Arg::required )
21 ///    ;
22 ///  auto result = options.parse( argc, argv );
23 ///
24 ///  if ( result.count( "root" ) )
25 ///    sysRoot = result["root"].arg();
26 /// \endcode
27 namespace argparse
28 {
29   using zypp::str::regex;
30   using zypp::str::smatch;
31   using zypp::str::regex_match;
32
33   ///////////////////////////////////////////////////////////////////
34   /// Exception thrown when defining Options or parsing.
35   class OptionException : public std::exception
36   {
37   public:
38     OptionException( std::string msg_r )
39     : _msg { std::move(msg_r) }
40     {}
41
42     OptionException( std::string msg_r, const std::string & msg2_r )
43     : _msg { std::move(msg_r) }
44     { if ( ! msg2_r.empty() ) _msg += msg2_r; }
45
46     const char* what() const noexcept override
47     { return _msg.c_str(); }
48
49   private:
50     std::string _msg;
51   };
52
53   ///////////////////////////////////////////////////////////////////
54   /// Option description (TBD define arg consumer)
55   class Option
56   {
57   public:
58     enum class Arg { none, required, optional };
59
60   public:
61     Option( std::string descr_r, Arg hasarg_r  )
62     : _descr { std::move(descr_r) }
63     , _hasarg { hasarg_r }
64     {
65       if ( hasarg_r == Arg::optional )
66         throw OptionException( "Not yet implemented: Option::Arg::optional" );
67     }
68
69     const std::string & descr() const
70     { return _descr; }
71
72     Arg hasarg() const
73     { return _hasarg; }
74
75   private:
76     std::string _descr;
77     Arg         _hasarg;
78   };
79
80
81   class ParsedOptions;
82
83   ///////////////////////////////////////////////////////////////////
84   /// Map of option names -> option descriptions.
85   class Options
86   {
87     typedef std::unordered_map<std::string, std::shared_ptr<const Option>> OptionMap;
88   public:
89     Options()
90     {}
91
92   public:
93     class Injector
94     {
95     public:
96       Injector( OptionMap & optmap_r )
97       : _optmap { optmap_r }
98       {}
99
100       Injector & operator()( const std::string & names_r, std::string descr_r, Option::Arg hasarg_r = Option::Arg::none )
101       {
102         smatch result;
103         if ( regex_match( names_r, result, regex("([[:alnum:]][-_[:alnum:]]+)(,([[:alnum:]]))?") ) )
104         {
105           auto opt = std::make_shared<const Option>( std::move(descr_r), hasarg_r );
106           add( result[1], opt );
107           if ( ! result[3].empty() )
108             add( result[3], opt );
109         }
110         else
111           throw OptionException( "Illegal option names: ", names_r );
112
113         return *this;
114       }
115
116     private:
117       void add( std::string name_r, std::shared_ptr<const Option> opt_r )
118       {
119         if ( _optmap.count( name_r ) )
120           throw OptionException( "Duplicate option name: ", name_r );
121         _optmap[name_r] = opt_r;
122       }
123
124     private:
125       OptionMap & _optmap;
126     };
127
128     Injector add()
129     { return Injector( _optmap ); }
130
131   public:
132     ParsedOptions parse( int argc, char * argv[] ) const;
133
134   public:
135     std::ostream & dumpOn( std::ostream & str_r ) const
136     {
137       str_r << "OPTIONS:";
138       if ( ! _optmap.empty() )
139       {
140         std::unordered_map<std::shared_ptr<const Option>, std::string> unify;
141         for ( const auto & p : _optmap )
142         {
143           std::string & t { unify[p.second] };
144           if ( t.empty() )
145             t = (p.first.length()>1?"--":"-")+p.first;
146           else if ( p.first.length() > 1 )
147             t = "--"+p.first+", "+t;
148           else
149             t = t+", -"+p.first;
150         }
151
152         boost::format fmt( "\n    %1% %|30t|%2%" );
153         for ( const auto & p : unify )
154         {
155           fmt % p.second % p.first->descr();
156           str_r << fmt.str();
157         }
158       }
159       else
160       {
161         str_r << "    This command accepts no options.";
162       }
163       return str_r;
164     }
165
166   private:
167     OptionMap _optmap;
168   };
169
170   inline std::ostream & operator<<( std::ostream & str_r, const Options & obj_r )
171   { return obj_r.dumpOn( str_r ); }
172
173   ///////////////////////////////////////////////////////////////////
174   /// Parsed option incl. option args value (by now just stores the string)
175   class OptionValue
176   {
177   public:
178     OptionValue( std::shared_ptr<const Option> opt_r )
179     : _opt { opt_r }
180     {}
181
182     OptionValue( std::shared_ptr<const Option> opt_r, std::string arg_r )
183     : _opt { opt_r }
184     , _arg { std::make_shared<std::string>( std::move(arg_r) ) }
185     {}
186
187     const std::string & arg() const
188     {
189       if ( ! _arg )
190         throw std::domain_error( "No arg value" );
191
192       return *_arg;
193     }
194
195   private:
196     std::shared_ptr<const Option> _opt;
197     std::shared_ptr<std::string> _arg;
198   };
199
200   ///////////////////////////////////////////////////////////////////
201   /// Parsed options and positional args
202   class ParsedOptions
203   {
204     typedef std::unordered_map<std::string, std::shared_ptr<const Option>> OptionMap;
205     typedef std::unordered_map<std::shared_ptr<const Option>, OptionValue> ResultMap;
206   public:
207     ParsedOptions( const OptionMap & optmap_r, int argc, char * argv[] )
208     : _optmap { optmap_r }
209     { parse( argc, argv ); }
210
211   public:
212     size_t count( const std::string & optname_r ) const
213     {
214       auto iter = _optmap.find( optname_r );
215       if ( iter == _optmap.end() )
216         return 0;
217
218       auto resiter = _options.find( iter->second );
219       return( resiter == _options.end() ? 0 : 1 );
220     }
221
222     const OptionValue & operator[]( const std::string & optname_r ) const
223     {
224       return requireOptValByName( optname_r );
225     }
226
227     const std::vector<std::string> & positionals() const
228     { return _positionals; }
229
230   private:
231     void parse( int argc, char * argv[] )
232     {
233       bool collectpositional = false;
234       for ( --argc,++argv; argc; --argc,++argv )
235       {
236         if ( (*argv)[0] == '-' && !collectpositional )
237         {
238           if ( (*argv)[1] == '-' )
239           {
240             if ( (*argv)[2] == '\0' )
241             {
242               // -- rest are positional...
243               collectpositional = true;
244             }
245             else
246             {
247               // --longopt
248               parseoptl( (*argv)+2, argc, argv );
249             }
250           }
251           else
252           {
253             // -s(hortopt)
254             parseopts( (*argv)+1, argc, argv );
255           }
256         }
257         else
258         {
259           // positional
260           _positionals.push_back( *argv );
261         }
262       }
263     }
264
265   private:
266     std::string dashed( std::string name_r ) const
267     { return name_r.insert( 0, name_r.size()>1?"--":"-" ); }
268
269     void parseoptl( const std::string & name_r, int & argc, char **& argv )
270     {
271       if ( name_r.length() < 2 )
272         throw OptionException( "Illegal long opt: --", name_r );
273
274       parseopt( name_r, argc, argv );
275     }
276
277     void parseopts( const std::string & name_r, int & argc, char **& argv )
278     {
279      if ( name_r.length() != 1 )
280         throw OptionException( "Illegal short opt: -", name_r );
281
282      parseopt( name_r, argc, argv );
283     }
284
285     void parseopt( const std::string & name_r, int & argc, char **& argv )
286     {
287       auto opt = requireOptByName( name_r );
288
289       auto iter = _options.find( opt );
290       if ( iter != _options.end() )
291         throw OptionException( "Multiple occurrences of option: ", dashed( name_r ) );
292
293       if ( opt->hasarg() != Option::Arg::none )
294       {
295         if ( opt->hasarg() == Option::Arg::optional )
296           throw OptionException( "Not yet implemented: Option::Arg::optional" ); // i.e. '--opt=arg'
297
298         if ( argc < 2 )
299           throw OptionException( "Missing argument for option: ", dashed( name_r ) );
300
301         --argc,++argv;
302         moveToResult( opt, OptionValue( opt, *argv ) );
303       }
304       else
305         moveToResult( opt, OptionValue( opt ) );
306     }
307
308     void moveToResult( std::shared_ptr<const Option> opt_r, OptionValue && value_r )
309     { _options.insert( std::make_pair( opt_r, std::move(value_r) ) ); }
310
311     std::shared_ptr<const Option> requireOptByName( const std::string & name_r ) const
312     {
313       auto iter = _optmap.find( name_r );
314       if ( iter == _optmap.end() )
315         throw OptionException( "Unknown option: ", dashed( name_r ) );
316       return iter->second;
317     }
318
319     const OptionValue & requireOptValByName( const std::string & name_r ) const
320     {
321       auto iter = _options.find( requireOptByName( name_r ) );
322       if ( iter == _options.end() )
323         throw OptionException( "Option not present: ", dashed( name_r ) );
324       return iter->second;
325     }
326
327   private:
328     const OptionMap &           _optmap;
329     ResultMap                   _options;
330     std::vector<std::string>    _positionals;
331   };
332
333   inline ParsedOptions Options::parse( int argc, char * argv[] ) const
334   { return ParsedOptions( _optmap, argc, argv ); }
335
336
337
338 } // namespace argparse
339 ///////////////////////////////////////////////////////////////////
340 #endif // ZYPP_TOOLS_ARGPARSE_H