E-MailRelay

goptionparser.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 goptionparser.cpp
19///
20
21#include "gdef.h"
22#include "goptionparser.h"
23#include "gformat.h"
24#include "ggettext.h"
25#include "gstr.h"
26#include "glog.h"
27#include <algorithm>
28#include <map>
29#include <sstream>
30#include <stdexcept>
31#include <utility>
32
33 G::OptionParser::OptionParser( const Options & spec , OptionMap & values_out , StringArray & errors_out ) :
34 m_spec(spec) ,
35 m_map(values_out) ,
36 m_errors(&errors_out)
37{
38}
39
40 G::OptionParser::OptionParser( const Options & spec , OptionMap & values_out , StringArray * errors_out ) :
41 m_spec(spec) ,
42 m_map(values_out) ,
43 m_errors(errors_out)
44{
45}
46
47 G::StringArray G::OptionParser::parse( const StringArray & args_in , const Options & spec ,
48 OptionMap & map_out , StringArray * errors_out , std::size_t start_position ,
49 std::size_t ignore_non_options )
50{
51 OptionParser parser( spec , map_out , errors_out ) ;
52 return parser.parse( args_in , start_position , ignore_non_options ) ;
53}
54
55 G::StringArray G::OptionParser::parse( const StringArray & args_in , std::size_t start ,
56 std::size_t ignore_non_options )
57{
58 StringArray args_out ;
59 std::size_t i = start ;
60 for( ; i < args_in.size() ; i++ )
61 {
62 const std::string & arg = args_in.at(i) ;
63
64 if( arg == "--" ) // end-of-options marker
65 {
66 i++ ;
67 break ;
68 }
69
70 if( isAnOptionSet(arg) ) // eg. "-ltv"
71 {
72 for( std::size_t n = 1U ; n < arg.length() ; n++ )
73 processOptionOn( arg.at(n) ) ;
74 }
75 else if( isOldOption(arg) ) // eg. "-v"
76 {
77 char c = arg.at(1U) ;
78 if( m_spec.valued(c) && (i+1U) >= args_in.size() )
79 errorNoValue( c ) ;
80 else if( m_spec.valued(c) )
81 processOption( c , args_in.at(++i) ) ;
82 else
83 processOptionOn( c ) ;
84 }
85 else if( isNewOption(arg) ) // eg. "--foo"
86 {
87 std::string name = arg.substr( 2U ) ; // eg. "--foo" or "--foo=..."
88 std::string::size_type pos_eq = eqPos( name ) ;
89 bool has_eq = pos_eq != std::string::npos ;
90 std::string key = has_eq ? name.substr(0U,pos_eq) : name ;
91 if( has_eq && m_spec.unvalued(key) && Str::isPositive(eqValue(name,pos_eq)) ) // "foo=yes"
92 processOptionOn( key ) ;
93 else if( has_eq && m_spec.unvalued(key) && Str::isNegative(eqValue(name,pos_eq)) ) // "foo=no"
94 processOptionOff( key ) ;
95 else if( has_eq ) // "foo=bar"
96 processOption( key , eqValue(name,pos_eq) , false ) ;
97 else if( m_spec.defaulting(name) )
98 processOption( key , std::string() , false ) ;
99 else if( m_spec.valued(name) && (i+1U) >= args_in.size() )
100 errorNoValue( name ) ;
101 else if( m_spec.valued(name) )
102 processOption( name , args_in.at(++i) , true ) ;
103 else
104 processOptionOn( name ) ;
105 }
106 else if( ignore_non_options != 0U )
107 {
108 --ignore_non_options ;
109 args_out.push_back( arg ) ;
110 }
111 else
112 {
113 break ;
114 }
115 }
116 for( ; i < args_in.size() ; i++ )
117 args_out.push_back( args_in.at(i) ) ;
118 return args_out ;
119}
120
121 void G::OptionParser::processOptionOn( const std::string & name )
122{
123 if( !m_spec.valid(name) )
124 errorUnknownOption( name ) ;
125 else if( m_spec.valued(name) )
126 errorNoValue( name ) ;
127 else if( haveSeenOff(name) )
128 errorConflict( name ) ;
129 else if( haveSeenOn(name) )
130 m_map.increment( name ) ;
131 else
132 m_map.insert( std::make_pair(name,OptionValue::on()) ) ;
133}
134
135 void G::OptionParser::processOptionOff( const std::string & name )
136{
137 if( !m_spec.valid(name) )
138 errorUnknownOption( name ) ;
139 else if( m_spec.valued(name) )
140 errorNoValue( name ) ;
141 else if( haveSeenOn(name) )
142 errorConflict( name ) ;
143 else if( haveSeenOff(name) )
144 m_map.increment( name ) ;
145 else
146 m_map.insert( std::make_pair(name,OptionValue::off()) ) ;
147}
148
149 void G::OptionParser::processOption( const std::string & name , const std::string & value ,
150 bool fail_if_dubious_value )
151{
152 if( !m_spec.valid(name) )
153 errorUnknownOption( name ) ;
154 else if( !value.empty() && value[0] == '-' && fail_if_dubious_value )
155 errorDubiousValue( name , value ) ;
156 else if( !m_spec.valued(name) && !value.empty() )
157 errorExtraValue( name , value ) ;
158 else if( m_spec.multivalued(name) )
159 m_map.insert( OptionMap::value_type(name,OptionValue(value,valueCount(value))) ) ;
160 else if( haveSeen(name) && !haveSeenSame(name,value) )
161 errorDuplicate( name ) ;
162 else if( haveSeen(name) )
163 m_map.increment( name ) ;
164 else
165 m_map.insert( OptionMap::value_type(name,OptionValue(value)) ) ;
166}
167
168 void G::OptionParser::processOptionOn( char c )
169{
170 std::string name = m_spec.lookup( c ) ;
171 if( !m_spec.valid(name) )
172 errorUnknownOption( c ) ;
173 else if( m_spec.valued(name) )
174 errorNoValue( c ) ;
175 else if( haveSeenOff(name) )
176 errorConflict( name ) ;
177 else if( haveSeenOn(name) )
178 m_map.increment( name ) ;
179 else
180 m_map.insert( std::make_pair(name,OptionValue::on()) ) ;
181}
182
183 void G::OptionParser::processOption( char c , const std::string & value )
184{
185 std::string name = m_spec.lookup( c ) ;
186 if( !m_spec.valid(name) )
187 errorUnknownOption( c ) ;
188 else if( !m_spec.valued(name) && !value.empty() )
189 errorExtraValue( name , value ) ;
190 else if( m_spec.multivalued(c) )
191 m_map.insert( OptionMap::value_type(name,OptionValue(value,valueCount(value))) ) ;
192 else if( haveSeen(name) && !haveSeenSame(name,value) )
193 errorDuplicate( c ) ;
194 else if( haveSeen(name) )
195 m_map.increment( name ) ;
196 else
197 m_map.insert( OptionMap::value_type(name,OptionValue(value)) ) ;
198}
199
200std::string::size_type G::OptionParser::eqPos( const std::string & s )
201{
202 std::string::size_type p = s.find_first_not_of( "abcdefghijklmnopqrstuvwxyz0123456789-_" ) ;
203 return p != std::string::npos && s.at(p) == '=' ? p : std::string::npos ;
204}
205
206std::string G::OptionParser::eqValue( const std::string & s , std::string::size_type pos )
207{
208 return (pos+1U) == s.length() ? std::string() : s.substr(pos+1U) ;
209}
210
211 bool G::OptionParser::isOldOption( const std::string & arg )
212{
213 return
214 ( arg.length() > 1U && arg.at(0U) == '-' ) &&
215 ! isNewOption( arg ) ;
216}
217
218 bool G::OptionParser::isNewOption( const std::string & arg )
219{
220 return arg.length() > 2U && arg.at(0U) == '-' && arg.at(1U) == '-' ;
221}
222
223 bool G::OptionParser::isAnOptionSet( const std::string & arg )
224{
225 return isOldOption(arg) && arg.length() > 2U ;
226}
227
228 void G::OptionParser::errorDubiousValue( const std::string & name , const std::string & value )
229{
230 std::string s = str(
231 format(G::gettext("%1% is probably a mistake, use %2% or %3%")) %
232 ("\"--"+name+" "+value+"\"") %
233 ("\"--"+name+"=... "+value+"\"") %
234 ("\"--"+name+"="+value+"\"") ) ;
235 error( s ) ;
236}
237
238 void G::OptionParser::errorDuplicate( char c )
239{
240 error( str( format(gettext("duplicate use of %1%")) % ("\"-"+std::string(1U,c)+"\"") ) ) ;
241}
242
243 void G::OptionParser::errorDuplicate( const std::string & name )
244{
245 error( str( format(gettext("duplicate use of %1%")) % ("\"--"+name+"\"") ) ) ;
246}
247
248 void G::OptionParser::errorExtraValue( char c , const std::string & )
249{
250 error( str( format(gettext("cannot give a value with %1%")) % ("\"-"+std::string(1U,c)+"\"") ) ) ;
251}
252
253 void G::OptionParser::errorExtraValue( const std::string & name , const std::string & value )
254{
255 error( str( format(gettext("cannot give a value with %1% (%2%)")) % ("\"--"+name+"\"") % value ) ) ;
256}
257
258 void G::OptionParser::errorNoValue( char c )
259{
260 error( str( format(gettext("no value supplied for %1%")) % ("-"+std::string(1U,c)) ) ) ;
261}
262
263 void G::OptionParser::errorNoValue( const std::string & name )
264{
265 error( str( format(gettext("no value supplied for %1%")) % ("\"--"+name+"\"") ) ) ;
266}
267
268 void G::OptionParser::errorUnknownOption( char c )
269{
270 error( str( format(gettext("invalid option: %1%")) % ("\"-"+std::string(1U,c)+"\"") ) ) ;
271}
272
273 void G::OptionParser::errorUnknownOption( const std::string & name )
274{
275 error( str( format(gettext("invalid option: %1%")) % ("\"--"+name+"\"") ) ) ;
276}
277
278 void G::OptionParser::errorConflict( const std::string & name )
279{
280 error( str( format(gettext("conflicting values: %1%")) % ("\"--"+name+"\"") ) ) ;
281}
282
283 void G::OptionParser::error( const std::string & s )
284{
285 if( m_errors )
286 m_errors->push_back( s ) ;
287}
288
289 bool G::OptionParser::haveSeenOn( const std::string & name ) const
290{
291 auto p = m_map.find( name ) ;
292 return p != m_map.end() && !(*p).second.isOff() ;
293}
294
295 bool G::OptionParser::haveSeenOff( const std::string & name ) const
296{
297 auto p = m_map.find( name ) ;
298 return p != m_map.end() && (*p).second.isOff() ;
299}
300
301 bool G::OptionParser::haveSeen( const std::string & name ) const
302{
303 return m_map.find(name) != m_map.end() ;
304}
305
306 bool G::OptionParser::haveSeenSame( const std::string & name , const std::string & value ) const
307{
308 auto p = m_map.find( name ) ;
309 return p != m_map.end() && (*p).second.value() == value ;
310}
311
312std::size_t G::OptionParser::valueCount( const std::string & s )
313{
314 return 1U + std::count( s.begin() , s.end() , ',' ) ;
315}
316
G::OptionMap
A multimap-like container for command-line options and their values.
Definition: goptionmap.h:42
G::OptionParser
A parser for command-line arguments that operates according to an Options specification and returns a...
Definition: goptionparser.h:43
G::OptionParser::errorDuplicate
void errorDuplicate(const std::string &)
Adds a 'duplicate' error in the constructor's error list for the given option.
Definition: goptionparser.cpp:243
G::OptionParser::OptionParser
OptionParser(const Options &spec, OptionMap &values_out, StringArray &errors_out)
Constructor.
Definition: goptionparser.cpp:33
G::OptionParser::parse
StringArray parse(const StringArray &args_in, std::size_t start_position=1U, std::size_t ignore_non_options=0U)
Parses the given command-line arguments into the value map and/or error list defined by the construct...
Definition: goptionparser.cpp:55
G::OptionValue::on
static OptionValue on()
A factory function for an unvalued option-enabled option.
Definition: goptionvalue.h:97
G::OptionValue::off
static OptionValue off()
A factory function for an unvalued option-disabled option.
Definition: goptionvalue.h:103
G::Options
A class to represent allowed command-line options and to provide command-line usage text.
Definition: goptions.h:66
G::Str::isNegative
static bool isNegative(const std::string &)
Returns true if the string has a negative meaning, such as "0", "false", "no".
Definition: gstr.cpp:1362
G::Str::isPositive
static bool isPositive(const std::string &)
Returns true if the string has a positive meaning, such as "1", "true", "yes".
Definition: gstr.cpp:1356
G::format
A simple version of boost::format for formatting strings in an i18n-friendly way.
Definition: gformat.h:46
gformat.h
ggettext.h
glog.h
goptionparser.h
gstr.h
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