VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
goptions.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2017 Graeme Walker
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 // goptions.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gstrings.h"
23 #include "gstr.h"
24 #include "gassert.h"
25 #include "goptions.h"
26 #include "genvironment.h"
27 #include <algorithm>
28 
30 {
31 }
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 outer_part ;
43  outer_part.reserve( 40U ) ;
44  std::string ws_major( 1U , sep_major ) ;
45  G::Str::splitIntoFields( spec , outer_part , ws_major , escape , false ) ;
46 
47  // for each option
48  for( StringArray::iterator p = outer_part.begin() ; p != outer_part.end() ; ++p )
49  {
50  // split into separate fields
51  if( (*p).empty() ) continue ;
52  StringArray inner_part ;
53  inner_part.reserve( 7U ) ;
54  std::string ws_minor( 1U , sep_minor ) ;
55  G::Str::splitIntoFields( *p , inner_part , ws_minor , escape ) ;
56  if( inner_part.size() != 7U )
57  {
58  std::ostringstream ss ;
59  ss << "\"" << *p << "\" (" << ws_minor << ")" ;
60  throw InvalidSpecification( ss.str() ) ;
61  }
62 
63  unsigned int value_multiplicity = G::Str::toUInt( inner_part[4U] ) ;
64  unsigned int level = G::Str::toUInt( inner_part[6U] ) ;
65  std::string short_form = inner_part[0] ;
66  char c = short_form.empty() ? '\0' : short_form.at(0U) ;
67 
68  addSpec( inner_part[1U] , c , inner_part[2U] ,
69  inner_part[3U] , value_multiplicity , inner_part[5U] , level ) ;
70  }
71 }
72 
73 void G::Options::addSpec( const std::string & name , char c ,
74  const std::string & description , const std::string & description_extra ,
75  unsigned int value_multiplicity , const std::string & value_description , unsigned int level )
76 {
77  std::pair<Map::iterator,bool> rc = m_map.insert( std::make_pair( name ,
78  Option(c,name,description,description_extra,value_multiplicity,value_description,level) ) ) ;
79  if( ! rc.second )
80  throw InvalidSpecification("duplication") ;
81  m_names.push_back( name ) ; // defines the display order
82 }
83 
84 bool G::Options::valued( char c ) const
85 {
86  return valued( lookup(c) ) ;
87 }
88 
89 bool G::Options::valued( const std::string & name ) const
90 {
91  Map::const_iterator p = m_map.find( name ) ;
92  return p == m_map.end() ? false : ( (*p).second.value_multiplicity > 0U ) ;
93 }
94 
95 bool G::Options::unvalued( const std::string & name ) const
96 {
97  return valid(name) && !valued(name) ;
98 }
99 
100 bool G::Options::multivalued( char c ) const
101 {
102  return multivalued( lookup(c) ) ;
103 }
104 
105 bool G::Options::multivalued( const std::string & name ) const
106 {
107  Map::const_iterator p = m_map.find( name ) ;
108  return p == m_map.end() ? false : ( (*p).second.value_multiplicity > 1U ) ;
109 }
110 
111 bool G::Options::visible( const std::string & name , Level level , bool exact ) const
112 {
113  Map::const_iterator p = m_map.find( name ) ;
114  if( p == m_map.end() ) return false ;
115  return
116  exact ?
117  ( !(*p).second.hidden && (*p).second.level == level.level ) :
118  ( !(*p).second.hidden && (*p).second.level <= level.level ) ;
119 }
120 
121 bool G::Options::valid( const std::string & name ) const
122 {
123  return m_map.find(name) != m_map.end() ;
124 }
125 
126 std::string G::Options::lookup( char c ) const
127 {
128  for( Map::const_iterator p = m_map.begin() ; c != '\0' && p != m_map.end() ; ++p )
129  {
130  if( (*p).second.c == c )
131  return (*p).second.name ;
132  }
133  return std::string() ;
134 }
135 
137 {
138  return m_names ;
139 }
140 
141 // --
142 
144 {
145  unsigned int result = 79U ;
146  std::string p = G::Environment::get("COLUMNS",std::string()) ;
147  if( !p.empty() )
148  {
149  try { result = G::Str::toUInt(p) ; } catch(std::exception&) {}
150  }
151  return result ;
152 }
153 
154 size_t G::Options::widthFloor( size_t w )
155 {
156  return (w != 0U && w < 50U) ? 50U : w ;
157 }
158 
160 {
161  return Layout( 30U ) ;
162 }
163 
165 {
166  return "usage: " ;
167 }
168 
170 {
171  return Level(99U) ;
172 }
173 
174 std::string G::Options::usageSummaryPartOne( Level level ) const
175 {
176  // summarise the single-character switches, excluding those which take a value
177  std::ostringstream ss ;
178  bool first = true ;
179  for( StringArray::const_iterator name_p = m_names.begin() ; name_p != m_names.end() ; ++name_p )
180  {
181  Map::const_iterator spec_p = m_map.find( *name_p ) ;
182  if( (*spec_p).second.c != '\0' && !valued(*name_p) && visible(*name_p,level,false) )
183  {
184  if( first )
185  ss << "[-" ;
186  first = false ;
187  ss << (*spec_p).second.c ;
188  }
189  }
190 
191  std::string s = ss.str() ;
192  if( s.length() ) s.append( "] " ) ;
193  return s ;
194 }
195 
196 std::string G::Options::usageSummaryPartTwo( Level level ) const
197 {
198  std::ostringstream ss ;
199  const char * sep = "" ;
200  for( StringArray::const_iterator name_p = m_names.begin() ; name_p != m_names.end() ; ++name_p )
201  {
202  if( visible(*name_p,level,false) )
203  {
204  Map::const_iterator spec_p = m_map.find( *name_p ) ;
205  ss << sep << "[" ;
206  if( (*spec_p).second.name.length() )
207  {
208  ss << "--" << (*spec_p).second.name ;
209  }
210  else
211  {
212  G_ASSERT( (*spec_p).second.c != '\0' ) ;
213  ss << "-" << (*spec_p).second.c ;
214  }
215  if( (*spec_p).second.value_multiplicity > 0U )
216  {
217  std::string vd = (*spec_p).second.value_description ;
218  if( vd.empty() ) vd = "value" ;
219  ss << "=<" << vd << ">" ;
220  }
221  ss << "]" ;
222  sep = " " ;
223  }
224  }
225  return ss.str() ;
226 }
227 
228 std::string G::Options::usageHelp( Level level , Layout layout , bool exact , bool extra ) const
229 {
230  layout.width = widthFloor( layout.width ) ;
231  return usageHelpCore( " " , level , layout , exact , extra ) ;
232 }
233 
234 std::string G::Options::usageHelpCore( const std::string & prefix , Level level ,
235  Layout layout , bool exact , bool extra ) const
236 {
237  std::string result ;
238  for( StringArray::const_iterator name_p = m_names.begin() ; name_p != m_names.end() ; ++name_p )
239  {
240  if( visible(*name_p,level,exact) )
241  {
242  Map::const_iterator spec_p = m_map.find( *name_p ) ;
243  std::string line( prefix ) ;
244  if( (*spec_p).second.c != '\0' )
245  {
246  line.append( "-" ) ;
247  line.append( 1U , (*spec_p).second.c ) ;
248  if( (*spec_p).second.name.length() )
249  line.append( ", " ) ;
250  }
251  if( (*spec_p).second.name.length() )
252  {
253  line.append( "--" ) ;
254  line.append( (*spec_p).second.name ) ;
255  }
256 
257  if( (*spec_p).second.value_multiplicity > 0U )
258  {
259  std::string vd = (*spec_p).second.value_description ;
260  if( vd.empty() ) vd = "value" ;
261  line.append( "=<" ) ;
262  line.append( vd ) ;
263  line.append( ">" ) ;
264  }
265  line.append( 1U , ' ' ) ;
266 
267  if( !layout.separator.empty() )
268  line.append( layout.separator ) ;
269  else if( line.length() < layout.column )
270  line.append( layout.column-line.length() , ' ' ) ;
271 
272  line.append( (*spec_p).second.description ) ;
273  if( extra )
274  line.append( (*spec_p).second.description_extra ) ;
275 
276  if( layout.width )
277  {
278  std::string indent = layout.indent.empty() ? std::string(layout.column,' ') : layout.indent ;
279  line = G::Str::wrap( line , "" , indent , layout.width ) ;
280  }
281  else
282  {
283  line.append( 1U , '\n' ) ;
284  }
285 
286  result.append( line ) ;
287  }
288  }
289  return result ;
290 }
291 
292 void G::Options::showUsage( std::ostream & stream , const std::string & exe , const std::string & args ,
293  const std::string & introducer , Level level , Layout layout , bool extra ) const
294 {
295  stream
296  << usageSummary(exe,args,introducer,level,layout.width) << std::endl
297  << usageHelp(level,layout,false,extra) ;
298 }
299 
300 std::string G::Options::usageSummary( const std::string & exe , const std::string & args ,
301  const std::string & introducer , Level level , size_t width ) const
302 {
303  std::string s = introducer + exe + " " + usageSummaryPartOne(level) + usageSummaryPartTwo(level) + (args.empty()||args.at(0U)==' '?"":" ") + args ;
304  std::string indent( 2U , ' ' ) ; // or from OptionsLayout ?
305  return width == 0U ? s : G::Str::wrap( s , "" , indent , widthFloor(width) ) ;
306 }
307 
308 // ==
309 
310 G::Options::Option::Option( char c_ , const std::string & name_ , const std::string & description_ ,
311  const std::string & description_extra_ , unsigned int value_multiplicity_ ,
312  const std::string & vd_ , unsigned int level_ ) :
313  c(c_) ,
314  name(name_) ,
315  description(description_) ,
316  description_extra(description_extra_) ,
317  value_multiplicity(value_multiplicity_) ,
318  hidden(description_.empty()||level_==0U) ,
319  value_description(vd_) ,
320  level(level_)
321 {
322 }
323 
324 /// \file goptions.cpp
static std::string get(const std::string &name, const std::string &default_)
Returns the environment variable value or the given default.
bool valid(const std::string &) const
Returns true if the long-form option name is valid.
Definition: goptions.cpp:121
bool unvalued(const std::string &option_name) const
Returns true if the given option name is valid and takes no value.
Definition: goptions.cpp:95
static Level levelDefault()
Returns the default level.
Definition: goptions.cpp:169
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:787
Describes the layout for G::Options output.
Definition: goptions.h:50
bool multivalued(char) const
Returns true if the short-form option can have multiple values.
Definition: goptions.cpp:100
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
static size_t widthDefault()
Returns a default, non-zero word-wrapping width, reflecting the size of the standard output where pos...
Definition: goptions.cpp:143
Used by G::Options for extra type safety.
Definition: goptions.h:40
std::string usageHelp(Level level=levelDefault(), Layout layout=layoutDefault(), bool level_exact=false, bool extra=true) const
Returns a multi-line string giving help on each option.
Definition: goptions.cpp:228
std::string lookup(char c) const
Converts from short-form option character to the corresponding long-form name.
Definition: goptions.cpp:126
static unsigned int toUInt(const std::string &s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:450
static std::string introducerDefault()
Returns the string "usage: ".
Definition: goptions.cpp:164
std::string usageSummary(const std::string &exe, const std::string &args, const std::string &introducer=introducerDefault(), Level level=levelDefault(), size_t wrap_width=widthDefault()) const
Returns a one-line (or line-wrapped) usage summary, as "usage: <exe> <options> <args>".
Definition: goptions.cpp:300
static Layout layoutDefault()
Returns a default column layout.
Definition: goptions.cpp:159
size_t width
overall width for wrapping, or zero for no newlines
Definition: goptions.h:56
bool valued(char) const
Returns true if the short-form option character is valid.
Definition: goptions.cpp:84
bool visible(const std::string &name, Level, bool exact) const
Returns true if the option is visible at the given level.
Definition: goptions.cpp:111
const StringArray & names() const
Returns the sorted list of long-form option names.
Definition: goptions.cpp:136
Options()
Default constructor for no options.
Definition: goptions.cpp:29
void showUsage(std::ostream &stream, const std::string &exe, const std::string &args=std::string(), const std::string &introducer=introducerDefault(), Level level=levelDefault(), Layout layout=layoutDefault(), bool extra=true) const
Streams out multi-line usage text using usageSummary() and usageHelp().
Definition: goptions.cpp:292
static void splitIntoFields(const std::string &in, StringArray &out, const std::string &seperators, char escape= '\0', bool remove_escapes=true)
Splits the string into fields.
Definition: gstr.cpp:921