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