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