1 /* Arg_parser - POSIX/GNU command line argument parser. (C++ version)
2 Copyright (C) 2006-2021 Antonio Diaz Diaz.
3
4 This library is free software. Redistribution and use in source and
5 binary forms, with or without modification, are permitted provided
6 that the following conditions are met:
7
8 1. Redistributions of source code must retain the above copyright
9 notice, this list of conditions, and the following disclaimer.
10
11 2. Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions, and the following disclaimer in the
13 documentation and/or other materials provided with the distribution.
14
15 This library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 */
19
20 #include <cstring>
21 #include <string>
22 #include <vector>
23
24 #include "arg_parser.h"
25
26
parse_long_option(const char * const opt,const char * const arg,const Option options[],int & argind)27 bool Arg_parser::parse_long_option( const char * const opt, const char * const arg,
28 const Option options[], int & argind )
29 {
30 unsigned len;
31 int index = -1;
32 bool exact = false, ambig = false;
33
34 for( len = 0; opt[len+2] && opt[len+2] != '='; ++len ) ;
35
36 // Test all long options for either exact match or abbreviated matches.
37 for( int i = 0; options[i].code != 0; ++i )
38 if( options[i].name && std::strncmp( options[i].name, &opt[2], len ) == 0 )
39 {
40 if( std::strlen( options[i].name ) == len ) // Exact match found
41 { index = i; exact = true; break; }
42 else if( index < 0 ) index = i; // First nonexact match found
43 else if( options[index].code != options[i].code ||
44 options[index].has_arg != options[i].has_arg )
45 ambig = true; // Second or later nonexact match found
46 }
47
48 if( ambig && !exact )
49 {
50 error_ = "option '"; error_ += opt; error_ += "' is ambiguous";
51 return false;
52 }
53
54 if( index < 0 ) // nothing found
55 {
56 error_ = "unrecognized option '"; error_ += opt; error_ += '\'';
57 return false;
58 }
59
60 ++argind;
61 data.push_back( Record( options[index].code ) );
62
63 if( opt[len+2] ) // '--<long_option>=<argument>' syntax
64 {
65 if( options[index].has_arg == no )
66 {
67 error_ = "option '--"; error_ += options[index].name;
68 error_ += "' doesn't allow an argument";
69 return false;
70 }
71 if( options[index].has_arg == yes && !opt[len+3] )
72 {
73 error_ = "option '--"; error_ += options[index].name;
74 error_ += "' requires an argument";
75 return false;
76 }
77 data.back().argument = &opt[len+3];
78 return true;
79 }
80
81 if( options[index].has_arg == yes )
82 {
83 if( !arg || !arg[0] )
84 {
85 error_ = "option '--"; error_ += options[index].name;
86 error_ += "' requires an argument";
87 return false;
88 }
89 ++argind; data.back().argument = arg;
90 return true;
91 }
92
93 return true;
94 }
95
96
parse_short_option(const char * const opt,const char * const arg,const Option options[],int & argind)97 bool Arg_parser::parse_short_option( const char * const opt, const char * const arg,
98 const Option options[], int & argind )
99 {
100 int cind = 1; // character index in opt
101
102 while( cind > 0 )
103 {
104 int index = -1;
105 const unsigned char c = opt[cind];
106
107 if( c != 0 )
108 for( int i = 0; options[i].code; ++i )
109 if( c == options[i].code )
110 { index = i; break; }
111
112 if( index < 0 )
113 {
114 error_ = "invalid option -- '"; error_ += c; error_ += '\'';
115 return false;
116 }
117
118 data.push_back( Record( c ) );
119 if( opt[++cind] == 0 ) { ++argind; cind = 0; } // opt finished
120
121 if( options[index].has_arg != no && cind > 0 && opt[cind] )
122 {
123 data.back().argument = &opt[cind]; ++argind; cind = 0;
124 }
125 else if( options[index].has_arg == yes )
126 {
127 if( !arg || !arg[0] )
128 {
129 error_ = "option requires an argument -- '"; error_ += c;
130 error_ += '\'';
131 return false;
132 }
133 data.back().argument = arg; ++argind; cind = 0;
134 }
135 }
136 return true;
137 }
138
139
Arg_parser(const int argc,const char * const argv[],const Option options[],const bool in_order)140 Arg_parser::Arg_parser( const int argc, const char * const argv[],
141 const Option options[], const bool in_order )
142 {
143 if( argc < 2 || !argv || !options ) return;
144
145 std::vector< const char * > non_options; // skipped non-options
146 int argind = 1; // index in argv
147
148 while( argind < argc )
149 {
150 const unsigned char ch1 = argv[argind][0];
151 const unsigned char ch2 = ch1 ? argv[argind][1] : 0;
152
153 if( ch1 == '-' && ch2 ) // we found an option
154 {
155 const char * const opt = argv[argind];
156 const char * const arg = ( argind + 1 < argc ) ? argv[argind+1] : 0;
157 if( ch2 == '-' )
158 {
159 if( !argv[argind][2] ) { ++argind; break; } // we found "--"
160 else if( !parse_long_option( opt, arg, options, argind ) ) break;
161 }
162 else if( !parse_short_option( opt, arg, options, argind ) ) break;
163 }
164 else
165 {
166 if( in_order ) data.push_back( Record( argv[argind++] ) );
167 else non_options.push_back( argv[argind++] );
168 }
169 }
170 if( !error_.empty() ) data.clear();
171 else
172 {
173 for( unsigned i = 0; i < non_options.size(); ++i )
174 data.push_back( Record( non_options[i] ) );
175 while( argind < argc )
176 data.push_back( Record( argv[argind++] ) );
177 }
178 }
179
180
Arg_parser(const char * const opt,const char * const arg,const Option options[])181 Arg_parser::Arg_parser( const char * const opt, const char * const arg,
182 const Option options[] )
183 {
184 if( !opt || !opt[0] || !options ) return;
185
186 if( opt[0] == '-' && opt[1] ) // we found an option
187 {
188 int argind = 1; // dummy
189 if( opt[1] == '-' )
190 { if( opt[2] ) parse_long_option( opt, arg, options, argind ); }
191 else
192 parse_short_option( opt, arg, options, argind );
193 if( !error_.empty() ) data.clear();
194 }
195 else data.push_back( Record( opt ) );
196 }
197