E-MailRelay

goptions.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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/// \file goptions.cpp
19///
20
21#include "gdef.h"
22#include "gstr.h"
23#include "gstringwrap.h"
24#include "ggettext.h"
25#include "gassert.h"
26#include "goptions.h"
27#include "genvironment.h"
28#include <algorithm>
29
30 G::Options::Options()
31= default;
32
33 G::Options::Options( const std::string & spec , char sep_major , char sep_minor , char escape )
34{
35 parseSpec( spec , sep_major , sep_minor , escape ) ;
36 std::sort( m_names.begin() , m_names.end() ) ;
37}
38
39 void G::Options::parseSpec( const std::string & spec , char sep_major , char sep_minor , char escape )
40{
41 // split into separate options
42 StringArray spec_items ;
43 spec_items.reserve( 40U ) ;
44 Str::splitIntoFields( spec , spec_items , {&sep_major,1U} , escape , false ) ;
45
46 // for each option
47 for( auto & spec_item : spec_items )
48 {
49 // split into separate fields
50 if( spec_item.empty() ) continue ;
51 StringArray inner_parts ;
52 inner_parts.reserve( 7U ) ;
53 Str::splitIntoFields( spec_item , inner_parts , {&sep_minor,1U} , escape ) ;
54 if( inner_parts.size() != 7U )
55 {
56 std::ostringstream ss ;
57 ss << "[" << spec_item << "] (" << sep_minor << ")" ;
58 throw InvalidSpecification( ss.str() ) ;
59 }
60
61 add( inner_parts ) ;
62 }
63}
64
65 void G::Options::add( StringArray spec_parts )
66{
67 if( spec_parts.size() != 7U )
68 throw InvalidSpecification( "[" + G::Str::join(",",spec_parts) + "]" ) ;
69
70 unsigned int level = Str::toUInt( spec_parts[6U] ) ;
71 std::string short_form = spec_parts[0] ;
72 char c = short_form.empty() ? '\0' : short_form.at(0U) ;
73
74 addImp( spec_parts[1U] , c , spec_parts[2U] ,
75 spec_parts[3U] , spec_parts[4U] , spec_parts[5U] , level ) ;
76}
77
78 void G::Options::add( StringArray spec_parts , char sep , char escape )
79{
80 if( spec_parts.size() != 6U )
81 throw InvalidSpecification( "[" + G::Str::join(",",spec_parts) + "]" ) ;
82
83 StringArray sub_parts ;
84 sub_parts.reserve( 2U ) ;
85 Str::splitIntoFields( spec_parts[2U] , sub_parts , {&sep,1U} , escape ) ;
86 if( sub_parts.empty() || sub_parts.size() > 2U )
87 throw InvalidSpecification( G::Str::join(",",spec_parts) ) ;
88
89 spec_parts[2U] = sub_parts[0U] ;
90 spec_parts.insert( spec_parts.begin()+3U , sub_parts.size() == 2U ? sub_parts[1U] : std::string() ) ;
91
92 add( spec_parts ) ;
93}
94
95 void G::Options::addImp( const std::string & name , char c ,
96 const std::string & description , const std::string & description_extra ,
97 const std::string & value_multiplicity , const std::string & value_description , unsigned int level )
98{
99 std::pair<Map::iterator,bool> rc = m_map.insert( std::make_pair( name ,
100 Option(c,name,description,description_extra,value_multiplicity,value_description,level) ) ) ;
101 if( ! rc.second )
102 throw InvalidSpecification( "duplication" ) ;
103 m_names.insert( std::lower_bound(m_names.begin(),m_names.end(),name) , name ) ;
104}
105
106 bool G::Options::defaulting( const std::string & name ) const
107{
108 auto p = m_map.find( name ) ;
109 return p == m_map.end() ? false : ( (*p).second.value_multiplicity == Option::Multiplicity::zero_or_one ) ;
110}
111
112 bool G::Options::valued( char c ) const
113{
114 return valued( lookup(c) ) ;
115}
116
117 bool G::Options::valued( const std::string & name ) const
118{
119 auto p = m_map.find( name ) ;
120 return p == m_map.end() ? false : ( (*p).second.value_multiplicity != Option::Multiplicity::zero ) ;
121}
122
123 bool G::Options::unvalued( const std::string & name ) const
124{
125 return valid(name) && !valued(name) ;
126}
127
128 bool G::Options::multivalued( char c ) const
129{
130 return multivalued( lookup(c) ) ;
131}
132
133 bool G::Options::multivalued( const std::string & name ) const
134{
135 auto p = m_map.find( name ) ;
136 return p == m_map.end() ? false : ( (*p).second.value_multiplicity == Option::Multiplicity::many ) ;
137}
138
139 bool G::Options::visible( const std::string & name , unsigned int level , bool level_exact ) const
140{
141 auto p = m_map.find( name ) ;
142 if( p == m_map.end() ) return false ;
143 return
144 level_exact ?
145 ( !(*p).second.hidden && (*p).second.level == level ) :
146 ( !(*p).second.hidden && (*p).second.level <= level ) ;
147}
148
149 bool G::Options::visible( const std::string & name ) const
150{
151 return visible( name , 99U , false ) ;
152}
153
154 bool G::Options::valid( const std::string & name ) const
155{
156 return m_map.find(name) != m_map.end() ;
157}
158
159std::string G::Options::lookup( char c ) const
160{
161 for( auto p = m_map.begin() ; c != '\0' && p != m_map.end() ; ++p )
162 {
163 if( (*p).second.c == c )
164 return (*p).second.name ;
165 }
166 return std::string() ;
167}
168
169 const G::StringArray & G::Options::names() const
170{
171 return m_names ;
172}
173
174// --
175
176 namespace G
177{
178 namespace OptionsLayoutImp
179 {
180 unsigned int widthDefault() ;
181 }
182}
183
184G::OptionsLayout::OptionsLayout() :
185 column(30U) ,
186 width(0U) ,
187 width2(0U) ,
188 margin(2U) ,
189 level(99U) ,
190 level_exact(false) ,
191 extra(false) ,
192 alt_usage(false)
193{
194 width = width2 = OptionsLayoutImp::widthDefault() ;
195}
196
197G::OptionsLayout::OptionsLayout( std::size_t column_ ) :
198 column(column_) ,
199 width(0U) ,
200 width2(0U) ,
201 margin(2U) ,
202 level(99U) ,
203 level_exact(false) ,
204 extra(false) ,
205 alt_usage(false)
206{
207 width = width2 = OptionsLayoutImp::widthDefault() ;
208}
209
210G::OptionsLayout::OptionsLayout( std::size_t column_ , std::size_t width_ ) :
211 column(column_) ,
212 width(width_) ,
213 width2(width_) ,
214 margin(2U) ,
215 level(99U) ,
216 level_exact(false) ,
217 extra(false) ,
218 alt_usage(false)
219{
220 width = width2 = OptionsLayoutImp::widthDefault() ;
221}
222
223 unsigned int G::OptionsLayoutImp::widthDefault()
224{
225 return Str::toUInt( Environment::get("COLUMNS",std::string()) , "79" ) ;
226}
227
228// --
229
230std::string G::Options::usageSummaryPartOne( const Layout & layout ) const
231{
232 // summarise the single-character switches, excluding those which take a value
233 std::ostringstream ss ;
234 bool first = true ;
235 for( const auto & name : m_names )
236 {
237 auto spec_p = m_map.find( name ) ;
238 if( (*spec_p).second.c != '\0' && !valued(name) && visible(name,layout.level,layout.level_exact) )
239 {
240 if( first )
241 ss << "[-" ;
242 first = false ;
243 ss << (*spec_p).second.c ;
244 }
245 }
246
247 std::string s = ss.str() ;
248 if( s.length() ) s.append( "] " ) ;
249 return s ;
250}
251
252std::string G::Options::usageSummaryPartTwo( const Layout & layout ) const
253{
254 std::ostringstream ss ;
255 const char * sep = "" ;
256 for( const auto & name : m_names )
257 {
258 if( visible(name,layout.level,layout.level_exact) )
259 {
260 auto spec_p = m_map.find( name ) ;
261 ss << sep << "[" ;
262 if( (*spec_p).second.name.length() )
263 {
264 ss << "--" << (*spec_p).second.name ;
265 }
266 else
267 {
268 G_ASSERT( (*spec_p).second.c != '\0' ) ;
269 ss << "-" << (*spec_p).second.c ;
270 }
271 if( (*spec_p).second.value_multiplicity != Option::Multiplicity::zero )
272 {
273 std::string vd = (*spec_p).second.value_description ;
274 if( vd.empty() ) vd = "value" ;
275 ss << "=<" << vd << ">" ;
276 }
277 ss << "]" ;
278 sep = " " ;
279 }
280 }
281 return ss.str() ;
282}
283
284std::string G::Options::usageHelpSyntax( Map::const_iterator spec_p ) const
285{
286 std::string syntax ;
287 if( (*spec_p).second.c != '\0' )
288 {
289 syntax.append( "-" ) ;
290 syntax.append( 1U , (*spec_p).second.c ) ;
291 if( (*spec_p).second.name.length() )
292 syntax.append( ", " ) ;
293 }
294 if( (*spec_p).second.name.length() )
295 {
296 syntax.append( "--" ) ;
297 syntax.append( (*spec_p).second.name ) ;
298 }
299 if( (*spec_p).second.value_multiplicity != Option::Multiplicity::zero )
300 {
301 bool defaulting = (*spec_p).second.value_multiplicity == Option::Multiplicity::zero_or_one ;
302 std::string vd = (*spec_p).second.value_description ;
303 if( vd.empty() ) vd = "value" ;
304 if( defaulting ) syntax.append( "[" ) ;
305 syntax.append( "=<" ) ;
306 syntax.append( vd ) ;
307 syntax.append( ">" ) ;
308 if( defaulting ) syntax.append( "]" ) ;
309 }
310 syntax.append( 1U , ' ' ) ;
311 return syntax ;
312}
313
314std::string G::Options::usageHelpDescription( Map::const_iterator spec_p , const Layout & layout ) const
315{
316 std::string description = (*spec_p).second.description ;
317 if( layout.extra )
318 description.append( (*spec_p).second.description_extra ) ;
319 return description ;
320}
321
322std::string G::Options::usageHelpSeparator( const Layout & layout , std::size_t syntax_length ) const
323{
324 std::string separator ;
325 if( !layout.separator.empty() )
326 separator = layout.separator ;
327 else if( (layout.margin+syntax_length) > layout.column )
328 separator = std::string( 1U , ' ' ) ;
329 else
330 separator = std::string( layout.column-syntax_length-layout.margin , ' ' ) ;
331 return separator ;
332}
333
334std::string G::Options::usageHelpWrap( const Layout & layout , const std::string & line_in ,
335 const std::string & margin ) const
336{
337 std::string line( line_in ) ;
338 std::size_t wrap_width = layout.width>layout.margin ? (layout.width-layout.margin) : 1U ;
339 bool separator_is_tab = layout.separator.length() == 1U && layout.separator.at(0U) == '\t' ;
340 if( separator_is_tab )
341 {
342 std::string prefix_other = std::string(layout.margin,' ').append(1U,'\t') ;
343 line = margin + StringWrap::wrap( line , std::string() , prefix_other ,
344 wrap_width , layout.width2 , true ) ;
345 }
346 else if( !layout.separator.empty() )
347 {
348 // wrap with a one-space indent for wrapped descriptions wrt. the syntax
349 if( line.length() > layout.width )
350 {
351 std::string prefix_other( layout.margin+1U , ' ' ) ;
352 line = margin + StringWrap::wrap( line , std::string() , prefix_other ,
353 wrap_width , layout.width2 , true ) ;
354 }
355 }
356 else
357 {
358 std::string prefix_other( layout.column , ' ' ) ;
359 line = margin + StringWrap::wrap( line , std::string() , prefix_other ,
360 wrap_width , layout.width2 , true ) ;
361 }
362 return line ;
363}
364
365std::size_t G::Options::longestSubLine( const std::string & s )
366{
367 std::size_t result = 0U ;
368 std::size_t n = 0U ;
369 for( auto c : s )
370 {
371 if( c == '\n' )
372 {
373 if( n > result )
374 result = n ;
375 n = 0 ;
376 }
377 else
378 {
379 n++ ;
380 }
381 }
382 return result ;
383}
384
385std::string G::Options::usageHelp( const Layout & layout ) const
386{
387 std::string result = usageHelpImp( layout ) ;
388
389 // check for width overflow even after wrapping, which can
390 // happen as 'layout.width' shrinks towards 'layout.column'
391 if( layout.width && layout.column && layout.separator.empty() &&
392 layout.width <= (layout.column+20) )
393 {
394 std::size_t longest = longestSubLine( result ) ;
395 if( longest > layout.width )
396 {
397 // give up on columns and make the description part wrap
398 // onto a completely new line by setting a huge separator
399 Layout new_layout( layout ) ;
400 new_layout.separator = std::string( layout.column+layout.column , ' ' ) ;
401 result = usageHelpImp( new_layout ) ;
402 }
403 }
404
405 return result ;
406}
407
408std::string G::Options::usageHelpImp( const Layout & layout ) const
409{
410 std::string result ;
411 for( const auto & name : m_names )
412 {
413 if( visible(name,layout.level,layout.level_exact) )
414 {
415 auto spec_p = m_map.find( name ) ;
416
417 std::string margin( layout.margin , ' ' ) ;
418 std::string syntax = usageHelpSyntax( spec_p ) ;
419 std::string description = usageHelpDescription( spec_p , layout ) ;
420 std::string separator = usageHelpSeparator( layout , syntax.length() ) ;
421
422 std::string line ;
423 line.append(margin).append(syntax).append(separator).append(description) ;
424
425 if( layout.width )
426 line = usageHelpWrap( layout , line , margin ) ;
427
428 line.append( 1U , '\n' ) ;
429 result.append( line ) ;
430 }
431 }
432 return result ;
433}
434
435 void G::Options::showUsage( const Layout & layout , std::ostream & stream ,
436 const std::string & exe , const std::string & args ) const
437{
438 stream
439 << usageSummary(layout,exe,args) << std::endl
440 << std::endl
441 << usageHelp(layout) ;
442}
443
444std::string G::Options::usageSummary( const Layout & layout ,
445 const std::string & exe , const std::string & args ) const
446{
447 const char * usage = gettext( "usage: " ) ;
448 const char * alt_usage = gettext( "abbreviated usage: " ) ;
449 std::string s = std::string()
450 .append(layout.alt_usage?alt_usage:usage)
451 .append(exe)
452 .append(" ")
453 .append(usageSummaryPartOne(layout))
454 .append(usageSummaryPartTwo(layout))
455 .append(args.empty()||args.at(0U)==' '?"":" ")
456 .append(args) ;
457 std::string indent( 2U , ' ' ) ;
458 return layout.width == 0U ? s : StringWrap::wrap( s , "" , indent , layout.width , 0U , true ) ;
459}
460
461// ==
462
463G::Options::Option::Option( char c_ , const std::string & name_ , const std::string & description_ ,
464 const std::string & description_extra_ , const std::string & value_multiplicity_ ,
465 const std::string & vd_ , unsigned int level_ ) :
466 c(c_) ,
467 name(name_) ,
468 description(description_) ,
469 description_extra(description_extra_) ,
470 value_multiplicity(decode(value_multiplicity_)) ,
471 hidden(description_.empty()||level_==0U) ,
472 value_description(vd_) ,
473 level(level_)
474{
475}
476
477G::Options::Option::Multiplicity G::Options::Option::decode( const std::string & s )
478{
479 if( s == "0" )
480 return Multiplicity::zero ;
481 else if( s == "01" )
482 return Multiplicity::zero_or_one ;
483 else if( s == "1" )
484 return Multiplicity::one ;
485 else if( s == "2" )
486 return Multiplicity::many ;
487 else
488 throw InvalidSpecification( "multiplicity" ) ;
489}
490
G::Environment::get
static std::string get(const std::string &name, const std::string &default_)
Returns the environment variable value or the given default.
Definition: genvironment_unix.cpp:27
G::Options::lookup
std::string lookup(char c) const
Converts from short-form option character to the corresponding long-form name.
Definition: goptions.cpp:159
G::Options::unvalued
bool unvalued(const std::string &) const
Returns true if the given option name is valid and takes no value.
Definition: goptions.cpp:123
G::Options::names
const StringArray & names() const
Returns the sorted list of long-form option names.
Definition: goptions.cpp:169
G::Options::visible
bool visible(const std::string &name, unsigned int level, bool level_exact=false) const
Returns true if the option is visible at the given level.
Definition: goptions.cpp:139
G::Options::defaulting
bool defaulting(const std::string &) const
Returns true if the given long-form single-valued() option can optionally have no explicit value,...
Definition: goptions.cpp:106
G::Options::valid
bool valid(const std::string &) const
Returns true if the long-form option name is valid.
Definition: goptions.cpp:154
G::Options::add
void add(StringArray)
Adds one component of the specification, broken down into its seven separate parts.
Definition: goptions.cpp:65
G::Options::multivalued
bool multivalued(char) const
Returns true if the short-form option can have multiple values.
Definition: goptions.cpp:128
G::Options::usageSummary
std::string usageSummary(const Layout &, const std::string &exe, const std::string &args=std::string()) const
Returns a one-line (or line-wrapped) usage summary, as "usage: <exe> <options> <args>".
Definition: goptions.cpp:444
G::Options::valued
bool valued(char) const
Returns true if the given short-form option takes a value, Returns true if the short-form option char...
Definition: goptions.cpp:112
G::Options::showUsage
void showUsage(const Layout &, std::ostream &stream, const std::string &exe, const std::string &args=std::string()) const
Streams out multi-line usage text using usageSummary() and usageHelp().
Definition: goptions.cpp:435
G::Options::Options
Options()
Default constructor for no options.
G::Options::usageHelp
std::string usageHelp(const Layout &) const
Returns a multi-line string giving help on each option.
Definition: goptions.cpp:385
G::Str::join
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1195
G::Str::splitIntoFields
static void splitIntoFields(const std::string &in, StringArray &out, string_view ws, char escape='\0', bool remove_escapes=true)
Splits the string into fields.
Definition: gstr.cpp:1146
G::Str::toUInt
static unsigned int toUInt(const std::string &s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:604
G::StringWrap::wrap
static std::string wrap(const std::string &text, const std::string &prefix_first, const std::string &prefix_other, std::size_t width_first=70U, std::size_t width_other=0U, bool preserve_spaces=false, const std::locale &=defaultLocale())
Does word-wrapping.
Definition: gstringwrap.cpp:114
gassert.h
genvironment.h
ggettext.h
goptions.h
gstr.h
gstringwrap.h
G
Low-level classes.
Definition: galign.h:28
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
G::gettext
const char * gettext(const char *)
Returns the message translation in the current locale's codeset, eg.
Definition: ggettext.h:69
G::OptionsLayout
Describes the layout for G::Options output.
Definition: goptions.h:40
G::OptionsLayout::column
std::size_t column
left hand column width if no separator (includes margin)
Definition: goptions.h:42
G::OptionsLayout::width
std::size_t width
overall width for wrapping, or zero for none
Definition: goptions.h:43
G::OptionsLayout::separator
std::string separator
separator between syntax and description
Definition: goptions.h:41
G::OptionsLayout::alt_usage
bool alt_usage
use alternate "usage:" string
Definition: goptions.h:49