VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
goptionparser.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 // goptionparser.cpp
19 //
20 
21 #include "gdef.h"
22 #include "goptionparser.h"
23 #include "gstr.h"
24 #include "gassert.h"
25 #include "gdebug.h"
26 #include <algorithm>
27 #include <map>
28 #include <stdexcept>
29 
30 G::OptionParser::OptionParser( const Options & spec , OptionMap & values_out , StringArray & errors_out ) :
31  m_spec(spec) ,
32  m_map(values_out) ,
33  m_errors(errors_out)
34 {
35 }
36 
37 G::OptionParser::OptionParser( const Options & spec , OptionMap & values_out ) :
38  m_spec(spec) ,
39  m_map(values_out) ,
40  m_errors(m_errors_ignored)
41 {
42 }
43 
44 size_t G::OptionParser::parse( const StringArray & args_in , size_t start )
45 {
46  size_t i = start ;
47  for( ; i < args_in.size() ; i++ )
48  {
49  const std::string & arg = args_in.at(i) ;
50 
51  if( arg == "--" ) // end-of-options marker
52  {
53  i++ ;
54  break ;
55  }
56 
57  if( isAnOptionSet(arg) ) // eg. "-ltv"
58  {
59  for( size_t n = 1U ; n < arg.length() ; n++ )
60  processOptionOn( arg.at(n) ) ;
61  }
62  else if( isOldOption(arg) ) // eg. "-v"
63  {
64  char c = arg.at(1U) ;
65  if( m_spec.valued(c) && (i+1U) >= args_in.size() )
66  errorNoValue( c ) ;
67  else if( m_spec.valued(c) )
68  processOption( c , args_in.at(++i) ) ;
69  else
70  processOptionOn( c ) ;
71  }
72  else if( isNewOption(arg) ) // eg. "--foo"
73  {
74  std::string name = arg.substr( 2U ) ; // eg. "foo" or "foo=..."
75  std::string::size_type pos_eq = eqPos(name) ;
76  bool has_eq = pos_eq != std::string::npos ;
77  std::string key = has_eq ? name.substr(0U,pos_eq) : name ;
78  if( has_eq && m_spec.unvalued(key) && G::Str::isPositive(eqValue(name,pos_eq)) ) // "foo=yes"
79  processOptionOn( key ) ;
80  else if( has_eq && m_spec.unvalued(key) && G::Str::isNegative(eqValue(name,pos_eq)) ) // "foo=no"
81  processOptionOff( key ) ;
82  else if( has_eq ) // "foo=bar"
83  processOption( key , eqValue(name,pos_eq) , false ) ;
84  else if( m_spec.valued(name) && (i+1U) >= args_in.size() ) // "foo bar"
85  errorNoValue( name ) ;
86  else if( m_spec.valued(name) )
87  processOption( name , args_in.at(++i) , true ) ;
88  else
89  processOptionOn( name ) ;
90  }
91  else
92  {
93  break ;
94  }
95  }
96  return i ;
97 }
98 
99 void G::OptionParser::processOptionOn( const std::string & name )
100 {
101  if( !m_spec.valid(name) )
102  errorUnknownOption( name ) ;
103  else if( m_spec.valued(name) )
104  errorNoValue( name ) ;
105  else if( haveSeenOff(name) )
106  errorConflict( name ) ;
107  else
108  m_map.insert( std::make_pair(name,OptionValue::on()) ) ;
109 }
110 
111 void G::OptionParser::processOptionOff( const std::string & name )
112 {
113  if( !m_spec.valid(name) )
114  errorUnknownOption( name ) ;
115  else if( m_spec.valued(name) )
116  errorNoValue( name ) ;
117  else if( haveSeenOn(name) )
118  errorConflict( name ) ;
119  else
120  m_map.insert( std::make_pair(name,OptionValue::off()) ) ;
121 }
122 
123 void G::OptionParser::processOption( const std::string & name , const std::string & value , bool fail_if_dubious_value )
124 {
125  if( !m_spec.valid(name) )
126  errorUnknownOption( name ) ;
127  else if( !value.empty() && value[0] == '-' && fail_if_dubious_value )
128  errorDubiousValue( name , value ) ;
129  else if( !m_spec.valued(name) )
130  errorExtraValue( name ) ;
131  else if( haveSeen(name) && !m_spec.multivalued(name) )
132  errorDuplicate( name ) ;
133  else
134  m_map.insert( OptionMap::value_type(name,OptionValue(value)) ) ;
135 }
136 
137 void G::OptionParser::processOptionOn( char c )
138 {
139  std::string name = m_spec.lookup( c ) ;
140  if( !m_spec.valid(name) )
141  errorUnknownOption( c ) ;
142  else if( m_spec.valued(name) )
143  errorNoValue( c ) ;
144  else if( haveSeenOff(name) )
145  errorConflict( name ) ;
146  else
147  m_map.insert( std::make_pair(name,OptionValue::on()) ) ;
148 }
149 
150 void G::OptionParser::processOption( char c , const std::string & value )
151 {
152  std::string name = m_spec.lookup( c ) ;
153  if( !m_spec.valid(name) )
154  errorUnknownOption( c ) ;
155  else if( !m_spec.valued(name) )
156  errorExtraValue( name ) ;
157  else if( haveSeen(name) && !m_spec.multivalued(c) )
158  errorDuplicate( c ) ;
159  else
160  m_map.insert( OptionMap::value_type(name,OptionValue(value)) ) ;
161 }
162 
163 std::string::size_type G::OptionParser::eqPos( const std::string & s )
164 {
165  std::string::size_type p = s.find_first_not_of("abcdefghijklmnopqrstuvwxyz0123456789-_") ;
166  return p != std::string::npos && s.at(p) == '=' ? p : std::string::npos ;
167 }
168 
169 std::string G::OptionParser::eqValue( const std::string & s , std::string::size_type pos )
170 {
171  return (pos+1U) == s.length() ? std::string() : s.substr(pos+1U) ;
172 }
173 
174 bool G::OptionParser::isOldOption( const std::string & arg )
175 {
176  return
177  ( arg.length() > 1U && arg.at(0U) == '-' ) &&
178  ! isNewOption( arg ) ;
179 }
180 
181 bool G::OptionParser::isNewOption( const std::string & arg )
182 {
183  return arg.length() > 2U && arg.at(0U) == '-' && arg.at(1U) == '-' ;
184 }
185 
186 bool G::OptionParser::isAnOptionSet( const std::string & arg )
187 {
188  return isOldOption(arg) && arg.length() > 2U ;
189 }
190 
191 void G::OptionParser::errorDubiousValue( const std::string & name , const std::string & value )
192 {
193  m_errors.push_back( std::string("use of \"--")+name+" "+value+"\" is probably a mistake, or try \"--"+name+"="+value+"\" instead" ) ;
194 }
195 
196 void G::OptionParser::errorDuplicate( char c )
197 {
198  m_errors.push_back( std::string("duplicate use of \"-") + std::string(1U,c) + "\"" ) ;
199 }
200 
201 void G::OptionParser::errorDuplicate( const std::string & name )
202 {
203  m_errors.push_back( std::string("duplicate use of \"--") + name + "\"" ) ;
204 }
205 
206 void G::OptionParser::errorExtraValue( char c )
207 {
208  m_errors.push_back( std::string("cannot give a value with \"-") + std::string(1U,c) + "\"" ) ;
209 }
210 
211 void G::OptionParser::errorExtraValue( const std::string & name )
212 {
213  m_errors.push_back( std::string("cannot give a value with \"--") + name + "\"" ) ;
214 }
215 
216 void G::OptionParser::errorNoValue( char c )
217 {
218  m_errors.push_back( std::string("no value supplied for -") + std::string(1U,c) ) ;
219 }
220 
221 void G::OptionParser::errorNoValue( const std::string & name )
222 {
223  m_errors.push_back( std::string("no value supplied for \"--") + name + "\"" ) ;
224 }
225 
226 void G::OptionParser::errorUnknownOption( char c )
227 {
228  m_errors.push_back( std::string("invalid option: \"-") + std::string(1U,c) + "\"" ) ;
229 }
230 
231 void G::OptionParser::errorUnknownOption( const std::string & name )
232 {
233  m_errors.push_back( std::string("invalid option: \"--") + name + "\"" ) ;
234 }
235 
236 void G::OptionParser::errorConflict( const std::string & name )
237 {
238  m_errors.push_back( std::string("conflicting values: \"--") + name + "\"" ) ;
239 }
240 
241 bool G::OptionParser::haveSeenOn( const std::string & name ) const
242 {
243  OptionMap::const_iterator p = m_map.find( name ) ;
244  return p != m_map.end() && !(*p).second.is_off() ;
245 }
246 
247 bool G::OptionParser::haveSeenOff( const std::string & name ) const
248 {
249  OptionMap::const_iterator p = m_map.find( name ) ;
250  return p != m_map.end() && (*p).second.is_off() ;
251 }
252 
253 bool G::OptionParser::haveSeen( const std::string & name ) const
254 {
255  return m_map.find(name) != m_map.end() ;
256 }
257 
258 /// \file goptionparser.cpp
static bool isPositive(const std::string &)
Returns true if the string has a positive meaning, such as "1", "true", "yes".
Definition: gstr.cpp:1085
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
size_t parse(const StringArray &args, size_t start_position=1U)
Parses the given command-line arguments into the value map and/or error list defined by the construct...
static OptionValue on()
A factory function for an unvalued option-enabled option.
Definition: goptionvalue.h:96
OptionParser(const Options &spec, OptionMap &values_out, StringArray &errors_out)
Constructor.
A map-like container for command-line options and their values.
Definition: goptionmap.h:38
static OptionValue off()
A factory function for an unvalued option-disabled option.
Definition: goptionvalue.h:104
static bool isNegative(const std::string &)
Returns true if the string has a negative meaning, such as "0", "false", "no".
Definition: gstr.cpp:1091
A class to represent allowed command-line options and to provide command-line usage text...
Definition: goptions.h:65