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 {
eqc(char c,std::pair<std::string,G::GetOpt::SwitchSpec> p)36 bool eqc( char c , std::pair<std::string,G::GetOpt::SwitchSpec> p )
37 {
38 return p.second.c == c ;
39 }
eqn(std::string n,std::pair<std::string,G::GetOpt::SwitchSpec> p)40 bool eqn( std::string n , std::pair<std::string,G::GetOpt::SwitchSpec> p )
41 {
42 return p.second.name == n ;
43 }
44 }
45
GetOpt(const Arg & args_in,const std::string & spec,char sep_major,char sep_minor,char escape)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
parseSpec(const std::string & spec,char sep_major,char sep_minor,char escape)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
addSpec(const std::string & sort_key,char c,const std::string & name,const std::string & description,const std::string & description_extra,bool is_valued,const std::string & value_description,unsigned int level)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
valued(const std::string & name) const94 bool G::GetOpt::valued( const std::string & name ) const
95 {
96 return valued(key(name)) ;
97 }
98
valued(char c) const99 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
key(const std::string & name) const106 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
wrapDefault()113 G::GetOpt::size_type G::GetOpt::wrapDefault()
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
tabDefault()124 G::GetOpt::size_type G::GetOpt::tabDefault()
125 {
126 return 30U ;
127 }
128
introducerDefault()129 std::string G::GetOpt::introducerDefault()
130 {
131 return "usage: " ;
132 }
133
levelDefault()134 G::GetOpt::Level G::GetOpt::levelDefault()
135 {
136 return Level(99U) ;
137 }
138
widthLimit(size_type w)139 G::GetOpt::size_type G::GetOpt::widthLimit( size_type w )
140 {
141 return (w != 0U && w < 50U) ? 50U : w ;
142 }
143
showUsage(std::ostream & stream,const std::string & args,bool verbose) const144 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
showUsage(std::ostream & stream,const std::string & exe,const std::string & args,const std::string & introducer,Level level,size_type tab_stop,size_type width,bool extra) const150 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
usageSummary(const std::string & exe,const std::string & args,const std::string & introducer,Level level,size_type width) const158 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
usageSummarySwitches(Level level) const172 std::string G::GetOpt::usageSummarySwitches( Level level ) const
173 {
174 return usageSummaryPartOne(level) + usageSummaryPartTwo(level) ;
175 }
176
visible(SwitchSpecMap::const_iterator p,Level level,bool exact)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
usageSummaryPartOne(Level level) const185 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
usageSummaryPartTwo(Level level) const206 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
usageHelp(Level level,size_type tab_stop,size_type width,bool exact,bool extra) const236 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
usageHelpCore(const std::string & prefix,Level level,size_type tab_stop,size_type width,bool exact,bool extra) const242 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
parseArgs(const Arg & args_in)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
eqPos(const std::string & s)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
eqValue(const std::string & s,size_type pos)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
isOldSwitch(const std::string & arg) const358 bool G::GetOpt::isOldSwitch( const std::string & arg ) const
359 {
360 return
361 ( arg.length() > 1U && arg.at(0U) == '-' ) &&
362 ! isNewSwitch( arg ) ;
363 }
364
isNewSwitch(const std::string & arg) const365 bool G::GetOpt::isNewSwitch( const std::string & arg ) const
366 {
367 return arg.length() > 2U && arg.at(0U) == '-' && arg.at(1U) == '-' ;
368 }
369
isSwitchSet(const std::string & arg) const370 bool G::GetOpt::isSwitchSet( const std::string & arg ) const
371 {
372 return isOldSwitch(arg) && arg.length() > 2U ;
373 }
374
errorNoValue(char c)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
errorNoValue(const std::string & name)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
errorUnknownSwitch(char c)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
errorUnknownSwitch(const std::string & name)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
processSwitch(const std::string & name)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
processSwitch(const std::string & name,const std::string & value)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
processSwitch(char c)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
processSwitch(char c,const std::string & value)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
errorList() const458 G::Strings G::GetOpt::errorList() const
459 {
460 return m_errors ;
461 }
462
contains(char c) const463 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
contains(const std::string & name) const469 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
value(char c) const476 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
value(const std::string & name) const484 std::string G::GetOpt::value( const std::string & name ) const
485 {
486 G_ASSERT( contains(name) ) ;
487 return value( key(name) ) ;
488 }
489
args() const490 G::Arg G::GetOpt::args() const
491 {
492 return m_args ;
493 }
494
show(std::ostream & stream,std::string prefix) const495 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
hasErrors() const517 bool G::GetOpt::hasErrors() const
518 {
519 return m_errors.size() != 0U ;
520 }
521
showErrors(std::ostream & stream) const522 void G::GetOpt::showErrors( std::ostream & stream ) const
523 {
524 showErrors( stream , m_args.prefix() ) ;
525 }
526
showErrors(std::ostream & stream,std::string prefix_1,std::string prefix_2) const527 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
remove(size_type n)535 void G::GetOpt::remove( size_type n )
536 {
537 if( n != 0U )
538 {
539 m_args.removeAt(1U,n-1U) ;
540 }
541 }
542
valid(const std::string & name) const543 bool G::GetOpt::valid( const std::string & name ) const
544 {
545 return valid( key(name) ) ;
546 }
547
valid(char c) const548 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
553 /// \file ggetopt.cpp
554