ggetopt.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2001-2013 Graeme Walker <graeme_walker@users.sourceforge.net>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // ===
17 //
18 // ggetopt.cpp
19 //
20 
21 #include "gdef.h"
22 #include "glog.h"
23 #include "gstrings.h"
24 #include "gstr.h"
25 #include "ggetopt.h"
26 #include "gassert.h"
27 #include "gdebug.h"
28 #include "genvironment.h"
29 #include <sstream>
30 #include <algorithm>
31 #include <functional>
32 #include <utility>
33 
34 namespace
35 {
36  bool eqc( char c , std::pair<std::string,G::GetOpt::SwitchSpec> p )
37  {
38  return p.second.c == c ;
39  }
40  bool eqn( std::string n , std::pair<std::string,G::GetOpt::SwitchSpec> p )
41  {
42  return p.second.name == n ;
43  }
44 }
45 
46 G::GetOpt::GetOpt( const Arg & args_in , const std::string & spec ,
47  char sep_major , char sep_minor , char escape ) :
48  m_args(args_in)
49 {
50  parseSpec( spec , sep_major , sep_minor , escape ) ;
51  size_type n = parseArgs( args_in ) ;
52  remove( n ) ;
53 }
54 
55 void G::GetOpt::parseSpec( const std::string & spec , char sep_major , char sep_minor , char escape )
56 {
57  Strings outer ;
58  std::string ws_major( 1U , sep_major ) ;
59  G::Str::splitIntoFields( spec , outer , ws_major , escape , false ) ;
60 
61  for( Strings::iterator p = outer.begin() ; p != outer.end() ; ++p )
62  {
63  if( (*p).empty() ) continue ;
64  StringArray inner ;
65  std::string ws_minor( 1U , sep_minor ) ;
66  G::Str::splitIntoFields( *p , inner , ws_minor , escape ) ;
67  if( inner.size() != 7U )
68  {
69  std::ostringstream ss ;
70  ss << "\"" << *p << "\" (" << ws_minor << ")" ;
71  throw InvalidSpecification( ss.str() ) ;
72  }
73  bool is_valued = G::Str::toUInt( inner[4U] ) != 0U ;
74  unsigned int level = G::Str::toUInt( inner[6U] ) ;
75  addSpec( inner[1U] , inner[0U].at(0U) , inner[1U] , inner[2U] , inner[3U] , is_valued , inner[5U] , level ) ;
76  }
77 }
78 
79 void G::GetOpt::addSpec( const std::string & sort_key , char c , const std::string & name ,
80  const std::string & description , const std::string & description_extra ,
81  bool is_valued , const std::string & value_description , unsigned int level )
82 {
83  if( c == '\0' )
84  throw InvalidSpecification() ;
85 
86  std::pair<SwitchSpecMap::iterator,bool> rc =
87  m_spec_map.insert( std::make_pair( sort_key ,
88  SwitchSpec(c,name,description,description_extra,is_valued,value_description,level) ) ) ;
89 
90  if( ! rc.second )
91  throw InvalidSpecification("duplication") ;
92 }
93 
94 bool G::GetOpt::valued( const std::string & name ) const
95 {
96  return valued(key(name)) ;
97 }
98 
99 bool G::GetOpt::valued( char c ) const
100 {
101  SwitchSpecMap::const_iterator p =
102  std::find_if( m_spec_map.begin() , m_spec_map.end() , std::bind1st(std::ptr_fun(eqc),c)) ;
103  return p == m_spec_map.end() ? false : (*p).second.valued ;
104 }
105 
106 char G::GetOpt::key( const std::string & name ) const
107 {
108  SwitchSpecMap::const_iterator p =
109  std::find_if( m_spec_map.begin() , m_spec_map.end() , std::bind1st(std::ptr_fun(eqn),name)) ;
110  return p == m_spec_map.end() ? '\0' : (*p).second.c ;
111 }
112 
114 {
115  unsigned int result = 79U ;
116  std::string p = G::Environment::get("COLUMNS",std::string()) ;
117  if( !p.empty() )
118  {
119  try { result = G::Str::toUInt(p) ; } catch(std::exception&) {}
120  }
121  return result ;
122 }
123 
125 {
126  return 30U ;
127 }
128 
130 {
131  return "usage: " ;
132 }
133 
135 {
136  return Level(99U) ;
137 }
138 
139 G::GetOpt::size_type G::GetOpt::widthLimit( size_type w )
140 {
141  return (w != 0U && w < 50U) ? 50U : w ;
142 }
143 
144 void G::GetOpt::showUsage( std::ostream & stream , const std::string & args , bool verbose ) const
145 {
146  showUsage( stream , m_args.prefix() , args , introducerDefault() ,
147  verbose ? levelDefault() : Level(1U) , tabDefault() , wrapDefault() , verbose ) ;
148 }
149 
150 void G::GetOpt::showUsage( std::ostream & stream , const std::string & exe , const std::string & args ,
151  const std::string & introducer , Level level , size_type tab_stop , size_type width , bool extra ) const
152 {
153  stream
154  << usageSummary(exe,args,introducer,level,width) << std::endl
155  << usageHelp(level,tab_stop,width,false,extra) ;
156 }
157 
158 std::string G::GetOpt::usageSummary( const std::string & exe , const std::string & args ,
159  const std::string & introducer , Level level , size_type width ) const
160 {
161  std::string s = introducer + exe + " " + usageSummarySwitches(level) + args ;
162  if( width != 0U )
163  {
164  return G::Str::wrap( s , "" , " " , widthLimit(width) ) ;
165  }
166  else
167  {
168  return s ;
169  }
170 }
171 
172 std::string G::GetOpt::usageSummarySwitches( Level level ) const
173 {
174  return usageSummaryPartOne(level) + usageSummaryPartTwo(level) ;
175 }
176 
177 bool G::GetOpt::visible( SwitchSpecMap::const_iterator p , Level level , bool exact )
178 {
179  return
180  exact ?
181  ( !(*p).second.hidden && (*p).second.level == level.level ) :
182  ( !(*p).second.hidden && (*p).second.level <= level.level ) ;
183 }
184 
185 std::string G::GetOpt::usageSummaryPartOne( Level level ) const
186 {
187  // summarise the single-character switches, excluding those which take a value
188  std::ostringstream ss ;
189  bool first = true ;
190  for( SwitchSpecMap::const_iterator p = m_spec_map.begin() ; p != m_spec_map.end() ; ++p )
191  {
192  if( !(*p).second.valued && visible(p,level,false) )
193  {
194  if( first )
195  ss << "[-" ;
196  first = false ;
197  ss << (*p).second.c ;
198  }
199  }
200 
201  std::string s = ss.str() ;
202  if( s.length() ) s.append( "] " ) ;
203  return s ;
204 }
205 
206 std::string G::GetOpt::usageSummaryPartTwo( Level level ) const
207 {
208  std::ostringstream ss ;
209  const char * sep = "" ;
210  for( SwitchSpecMap::const_iterator p = m_spec_map.begin() ; p != m_spec_map.end() ; ++p )
211  {
212  if( visible(p,level,false) )
213  {
214  ss << sep << "[" ;
215  if( (*p).second.name.length() )
216  {
217  ss << "--" << (*p).second.name ;
218  }
219  else
220  {
221  ss << "-" << (*p).second.c ;
222  }
223  if( (*p).second.valued )
224  {
225  std::string vd = (*p).second.value_description ;
226  if( vd.empty() ) vd = "value" ;
227  ss << "=<" << vd << ">" ;
228  }
229  ss << "]" ;
230  sep = " " ;
231  }
232  }
233  return ss.str() ;
234 }
235 
236 std::string G::GetOpt::usageHelp( Level level , size_type tab_stop , size_type width ,
237  bool exact , bool extra ) const
238 {
239  return usageHelpCore( " " , level , tab_stop , widthLimit(width) , exact , extra ) ;
240 }
241 
242 std::string G::GetOpt::usageHelpCore( const std::string & prefix , Level level ,
243  size_type tab_stop , size_type width , bool exact , bool extra ) const
244 {
245  std::string result ;
246  for( SwitchSpecMap::const_iterator p = m_spec_map.begin() ; p != m_spec_map.end() ; ++p )
247  {
248  if( visible(p,level,exact) )
249  {
250  std::string line( prefix ) ;
251  line.append( "-" ) ;
252  line.append( 1U , (*p).second.c ) ;
253 
254  if( (*p).second.name.length() )
255  {
256  line.append( ", --" ) ;
257  line.append( (*p).second.name ) ;
258  }
259 
260  if( (*p).second.valued )
261  {
262  std::string vd = (*p).second.value_description ;
263  if( vd.empty() ) vd = "value" ;
264  line.append( "=<" ) ;
265  line.append( vd ) ;
266  line.append( ">" ) ;
267  }
268  line.append( 1U , ' ' ) ;
269 
270  if( line.length() < tab_stop )
271  line.append( tab_stop-line.length() , ' ' ) ;
272 
273  line.append( (*p).second.description ) ;
274  if( extra )
275  line.append( (*p).second.description_extra ) ;
276 
277  if( width )
278  {
279  std::string indent( tab_stop , ' ' ) ;
280  line = G::Str::wrap( line , "" , indent , width ) ;
281  }
282  else
283  {
284  line.append( 1U , '\n' ) ;
285  }
286 
287  result.append( line ) ;
288  }
289  }
290  return result ;
291 }
292 
293 G::GetOpt::size_type G::GetOpt::parseArgs( const Arg & args_in )
294 {
295  G::Arg::size_type i = 1U ;
296  for( ; i < args_in.c() ; i++ )
297  {
298  const std::string & arg = args_in.v(i) ;
299 
300  if( arg == "--" ) // special "end-of-switches" switch
301  {
302  i++ ;
303  break ;
304  }
305 
306  if( isSwitchSet(arg) ) // eg. "-lt"
307  {
308  for( size_type n = 1U ; n < arg.length() ; n++ )
309  processSwitch( arg.at(n) ) ;
310  }
311  else if( isOldSwitch(arg) ) // eg. "-v"
312  {
313  char c = arg.at(1U) ;
314  if( valued(c) && (i+1U) >= args_in.c() )
315  errorNoValue( c ) ;
316  else if( valued(c) )
317  processSwitch( c , args_in.v(++i) ) ;
318  else
319  processSwitch( c ) ;
320  }
321  else if( isNewSwitch(arg) ) // eg. "--foo"
322  {
323  std::string name = arg.substr( 2U ) ;
324  size_type pos_eq = eqPos(name) ;
325  bool has_eq = pos_eq != std::string::npos ;
326  std::string eq_value = eqValue(name,pos_eq) ;
327  if( has_eq ) name = name.substr(0U,pos_eq) ;
328 
329  if( valued(name) && !has_eq && (i+1U) >= args_in.c() )
330  errorNoValue( name ) ;
331  else if( valued(name) && !has_eq )
332  processSwitch( name , args_in.v(++i) ) ;
333  else if( valued(name) )
334  processSwitch( name , eq_value ) ;
335  else
336  processSwitch( name ) ;
337  }
338  else
339  {
340  break ;
341  }
342  }
343  i-- ;
344  return i ;
345 }
346 
347 G::GetOpt::size_type G::GetOpt::eqPos( const std::string & s )
348 {
349  size_type p = s.find_first_not_of("abcdefghijklmnopqrstuvwxyz0123456789-_") ;
350  return p != std::string::npos && s.at(p) == '=' ? p : std::string::npos ;
351 }
352 
353 std::string G::GetOpt::eqValue( const std::string & s , size_type pos )
354 {
355  return (pos+1U) == s.length() ? std::string() : s.substr(pos+1U) ;
356 }
357 
358 bool G::GetOpt::isOldSwitch( const std::string & arg ) const
359 {
360  return
361  ( arg.length() > 1U && arg.at(0U) == '-' ) &&
362  ! isNewSwitch( arg ) ;
363 }
364 
365 bool G::GetOpt::isNewSwitch( const std::string & arg ) const
366 {
367  return arg.length() > 2U && arg.at(0U) == '-' && arg.at(1U) == '-' ;
368 }
369 
370 bool G::GetOpt::isSwitchSet( const std::string & arg ) const
371 {
372  return isOldSwitch(arg) && arg.length() > 2U ;
373 }
374 
375 void G::GetOpt::errorNoValue( char c )
376 {
377  std::string e("no value supplied for -") ;
378  e.append( 1U , c ) ;
379  m_errors.push_back( e ) ;
380 }
381 
382 void G::GetOpt::errorNoValue( const std::string & name )
383 {
384  std::string e("no value supplied for --") ;
385  e.append( name ) ;
386  m_errors.push_back( e ) ;
387 }
388 
389 void G::GetOpt::errorUnknownSwitch( char c )
390 {
391  std::string e("invalid option: -") ;
392  e.append( 1U , c ) ;
393  m_errors.push_back( e ) ;
394 }
395 
396 void G::GetOpt::errorUnknownSwitch( const std::string & name )
397 {
398  std::string e("invalid option: --") ;
399  e.append( name ) ;
400  m_errors.push_back( e ) ;
401 }
402 
403 void G::GetOpt::processSwitch( const std::string & name )
404 {
405  if( !valid(name) )
406  {
407  errorUnknownSwitch( name ) ;
408  return ;
409  }
410 
411  char c = key(name) ;
412  if( valued(c) )
413  {
414  errorNoValue( name ) ;
415  return ;
416  }
417 
418  m_map.insert( std::make_pair(c,std::make_pair(false,std::string())) ) ;
419 }
420 
421 void G::GetOpt::processSwitch( const std::string & name , const std::string & value )
422 {
423  if( !valid(name) )
424  {
425  errorUnknownSwitch( name ) ;
426  return ;
427  }
428 
429  char c = key(name) ;
430  m_map.insert( std::make_pair(c,std::make_pair(true,value)) ) ;
431 }
432 
433 void G::GetOpt::processSwitch( char c )
434 {
435  if( !valid(c) )
436  {
437  errorUnknownSwitch( c ) ;
438  return ;
439  }
440 
441  if( valued(c) )
442  {
443  errorNoValue( c ) ;
444  return ;
445  }
446 
447  m_map.insert( std::make_pair(c,std::make_pair(false,std::string())) ) ;
448 }
449 
450 void G::GetOpt::processSwitch( char c , const std::string & value )
451 {
452  if( !valid(c) )
453  errorUnknownSwitch( c ) ;
454 
455  m_map.insert( std::make_pair(c,std::make_pair(true,value)) ) ;
456 }
457 
459 {
460  return m_errors ;
461 }
462 
463 bool G::GetOpt::contains( char c ) const
464 {
465  SwitchMap::const_iterator p = m_map.find( c ) ;
466  return p != m_map.end() ;
467 }
468 
469 bool G::GetOpt::contains( const std::string & name ) const
470 {
471  char c = key(name) ;
472  SwitchMap::const_iterator p = m_map.find( c ) ;
473  return p != m_map.end() ;
474 }
475 
476 std::string G::GetOpt::value( char c ) const
477 {
478  G_ASSERT( contains(c) ) ;
479  SwitchMap::const_iterator p = m_map.find( c ) ;
480  Value value_pair = (*p).second ;
481  return value_pair.second ;
482 }
483 
484 std::string G::GetOpt::value( const std::string & name ) const
485 {
486  G_ASSERT( contains(name) ) ;
487  return value( key(name) ) ;
488 }
489 
491 {
492  return m_args ;
493 }
494 
495 void G::GetOpt::show( std::ostream & stream , std::string prefix ) const
496 {
497  for( SwitchMap::const_iterator p = m_map.begin() ; p != m_map.end() ; ++p )
498  {
499  char c = (*p).first ;
500  Value v = (*p).second ;
501  bool valued = v.first ;
502  std::string value = v.second ;
503 
504  SwitchSpecMap::const_iterator q = std::find_if( m_spec_map.begin() , m_spec_map.end() ,
505  std::bind1st(std::ptr_fun(eqc),c)) ;
506  std::string name = q == m_spec_map.end() ? std::string() : (*q).second.name ;
507 
508  stream << prefix << "-" << c ;
509  if( !name.empty() )
510  stream << ",--" << name ;
511  if( valued )
512  stream << " = \"" << value << "\"" ;
513  stream << std::endl ;
514  }
515 }
516 
518 {
519  return m_errors.size() != 0U ;
520 }
521 
522 void G::GetOpt::showErrors( std::ostream & stream ) const
523 {
524  showErrors( stream , m_args.prefix() ) ;
525 }
526 
527 void G::GetOpt::showErrors( std::ostream & stream , std::string prefix_1 , std::string prefix_2 ) const
528 {
529  for( Strings::const_iterator p = m_errors.begin() ; p != m_errors.end() ; ++p )
530  {
531  stream << prefix_1 << prefix_2 << *p << std::endl ;
532  }
533 }
534 
535 void G::GetOpt::remove( size_type n )
536 {
537  if( n != 0U )
538  {
539  m_args.removeAt(1U,n-1U) ;
540  }
541 }
542 
543 bool G::GetOpt::valid( const std::string & name ) const
544 {
545  return valid( key(name) ) ;
546 }
547 
548 bool G::GetOpt::valid( char c ) const
549 {
550  return !! std::count_if( m_spec_map.begin() , m_spec_map.end() , std::bind1st(std::ptr_fun(eqc),c)) ;
551 }
552 
bool contains(char switch_letter) const
Returns true if the command line contains the given switch.
Definition: ggetopt.cpp:463
static std::string get(const std::string &name, const std::string &default_)
Returns the environment variable value or the given default.
void showErrors(std::ostream &stream, std::string prefix_1, std::string prefix_2=std::string(": ")) const
A convenience function which streams out each errorList() item to the given stream, prefixed with the given prefix(es).
Definition: ggetopt.cpp:527
Strings errorList() const
Returns the list of errors.
Definition: ggetopt.cpp:458
size_t size_type
Definition: garg.h:49
static unsigned int toUInt(const std::string &s, bool limited=false)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:346
std::list< std::string > Strings
A std::list of std::strings.
Definition: gstrings.h:39
static std::string wrap(std::string text, const std::string &prefix_first_line, const std::string &prefix_subsequent_lines, size_type width=70U)
Does word-wrapping.
Definition: gstr.cpp:633
static void splitIntoFields(const std::string &in, Strings &out, const std::string &seperators, char escape= '\0', bool discard_bogus_escapes=true)
Splits the string into fields.
Definition: gstr.cpp:765
bool hasErrors() const
Returns true if there are errors.
Definition: ggetopt.cpp:517
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:44
std::string::size_type size_type
A std::size_t type.
Definition: md5.h:43
void show(std::ostream &stream, std::string prefix) const
For debugging.
Definition: ggetopt.cpp:495
std::string value(const std::string &switch_name) const
Returns the value related to the given value-based switch.
Definition: ggetopt.cpp:484
GetOpt(const Arg &arg, const std::string &spec, char sep_major= '|', char sep_minor= '/', char escape= '\\')
Constructor taking a Arg reference and a specification string.
Definition: ggetopt.cpp:46
static size_type tabDefault()
Returns a default tab-stop.
Definition: ggetopt.cpp:124
Arg args() const
Returns all the non-switch command-line arguments.
Definition: ggetopt.cpp:490
#define G_ASSERT(test)
Definition: gassert.h:30
std::string usageHelp(Level level=levelDefault(), size_type tab_stop=tabDefault(), size_type wrap_width=wrapDefault(), bool level_exact=false, bool extra=true) const
Returns a multi-line string giving help on each switch.
Definition: ggetopt.cpp:236
static size_type wrapDefault()
Returns a default word-wrapping width.
Definition: ggetopt.cpp:113
static Level levelDefault()
Returns the default level.
Definition: ggetopt.cpp:134
A class which holds a represention of the argc/argv command line array, and supports simple command-l...
Definition: garg.h:46
std::string::size_type size_type
Definition: ggetopt.h:57
std::string usageSummary(const std::string &exe, const std::string &args, const std::string &introducer=introducerDefault(), Level level=levelDefault(), size_type wrap_width=wrapDefault()) const
Returns a one-line usage summary, as "usage: ".
Definition: ggetopt.cpp:158
static std::string introducerDefault()
Returns "usage: ".
Definition: ggetopt.cpp:129
Used by G::GetOpt for extra type safety.
Definition: ggetopt.h:55
std::string usageSummarySwitches(Level level=levelDefault()) const
Returns the one-line summary of switches.
Definition: ggetopt.cpp:172
void showUsage(std::ostream &stream, const std::string &exe, const std::string &args, const std::string &introducer=introducerDefault(), Level level=levelDefault(), size_type tab_stop=tabDefault(), size_type wrap_width=wrapDefault(), bool extra=true) const
Streams out multi-line usage text using usageSummary() and usageHelp().
Definition: ggetopt.cpp:150