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