1 #ifndef ZYPP_TOOLS_ARGPARSE_H
2 #define ZYPP_TOOLS_ARGPARSE_H
7 #include <unordered_map>
8 #include <unordered_set>
11 //#include <regex> seems to be bad with gcc < 4.9
12 #include <zypp/base/Regex.h>
14 ///////////////////////////////////////////////////////////////////
15 /// Simple arg parser for tools
17 /// argparse::Options options;
19 /// ( "help,h", "Print help and exit." )
20 /// ( "root", "Use the system located below ROOTDIR.", argparse::Option::Arg::required )
22 /// auto result = options.parse( argc, argv );
24 /// if ( result.count( "root" ) )
25 /// sysRoot = result["root"].arg();
29 using zypp::str::regex;
30 using zypp::str::smatch;
31 using zypp::str::regex_match;
33 ///////////////////////////////////////////////////////////////////
34 /// Exception thrown when defining Options or parsing.
35 class OptionException : public std::exception
38 OptionException( std::string msg_r )
39 : _msg { std::move(msg_r) }
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; }
46 const char* what() const noexcept override
47 { return _msg.c_str(); }
53 ///////////////////////////////////////////////////////////////////
54 /// Option description (TBD define arg consumer)
58 enum class Arg { none, required, optional };
61 Option( std::string descr_r, Arg hasarg_r )
62 : _descr { std::move(descr_r) }
63 , _hasarg { hasarg_r }
65 if ( hasarg_r == Arg::optional )
66 throw OptionException( "Not yet implemented: Option::Arg::optional" );
69 const std::string & descr() const
83 ///////////////////////////////////////////////////////////////////
84 /// Map of option names -> option descriptions.
87 typedef std::unordered_map<std::string, std::shared_ptr<const Option>> OptionMap;
96 Injector( OptionMap & optmap_r )
97 : _optmap { optmap_r }
100 Injector & operator()( const std::string & names_r, std::string descr_r, Option::Arg hasarg_r = Option::Arg::none )
103 if ( regex_match( names_r, result, regex("([[:alnum:]][-_[:alnum:]]+)(,([[:alnum:]]))?") ) )
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 );
111 throw OptionException( "Illegal option names: ", names_r );
117 void add( std::string name_r, std::shared_ptr<const Option> opt_r )
119 if ( _optmap.count( name_r ) )
120 throw OptionException( "Duplicate option name: ", name_r );
121 _optmap[name_r] = opt_r;
129 { return Injector( _optmap ); }
132 ParsedOptions parse( int argc, char * argv[] ) const;
135 std::ostream & dumpOn( std::ostream & str_r ) const
138 if ( ! _optmap.empty() )
140 std::unordered_map<std::shared_ptr<const Option>, std::string> unify;
141 for ( const auto & p : _optmap )
143 std::string & t { unify[p.second] };
145 t = (p.first.length()>1?"--":"-")+p.first;
146 else if ( p.first.length() > 1 )
147 t = "--"+p.first+", "+t;
152 boost::format fmt( "\n %1% %|30t|%2%" );
153 for ( const auto & p : unify )
155 fmt % p.second % p.first->descr();
161 str_r << " This command accepts no options.";
170 inline std::ostream & operator<<( std::ostream & str_r, const Options & obj_r )
171 { return obj_r.dumpOn( str_r ); }
173 ///////////////////////////////////////////////////////////////////
174 /// Parsed option incl. option args value (by now just stores the string)
178 OptionValue( std::shared_ptr<const Option> opt_r )
182 OptionValue( std::shared_ptr<const Option> opt_r, std::string arg_r )
184 , _arg { std::make_shared<std::string>( std::move(arg_r) ) }
187 const std::string & arg() const
190 throw std::domain_error( "No arg value" );
196 std::shared_ptr<const Option> _opt;
197 std::shared_ptr<std::string> _arg;
200 ///////////////////////////////////////////////////////////////////
201 /// Parsed options and positional args
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;
207 ParsedOptions( const OptionMap & optmap_r, int argc, char * argv[] )
208 : _optmap { optmap_r }
209 { parse( argc, argv ); }
212 size_t count( const std::string & optname_r ) const
214 auto iter = _optmap.find( optname_r );
215 if ( iter == _optmap.end() )
218 auto resiter = _options.find( iter->second );
219 return( resiter == _options.end() ? 0 : 1 );
222 const OptionValue & operator[]( const std::string & optname_r ) const
224 return requireOptValByName( optname_r );
227 const std::vector<std::string> & positionals() const
228 { return _positionals; }
231 void parse( int argc, char * argv[] )
233 bool collectpositional = false;
234 for ( --argc,++argv; argc; --argc,++argv )
236 if ( (*argv)[0] == '-' && !collectpositional )
238 if ( (*argv)[1] == '-' )
240 if ( (*argv)[2] == '\0' )
242 // -- rest are positional...
243 collectpositional = true;
248 parseoptl( (*argv)+2, argc, argv );
254 parseopts( (*argv)+1, argc, argv );
260 _positionals.push_back( *argv );
266 std::string dashed( std::string name_r ) const
267 { return name_r.insert( 0, name_r.size()>1?"--":"-" ); }
269 void parseoptl( const std::string & name_r, int & argc, char **& argv )
271 if ( name_r.length() < 2 )
272 throw OptionException( "Illegal long opt: --", name_r );
274 parseopt( name_r, argc, argv );
277 void parseopts( const std::string & name_r, int & argc, char **& argv )
279 if ( name_r.length() != 1 )
280 throw OptionException( "Illegal short opt: -", name_r );
282 parseopt( name_r, argc, argv );
285 void parseopt( const std::string & name_r, int & argc, char **& argv )
287 auto opt = requireOptByName( name_r );
289 auto iter = _options.find( opt );
290 if ( iter != _options.end() )
291 throw OptionException( "Multiple occurrences of option: ", dashed( name_r ) );
293 if ( opt->hasarg() != Option::Arg::none )
295 if ( opt->hasarg() == Option::Arg::optional )
296 throw OptionException( "Not yet implemented: Option::Arg::optional" ); // i.e. '--opt=arg'
299 throw OptionException( "Missing argument for option: ", dashed( name_r ) );
302 moveToResult( opt, OptionValue( opt, *argv ) );
305 moveToResult( opt, OptionValue( opt ) );
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) ) ); }
311 std::shared_ptr<const Option> requireOptByName( const std::string & name_r ) const
313 auto iter = _optmap.find( name_r );
314 if ( iter == _optmap.end() )
315 throw OptionException( "Unknown option: ", dashed( name_r ) );
319 const OptionValue & requireOptValByName( const std::string & name_r ) const
321 auto iter = _options.find( requireOptByName( name_r ) );
322 if ( iter == _options.end() )
323 throw OptionException( "Option not present: ", dashed( name_r ) );
328 const OptionMap & _optmap;
330 std::vector<std::string> _positionals;
333 inline ParsedOptions Options::parse( int argc, char * argv[] ) const
334 { return ParsedOptions( _optmap, argc, argv ); }
338 } // namespace argparse
339 ///////////////////////////////////////////////////////////////////
340 #endif // ZYPP_TOOLS_ARGPARSE_H