yaggo 1.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +107 -0
- data/bin/create_yaggo_one_file +45 -0
- data/bin/yaggo +22 -0
- data/lib/yaggo/dsl.rb +573 -0
- data/lib/yaggo/general.rb +127 -0
- data/lib/yaggo/library.rb +209 -0
- data/lib/yaggo/main.rb +152 -0
- data/lib/yaggo/man_page.rb +449 -0
- data/lib/yaggo/parser.rb +404 -0
- data/lib/yaggo/stub.rb +42 -0
- data/lib/yaggo/version.rb +1 -0
- data/lib/yaggo/zsh_completion.rb +94 -0
- metadata +58 -0
@@ -0,0 +1,449 @@
|
|
1
|
+
# This file is part of Yaggo.
|
2
|
+
|
3
|
+
# Yaggo is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
|
8
|
+
# Yaggo is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with Yaggo. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
require 'pathname'
|
17
|
+
|
18
|
+
def display_man_page out
|
19
|
+
manual = <<EOS
|
20
|
+
.TH yaggo 1 "2015-06-24" "version #{$yaggo_version}" "USER COMMANDS"
|
21
|
+
|
22
|
+
.SH NAME
|
23
|
+
yaggo \- command line switch parser generator
|
24
|
+
|
25
|
+
.SH SYNOPSIS
|
26
|
+
.B yaggo
|
27
|
+
[-o|--output FILE] [-l|--license PATH] [-s|--stub] [--zc PATH] [-e|--extended-syntax] [--man] [-h|--help]
|
28
|
+
|
29
|
+
.SH DESCRIPTION
|
30
|
+
Yaggo stands for Yet Another GenGetOpt. It is inspired by gengetopt
|
31
|
+
software from the FSF.
|
32
|
+
|
33
|
+
Yaggo generates a C++ class to parse command line switches (usually
|
34
|
+
argc and argv passed to main) using getopt_long. The switches and
|
35
|
+
arguments to the program are specified in a description file. To each
|
36
|
+
description file, yaggo generates one C++ header file containing the
|
37
|
+
parsing code.
|
38
|
+
.PP
|
39
|
+
See the EXAMPLES section for a complete and simple example.
|
40
|
+
|
41
|
+
.SH OPTIONS
|
42
|
+
.TP
|
43
|
+
\-l|\-\-license
|
44
|
+
Display the file at the top of the generated headers. It usually
|
45
|
+
contains the license governing the distribution of the headers.
|
46
|
+
.TP
|
47
|
+
-m|\-\-man
|
48
|
+
Display this man page
|
49
|
+
.TP
|
50
|
+
\-s|\-\-stub
|
51
|
+
Generate a stub: a simple yaggo file that can be modified for one's use.
|
52
|
+
.TP
|
53
|
+
\-e|--extended-syntax
|
54
|
+
Use the extended syntax: blocks can be defined on the next line of a command.
|
55
|
+
.TP
|
56
|
+
\-h|--help
|
57
|
+
Display a short help text
|
58
|
+
.PP
|
59
|
+
|
60
|
+
.SH EXAMPLE
|
61
|
+
|
62
|
+
Consider the description files 'example_args.yaggo' which defines a
|
63
|
+
switch "-i" (or "--int") that takes an unsigned integer and defaults
|
64
|
+
to 42; a switch "-s" (or "--string") that takes a string and can be
|
65
|
+
given multiple times; a switch "--flag" which does not take any
|
66
|
+
argument; a switch "--severity" which can take only 3 values: "low",
|
67
|
+
"middle" and "high".
|
68
|
+
|
69
|
+
It takes the following arguments: a string followed by zero or more floating point numbers.
|
70
|
+
|
71
|
+
.nf
|
72
|
+
purpose "Example of yaggo usage"
|
73
|
+
package "example"
|
74
|
+
description "This is just an example.
|
75
|
+
And a multi-line description."
|
76
|
+
|
77
|
+
option("int", "i") {
|
78
|
+
description "Integer switch"
|
79
|
+
uint32; default "42" }
|
80
|
+
option("string", "s") {
|
81
|
+
description "Many strings"
|
82
|
+
string; multiple }
|
83
|
+
option("flag") {
|
84
|
+
description "A flag switch"
|
85
|
+
flag; off }
|
86
|
+
option("severity") {
|
87
|
+
description "An enum switch"
|
88
|
+
enum "low", "middle", "high" }
|
89
|
+
arg("first") {
|
90
|
+
description "First arg"
|
91
|
+
c_string }
|
92
|
+
arg("rest") {
|
93
|
+
description "Rest of'em"
|
94
|
+
double; multiple }
|
95
|
+
.fi
|
96
|
+
|
97
|
+
The associated simple C++ program 'examples.cpp' which display information about the switches and arguments passed:
|
98
|
+
|
99
|
+
.nf
|
100
|
+
#include <iostream>
|
101
|
+
#include "example_args.hpp"
|
102
|
+
|
103
|
+
int main(int argc, char *argv[]) {
|
104
|
+
example_args args(argc, argv);
|
105
|
+
|
106
|
+
std::cout << "Integer switch: " << args.int_arg << "\\\\n";
|
107
|
+
if(args.string_given)
|
108
|
+
std::cout << "Number of string(s): " << args.string_arg.size() << "\\\\n";
|
109
|
+
else
|
110
|
+
std::cout << "No string switch\\\\n";
|
111
|
+
std::cout << "Flag is " << (args.flag_flag ? "on" : "off") << "\\\\n";
|
112
|
+
std::cout << "First arg: " << args.first_arg << "\\\\n";
|
113
|
+
std::cout << "Severity arg: " << args.severity_arg << " " << example_args::severity::strs[args.severity_arg] << "\\\\n";
|
114
|
+
if(args.severity_arg == example_args::severity::high)
|
115
|
+
std::cout << "Warning: severity is high\\\\n";
|
116
|
+
std::cout << "Rest:";
|
117
|
+
for(example_args::rest_arg_it it = args.rest_arg.begin(); it != args.rest_arg.end(); ++it)
|
118
|
+
std::cout << " " << *it;
|
119
|
+
std::cout << std::endl;
|
120
|
+
|
121
|
+
return 0;
|
122
|
+
}
|
123
|
+
.fi
|
124
|
+
|
125
|
+
This can be compiled with the following commands:
|
126
|
+
|
127
|
+
.nf
|
128
|
+
% yaggo example_args.yaggo
|
129
|
+
% g++ -o example example.cpp
|
130
|
+
.fi
|
131
|
+
|
132
|
+
The yaggo command above will create by default the file
|
133
|
+
'example_args.hpp' (changed '.yaggo' extension to '.hpp'). The output
|
134
|
+
file name can be changed with the 'output' keyword explained below.
|
135
|
+
|
136
|
+
.SH DESCRIPTION FORMAT
|
137
|
+
|
138
|
+
A description file is a sequence of statements. A statement is a
|
139
|
+
keyword followed by some arguments. Strings must be surrounded by
|
140
|
+
quotes ("" or '') and can span multiple lines. The order of the
|
141
|
+
statements is irrelevant. Statements are separated by new lines or
|
142
|
+
semi-colons ';'.
|
143
|
+
|
144
|
+
.IP *
|
145
|
+
Technically speaking, yaggo is implemented as a DSL (Domain Specific
|
146
|
+
Language) using ruby. The description file is a valid ruby script and
|
147
|
+
the keywords are ruby functions.
|
148
|
+
.PP
|
149
|
+
|
150
|
+
The following statements are global, not attached to a particular option or argument.
|
151
|
+
|
152
|
+
.TP
|
153
|
+
purpose
|
154
|
+
A one line description of the program.
|
155
|
+
.TP
|
156
|
+
package
|
157
|
+
The name of the package for the usage string. Defaults to the name of the class.
|
158
|
+
.TP
|
159
|
+
usage
|
160
|
+
The usage string. If none given a standard one is generated by yaggo.
|
161
|
+
.TP
|
162
|
+
description
|
163
|
+
A longer description of the program displayed before the list of switch. Displayed by the help.
|
164
|
+
.TP
|
165
|
+
text
|
166
|
+
Some text to be displayed after the list of switches. Displayed by the help.
|
167
|
+
.TP
|
168
|
+
version
|
169
|
+
The version string of the software.
|
170
|
+
.TP
|
171
|
+
license
|
172
|
+
The license and copyright string of the software.
|
173
|
+
.TP
|
174
|
+
name
|
175
|
+
The name of the class generated. Defaults to the name of the
|
176
|
+
description file minus the .yaggo extension.
|
177
|
+
.TP
|
178
|
+
posix
|
179
|
+
Posix correct behavior (instead of GNU behavior): switch processing
|
180
|
+
stops at the first non-option argument
|
181
|
+
.TP
|
182
|
+
output
|
183
|
+
The name of the output file. Defaults to the name of the
|
184
|
+
description file with the .yaggo extension changed to .hpp.
|
185
|
+
.PP
|
186
|
+
|
187
|
+
The 'option' statement takes one or two arguments, which must be in
|
188
|
+
parentheses, and a block of statements surrounded by curly braces
|
189
|
+
({...}). The arguments are the long and short version of the
|
190
|
+
option. Either one of the long or short version can be omitted. The
|
191
|
+
block of statements describe the option in more details, as described
|
192
|
+
below.
|
193
|
+
|
194
|
+
A switch is named after the long version, or the short version if no
|
195
|
+
long version. An 'option' statement for an option named 'switch'
|
196
|
+
defines one or two public members in the class. For a flag, it
|
197
|
+
creates 'switch_flag' as a boolean. Otherwise, it
|
198
|
+
creates 'switch_arg', with a type as specified, and 'switch_given', a
|
199
|
+
boolean indicating whether or not the switch was given on the command
|
200
|
+
line.
|
201
|
+
|
202
|
+
For example, the statement:
|
203
|
+
|
204
|
+
.nf
|
205
|
+
option("integer", "i") {
|
206
|
+
int; default 5
|
207
|
+
}
|
208
|
+
.fi
|
209
|
+
|
210
|
+
will add the following members to the C++ class:
|
211
|
+
|
212
|
+
.nf
|
213
|
+
int integer_arg;
|
214
|
+
bool integer_given;
|
215
|
+
.fi
|
216
|
+
|
217
|
+
where "integer_arg" is initialized to 5 and "integer_given" is
|
218
|
+
initialized to "false". If the switch "--integer 10" or "-i 10" is
|
219
|
+
passed on the command line "integer_arg" is set to 10 and
|
220
|
+
integer_given is set to "true".
|
221
|
+
|
222
|
+
The statement:
|
223
|
+
|
224
|
+
.nf
|
225
|
+
option("verbose") {
|
226
|
+
off
|
227
|
+
}
|
228
|
+
.fi
|
229
|
+
|
230
|
+
will add the following member to the C++ class:
|
231
|
+
|
232
|
+
.nf
|
233
|
+
bool verbose_flag;
|
234
|
+
.fi
|
235
|
+
|
236
|
+
where "verbose_flag" is initialized to "false". Passing the switch
|
237
|
+
"--verbose" on the command line sets "verbose_flag" to true".
|
238
|
+
|
239
|
+
|
240
|
+
In addition to the switch created by 'option', the following switches
|
241
|
+
are defined by default (unless some option statement overrides them):
|
242
|
+
|
243
|
+
.TP
|
244
|
+
\-h, \-\-help
|
245
|
+
Display the help message.
|
246
|
+
.TP
|
247
|
+
\-\-full\-help
|
248
|
+
Display hidden options as well.
|
249
|
+
.TP
|
250
|
+
\-\-version
|
251
|
+
Display version string.
|
252
|
+
.PP
|
253
|
+
|
254
|
+
The following statement are recognized in an option block:
|
255
|
+
|
256
|
+
.TP
|
257
|
+
description "str"
|
258
|
+
A short description for this switch.
|
259
|
+
|
260
|
+
.TP
|
261
|
+
int32, int64, uint32, uint64, double, int, long
|
262
|
+
This switch is parsed as a number with the corresponding type int32_t,
|
263
|
+
int64_t, uint32_t, uint64_t, double, int and long.
|
264
|
+
|
265
|
+
.TP
|
266
|
+
suffix
|
267
|
+
Valid for numerical type switches as above. It can be appended
|
268
|
+
with a SI suffix (e.g. 1M mean 1000000). The suffixes k, M, G, T, P,
|
269
|
+
and E are supported for all the numerical types. The suffixes m, u, n,
|
270
|
+
p, f, and a are supported for the double type.
|
271
|
+
|
272
|
+
.TP
|
273
|
+
c_string, string
|
274
|
+
This switch is taken as a C string (const char *) or a C++ string
|
275
|
+
(inherits from std::string). The C++ string type has the extra
|
276
|
+
methods '<type> as_<type>(bool suffix)', where <type> is any numerical
|
277
|
+
type as above, to convert the string into that type. If the 'suffix'
|
278
|
+
boolean is true, parsing is done using SI suffixes.
|
279
|
+
|
280
|
+
.TP
|
281
|
+
enum
|
282
|
+
This statement must be followed by a comma separated list of strings
|
283
|
+
(as in 'enum "choice0", "choice1", "choice2"'). This switch takes value
|
284
|
+
a string in the list and is converted to int. C enum type named
|
285
|
+
"switchname::enum" is defined with the same choices in the given order.
|
286
|
+
|
287
|
+
.TP
|
288
|
+
required
|
289
|
+
This switch is required. An error is generated if not given on the
|
290
|
+
command line.
|
291
|
+
.TP
|
292
|
+
conflict
|
293
|
+
Specify a comma separated list of switches that conflicts with this
|
294
|
+
one.
|
295
|
+
.TP
|
296
|
+
imply
|
297
|
+
Specify a comma separated list of switches (of type flag) which are
|
298
|
+
implied by this one.
|
299
|
+
.TP
|
300
|
+
hidden
|
301
|
+
This switch is not shown with --help. Use --full-help to see the
|
302
|
+
hidden switches, if any.
|
303
|
+
.TP
|
304
|
+
secret
|
305
|
+
This switch is not shown in any help message. Neither --help nor
|
306
|
+
--full-help.
|
307
|
+
.TP
|
308
|
+
multiple
|
309
|
+
This switch can be passed multiple times. The values are stored in a
|
310
|
+
std::vector. A type for the iterator is also defined in the class with
|
311
|
+
the name 'switch_arg_it', where 'switch' is the name of the option.
|
312
|
+
.TP
|
313
|
+
flag
|
314
|
+
This switch is a flag and does not take an argument.
|
315
|
+
.TP
|
316
|
+
on, off
|
317
|
+
The default state for a flag switch. Implies flag. Unless the 'no'
|
318
|
+
option is used (see below), with 'off', the default value of the flag
|
319
|
+
is "false" and passing --flag sets it to true. With 'on', the default
|
320
|
+
value of the flag is "true" and passing --flag sets it to false.
|
321
|
+
.TP
|
322
|
+
no
|
323
|
+
A flag with two switches. If the switch is named "flag", two switches
|
324
|
+
are generated: --flag and --noflag, respectively setting it to "true"
|
325
|
+
and "false". The 'on' and 'off' options define the default value.
|
326
|
+
.TP
|
327
|
+
default "val"
|
328
|
+
The default value for this switch. It can be a string or a valid
|
329
|
+
number. SI suffixes are supported as well (for example "1M" means 1
|
330
|
+
m`illion).
|
331
|
+
.TP
|
332
|
+
typestr "str"
|
333
|
+
In the help message, by default, the type of the option is
|
334
|
+
displayed. It can be replaced by the string given to 'typestr'.
|
335
|
+
.TP
|
336
|
+
at_least n
|
337
|
+
The given switch must be given at least n times. Implies multiple.
|
338
|
+
.TP
|
339
|
+
access "type"
|
340
|
+
Make sure that the string passed is a path to which we have
|
341
|
+
access. "type" is a comma separated list of "read", "write" or
|
342
|
+
"exec". It is checked with access(2). The same warning applies:
|
343
|
+
|
344
|
+
"Warning: Using access() to check if a user is authorized to, for
|
345
|
+
example, open a file before actually doing so using open(2) creates a
|
346
|
+
security hole, because the user might exploit the short time interval
|
347
|
+
between checking and opening the file to manipulate it. For this
|
348
|
+
reason, the use of this system call should be avoided. (In the
|
349
|
+
example just described, a safer alternative would be to temporarily
|
350
|
+
switch the process's effective user ID to the real ID and then call
|
351
|
+
open(2).)"
|
352
|
+
|
353
|
+
.PP
|
354
|
+
|
355
|
+
A 'arg' statement defines an arg passed to the command line. The
|
356
|
+
statement takes a single argument, the name of the arg, and a block of
|
357
|
+
statements. The block of statements are similar to the option block,
|
358
|
+
except that "hidden", "flag", "on", "off" and "no" are not allowed. At
|
359
|
+
most one arg can have the 'multiple' statement, and it must be the
|
360
|
+
last one.
|
361
|
+
|
362
|
+
.SH EXAMPLE USAGE
|
363
|
+
|
364
|
+
The argument object parses the switches on construction or later on
|
365
|
+
using the parse method. For example, the two pieces code show these
|
366
|
+
two different usage.
|
367
|
+
|
368
|
+
Using parse method:
|
369
|
+
.nf
|
370
|
+
example_args args; // Global variable with switches
|
371
|
+
|
372
|
+
int main(int argc, char* argv[]) {
|
373
|
+
args.parse(argc, argv);
|
374
|
+
}
|
375
|
+
.fi
|
376
|
+
|
377
|
+
Parse on construction:
|
378
|
+
.nf
|
379
|
+
int main(int argc, char* argv[]) {
|
380
|
+
example_args args(argc, argv);
|
381
|
+
}
|
382
|
+
.fi
|
383
|
+
|
384
|
+
The subclass error can be used to output error messsage (and terminate
|
385
|
+
program). It output an error message, the usage string, etc. The error
|
386
|
+
class behave like an output stream, it can be used to create
|
387
|
+
complicated error message. For example:
|
388
|
+
|
389
|
+
.nf
|
390
|
+
if(false_condition)
|
391
|
+
example_args::error() << "Failed to open file '" << args.file_arg << "'";
|
392
|
+
.fi
|
393
|
+
|
394
|
+
An error object prints an error message and terminate the program with
|
395
|
+
exit upon destruction. An exit code can be passed to error. By default
|
396
|
+
the exit code (passed to exit) is the constant EXIT_FAILURE (normally
|
397
|
+
1). For example:
|
398
|
+
|
399
|
+
.nf
|
400
|
+
example_args::error(77) << "Failed with return code 77";
|
401
|
+
.fi
|
402
|
+
|
403
|
+
.SH LICENSE
|
404
|
+
|
405
|
+
There are 2 parts to the software: the yaggo ruby script itself, and
|
406
|
+
the header files generated by yaggo from the description files. The
|
407
|
+
licenses are as follow:
|
408
|
+
|
409
|
+
.TP
|
410
|
+
yaggo the ruby script
|
411
|
+
This software is licensed under the GNU General
|
412
|
+
Public License version 3 or any later version. Copyright (c) 2011
|
413
|
+
Guillaume Marcais.
|
414
|
+
|
415
|
+
.TP The generated header files. These files have the license and
|
416
|
+
copyright that you, the user of yaggo, assign with the 'license'
|
417
|
+
keyword. .PP In short: only yaggo the software is GPL. The generated
|
418
|
+
header files are considered derivative of your work (e.g. the
|
419
|
+
description), and you define the copyright and license of those as you
|
420
|
+
see fit.
|
421
|
+
|
422
|
+
.SH BUGS
|
423
|
+
.IP *
|
424
|
+
The error message returned by ruby can be a little confusing.
|
425
|
+
|
426
|
+
.SH AUTHOR
|
427
|
+
Guillaume Marcais (gmarcais@umd.edu)
|
428
|
+
.SH SEE ALSO
|
429
|
+
getopt_long(3), gengetopt(1), exit(2)
|
430
|
+
EOS
|
431
|
+
|
432
|
+
if !out && STDOUT.isatty
|
433
|
+
require 'tempfile'
|
434
|
+
Tempfile.open("yaggo_man") do |fd|
|
435
|
+
begin
|
436
|
+
fd.write(manual)
|
437
|
+
fd.flush
|
438
|
+
system("man", fd.path)
|
439
|
+
ensure
|
440
|
+
fd.unlink
|
441
|
+
end
|
442
|
+
end
|
443
|
+
elsif !out
|
444
|
+
STDOUT.puts(manual)
|
445
|
+
else
|
446
|
+
path = Pathname.new(out)
|
447
|
+
path.write manual
|
448
|
+
end
|
449
|
+
end
|
data/lib/yaggo/parser.rb
ADDED
@@ -0,0 +1,404 @@
|
|
1
|
+
# This file is part of Yaggo.
|
2
|
+
|
3
|
+
# Yaggo is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
|
8
|
+
# Yaggo is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with Yaggo. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
def quote_newline_dquotes str, spaces = ""
|
17
|
+
str.gsub(/"/, '\\"').split(/\n/).join("\\n\" \\\n#{spaces}\"")
|
18
|
+
end
|
19
|
+
|
20
|
+
def output_options_descriptions out, opts, hidden
|
21
|
+
opts.each { |o|
|
22
|
+
# need to be improved. break lines if too long
|
23
|
+
next if o.secret || (o.hidden ^ hidden)
|
24
|
+
s = " " + o.switches
|
25
|
+
if s.size >= $switchesjust
|
26
|
+
s += "\\n" + "".ljust($switchesjust)
|
27
|
+
else
|
28
|
+
s = s.ljust($switchesjust)
|
29
|
+
end
|
30
|
+
out.puts(" \"#{s} #{o.help}\\n\"")
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def output_cpp_parser(h, class_name)
|
35
|
+
$options.each { |o| o.check }
|
36
|
+
$args.each { |a| a.check }
|
37
|
+
if $args.size > 1
|
38
|
+
mul_args = $args[0..-2].select { |a| a.multiple }
|
39
|
+
if mul_args.size > 0
|
40
|
+
gram = mul_args.size > 1 ? "s are" : " is"
|
41
|
+
raise "The following#{gram} not the last arg but marked multiple: #{mul_args.map { |a| a.name }.join(", ")}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Headers
|
46
|
+
|
47
|
+
h.puts(<<EOS)
|
48
|
+
/***** This code was generated by Yaggo. Do not edit ******/
|
49
|
+
|
50
|
+
EOS
|
51
|
+
|
52
|
+
if $license
|
53
|
+
lines = $license.split(/\n/)
|
54
|
+
h.puts("/* #{lines[0]}", *(lines[1..-1].map { |l| " * " + l }))
|
55
|
+
h.puts(" */", "")
|
56
|
+
elsif $yaggo_options[:license]
|
57
|
+
open($yaggo_options[:license]) { |fd|
|
58
|
+
h.puts(fd.read)
|
59
|
+
}
|
60
|
+
h.puts("")
|
61
|
+
end
|
62
|
+
|
63
|
+
h.puts(<<EOS)
|
64
|
+
#ifndef __#{class_name.upcase()}_HPP__
|
65
|
+
#define __#{class_name.upcase()}_HPP__
|
66
|
+
|
67
|
+
#include <stdint.h>
|
68
|
+
#include <unistd.h>
|
69
|
+
#include <stdlib.h>
|
70
|
+
#include <getopt.h>
|
71
|
+
#include <errno.h>
|
72
|
+
#include <string.h>
|
73
|
+
#include <stdexcept>
|
74
|
+
#include <string>
|
75
|
+
#include <limits>
|
76
|
+
#include <vector>
|
77
|
+
#include <iostream>
|
78
|
+
#include <sstream>
|
79
|
+
#include <memory>
|
80
|
+
|
81
|
+
class #{class_name} {
|
82
|
+
// Boiler plate stuff. Conversion from string to other formats
|
83
|
+
EOS
|
84
|
+
|
85
|
+
output_conversion_code h
|
86
|
+
|
87
|
+
h.puts(<<EOS)
|
88
|
+
public:
|
89
|
+
EOS
|
90
|
+
|
91
|
+
static_decl = $options.map { |o| o.static_decl }.flatten
|
92
|
+
h.puts(" " + static_decl.join("\n "), "") unless static_decl.empty?
|
93
|
+
|
94
|
+
($options + $args).each { |o| h.puts(" " + o.var_decl.join("\n ")) }
|
95
|
+
h.puts("")
|
96
|
+
|
97
|
+
# Create enum if option with no short version
|
98
|
+
only_long = $options.map { |o| o.long_enum }.flatten.compact
|
99
|
+
need_full = $options.any? { |o| o.hidden }
|
100
|
+
|
101
|
+
help_no_h = $options.any? { |o| o.short == "h" }
|
102
|
+
version_no_V = $options.any? { |o| o.short == "V" }
|
103
|
+
usage_no_U = $options.any? { |o| o.short == "U" }
|
104
|
+
h.print(" enum {\n START_OPT = 1000")
|
105
|
+
h.print(",\n FULL_HELP_OPT") if need_full
|
106
|
+
h.print(",\n HELP_OPT") if help_no_h
|
107
|
+
h.print(",\n VERSION_OPT") if version_no_V
|
108
|
+
h.print(",\n USAGE_OPT") if usage_no_U
|
109
|
+
if only_long.empty?
|
110
|
+
h.puts("\n };")
|
111
|
+
else
|
112
|
+
h.puts(",", " " + only_long.join(",\n "), " };")
|
113
|
+
end
|
114
|
+
|
115
|
+
# Constructors and initialization
|
116
|
+
h.puts("", " #{class_name}() :")
|
117
|
+
h.puts(" " + ($options + $args).map { |o| o.init }.join(",\n "), " { }")
|
118
|
+
h.puts("", " #{class_name}(int argc, char* argv[]) :")
|
119
|
+
h.puts(" " + ($options + $args).map { |o| o.init }.join(",\n "))
|
120
|
+
h.puts(" { parse(argc, argv); }", "");
|
121
|
+
|
122
|
+
# Main arsing function
|
123
|
+
h.puts(" void parse(int argc, char* argv[]) {",
|
124
|
+
" static struct option long_options[] = {")
|
125
|
+
$options.empty? or
|
126
|
+
h.puts(" " + $options.map { |o| o.struct }.flatten.join(",\n ") + ",")
|
127
|
+
h.puts(" {\"help\", 0, 0, #{help_no_h ? "HELP_OPT" : "'h'"}},")
|
128
|
+
h.puts(" {\"full-help\", 0, 0, FULL_HELP_OPT},") if need_full
|
129
|
+
h.puts(" {\"usage\", 0, 0, #{usage_no_U ? "USAGE_OPT" : "'U'"}},",
|
130
|
+
" {\"version\", 0, 0, #{version_no_V ? "VERSION_OPT" : "'V'"}},",
|
131
|
+
" {0, 0, 0, 0}", " };")
|
132
|
+
short_str = $posix ? "+" : ""
|
133
|
+
short_str += "h" unless help_no_h
|
134
|
+
short_str += "V" unless version_no_V
|
135
|
+
short_str += "U" unless usage_no_U
|
136
|
+
short_str += $options.map { |o| o.short_str }.compact.join("")
|
137
|
+
|
138
|
+
h.puts(" static const char *short_options = \"#{short_str}\";", "")
|
139
|
+
|
140
|
+
need_err = $options.any? { |o| o.type != :flag && o.type != :string && o.type != :c_string}
|
141
|
+
need_err ||= $args.any? { |a| a.type != :string && a.type != :c_string }
|
142
|
+
need_err ||= ($options + $args).any? { |o| !o.access_types.empty? }
|
143
|
+
h.puts(" ::std::string err;") if need_err
|
144
|
+
|
145
|
+
# Actual parsing
|
146
|
+
h.puts(<<EOS)
|
147
|
+
#define CHECK_ERR(type,val,which) if(!err.empty()) { ::std::cerr << "Invalid " #type " '" << val << "' for [" which "]: " << err << "\\n"; exit(1); }
|
148
|
+
while(true) {
|
149
|
+
int index = -1;
|
150
|
+
int c = getopt_long(argc, argv, short_options, long_options, &index);
|
151
|
+
if(c == -1) break;
|
152
|
+
switch(c) {
|
153
|
+
case ':':
|
154
|
+
::std::cerr << \"Missing required argument for \"
|
155
|
+
<< (index == -1 ? ::std::string(1, (char)optopt) : std::string(long_options[index].name))
|
156
|
+
<< ::std::endl;
|
157
|
+
exit(1);
|
158
|
+
case #{help_no_h ? "HELP_OPT" : "'h'"}:
|
159
|
+
::std::cout << usage() << \"\\n\\n\" << help() << std::endl;
|
160
|
+
exit(0);
|
161
|
+
case #{usage_no_U ? "USAGE_OPT" : "'U'"}:
|
162
|
+
::std::cout << usage() << \"\\nUse --help for more information.\" << std::endl;
|
163
|
+
exit(0);
|
164
|
+
case 'V':
|
165
|
+
print_version();
|
166
|
+
exit(0);
|
167
|
+
case '?':
|
168
|
+
::std::cerr << \"Use --usage or --help for some help\\n\";
|
169
|
+
exit(1);
|
170
|
+
EOS
|
171
|
+
if need_full
|
172
|
+
h.puts(<<EOS)
|
173
|
+
case FULL_HELP_OPT:
|
174
|
+
::std::cout << usage() << \"\\n\\n\" << help() << \"\\n\\n\" << hidden() << std::flush;
|
175
|
+
exit(0);
|
176
|
+
EOS
|
177
|
+
end
|
178
|
+
|
179
|
+
$options.each { |o|
|
180
|
+
if o.type == :flag && o.noflag
|
181
|
+
h.puts(" case #{o.long_enum[0]}:",
|
182
|
+
" " + o.parse_arg.join("\n "),
|
183
|
+
" break;",
|
184
|
+
" case #{o.long_enum[1]}:",
|
185
|
+
" " + o.parse_arg(true).join("\n "),
|
186
|
+
" break;")
|
187
|
+
else
|
188
|
+
h.puts(" case #{o.long_enum ? o.long_enum[0] : "'" + o.short + "'"}:",
|
189
|
+
" " + o.parse_arg.join("\n "),
|
190
|
+
" break;")
|
191
|
+
end
|
192
|
+
}
|
193
|
+
h.puts(" }", # close case
|
194
|
+
" }") # close while(true)
|
195
|
+
|
196
|
+
# Check required
|
197
|
+
$options.any? { |o| o.required} and
|
198
|
+
h.puts("", " // Check that required switches are present")
|
199
|
+
$options.each { |o|
|
200
|
+
next unless o.required
|
201
|
+
h.puts(<<EOS)
|
202
|
+
if(!#{o.var}_given)
|
203
|
+
error("[#{o.switches}] required switch");
|
204
|
+
EOS
|
205
|
+
}
|
206
|
+
# Check conflict
|
207
|
+
$options.any? { |o| !o.conflict.empty? } and
|
208
|
+
h.puts("", " // Check mutually exlusive switches")
|
209
|
+
$options.each { |o|
|
210
|
+
o_check = o.var + (o.type == :flag ? "_flag" : "_given")
|
211
|
+
o.conflict.each { |cos|
|
212
|
+
co = $opt_hash[cos]
|
213
|
+
co_check = co.var + (co.type == :flag ? "_flag" : "_given")
|
214
|
+
h.puts(<<EOS)
|
215
|
+
if(#{o_check} && #{co_check})
|
216
|
+
error("Switches [#{o.switches}] and [#{co.switches}] are mutually exclusive");
|
217
|
+
EOS
|
218
|
+
}
|
219
|
+
}
|
220
|
+
# Check at_least
|
221
|
+
$options.any? { |o| o.at_least } and
|
222
|
+
h.puts("", " // Check at_least requirements")
|
223
|
+
$options.each { |o|
|
224
|
+
next unless o.multiple && !o.at_least.nil?
|
225
|
+
h.puts(<<EOS)
|
226
|
+
if(#{o.var}_arg.size() < #{o.at_least})
|
227
|
+
error("[#{o.switches}] must be given at least #{o.at_least} times");
|
228
|
+
EOS
|
229
|
+
}
|
230
|
+
|
231
|
+
# Parse arguments
|
232
|
+
h.puts("", " // Parse arguments")
|
233
|
+
if $args.size == 0 || !$args[-1].multiple
|
234
|
+
h.puts(<<EOS)
|
235
|
+
if(argc - optind != #{$args.size})
|
236
|
+
error("Requires exactly #{$args.size} argument#{$args.size > 1 ? "s" : ""}.");
|
237
|
+
EOS
|
238
|
+
else
|
239
|
+
min_args = $args.size - 1 + $args[-1].at_least
|
240
|
+
h.puts(<<EOS)
|
241
|
+
if(argc - optind < #{min_args})
|
242
|
+
error("Requires at least #{min_args} argument#{min_args > 1 ? "s" : ""}.");
|
243
|
+
EOS
|
244
|
+
end
|
245
|
+
$args.each { |a| h.puts(" " + a.parse_arg.join("\n ")) }
|
246
|
+
|
247
|
+
# Check access rights
|
248
|
+
if ($options + $args).any? { |o| !o.access_types.empty? }
|
249
|
+
r_to_f = { "read" => "R_OK", "write" => "W_OK", "exec" => "X_OK" }
|
250
|
+
h.puts("", " // Check access rights")
|
251
|
+
($args + $options).each { |o|
|
252
|
+
next if o.access_types.empty?
|
253
|
+
mode = o.access_types.map { |t| r_to_f[t] }.join("|")
|
254
|
+
msg = Arg === o ? "Argument " + o.name : "Switch " + o.switches
|
255
|
+
msg += ", access right (#{o.access_types.join("|")}) failed for file '"
|
256
|
+
h.puts(" if(access(#{o.var}_arg, #{mode})) {",
|
257
|
+
" err = \"#{msg}\";",
|
258
|
+
" ((err += #{o.var}_arg) += \"': \") += strerror(errno);",
|
259
|
+
" error(err.c_str());",
|
260
|
+
" }")
|
261
|
+
}
|
262
|
+
end
|
263
|
+
|
264
|
+
h.puts(" }") # close parser
|
265
|
+
|
266
|
+
# Usage
|
267
|
+
if !$usage.nil?
|
268
|
+
ausage = quote_newline_dquotes($usage, " ")
|
269
|
+
else
|
270
|
+
ausage = "Usage: #{$package || class_name} [options]"
|
271
|
+
$args.each { |a|
|
272
|
+
ausage += " #{a.name}:#{a.typestr || dflt_typestr(a.type)}#{a.multiple ? "+" : ""}"
|
273
|
+
}
|
274
|
+
end
|
275
|
+
|
276
|
+
h.puts(<<EOS)
|
277
|
+
static const char * usage() { return "#{ausage}"; }
|
278
|
+
class error {
|
279
|
+
int code_;
|
280
|
+
std::ostringstream msg_;
|
281
|
+
|
282
|
+
// Select the correct version (GNU or XSI) version of
|
283
|
+
// strerror_r. strerror_ behaves like the GNU version of strerror_r,
|
284
|
+
// regardless of which version is provided by the system.
|
285
|
+
static const char* strerror__(char* buf, int res) {
|
286
|
+
return res != -1 ? buf : "Invalid error";
|
287
|
+
}
|
288
|
+
static const char* strerror__(char* buf, char* res) {
|
289
|
+
return res;
|
290
|
+
}
|
291
|
+
static const char* strerror_(int err, char* buf, size_t buflen) {
|
292
|
+
return strerror__(buf, strerror_r(err, buf, buflen));
|
293
|
+
}
|
294
|
+
struct no_t { };
|
295
|
+
|
296
|
+
public:
|
297
|
+
static no_t no;
|
298
|
+
error(int code = EXIT_FAILURE) : code_(code) { }
|
299
|
+
explicit error(const char* msg, int code = EXIT_FAILURE) : code_(code)
|
300
|
+
{ msg_ << msg; }
|
301
|
+
error(const std::string& msg, int code = EXIT_FAILURE) : code_(code)
|
302
|
+
{ msg_ << msg; }
|
303
|
+
error& operator<<(no_t) {
|
304
|
+
char buf[1024];
|
305
|
+
msg_ << ": " << strerror_(errno, buf, sizeof(buf));
|
306
|
+
return *this;
|
307
|
+
}
|
308
|
+
template<typename T>
|
309
|
+
error& operator<<(const T& x) { msg_ << x; return (*this); }
|
310
|
+
~error() {
|
311
|
+
::std::cerr << "Error: " << msg_.str() << "\\n"
|
312
|
+
<< usage() << "\\n"
|
313
|
+
<< "Use --help for more information"
|
314
|
+
<< ::std::endl;
|
315
|
+
exit(code_);
|
316
|
+
}
|
317
|
+
};
|
318
|
+
EOS
|
319
|
+
|
320
|
+
# Help
|
321
|
+
desc = ""
|
322
|
+
unless $purpose.nil?
|
323
|
+
desc += $purpose + "\\n\\n"
|
324
|
+
end
|
325
|
+
unless $description.nil?
|
326
|
+
desc += $description.split(/\n/).join("\\n\" \\\n \"") + "\\n\\n"
|
327
|
+
end
|
328
|
+
|
329
|
+
h.puts(<<EOS)
|
330
|
+
static const char * help() { return
|
331
|
+
"#{desc}"
|
332
|
+
"Options (default value in (), *required):\\n"
|
333
|
+
EOS
|
334
|
+
output_options_descriptions(h, $options, false)
|
335
|
+
usage_switch = " -U, "
|
336
|
+
usage_switch = " " * usage_switch.size if usage_no_U
|
337
|
+
usage_switch += "--usage"
|
338
|
+
h.puts(" \"#{usage_switch.ljust($switchesjust)} Usage\\n\"")
|
339
|
+
help_switch = " -h, "
|
340
|
+
help_switch = " " * help_switch.size if help_no_h
|
341
|
+
help_switch += "--help"
|
342
|
+
h.puts(" \"#{help_switch.ljust($switchesjust)} This message\\n\"")
|
343
|
+
h.puts(" \"#{" --full-help".ljust($switchesjust)} Detailed help\\n\"") if need_full
|
344
|
+
version_switch = " -V, "
|
345
|
+
version_switch = " " * version_switch.size if version_no_V
|
346
|
+
version_switch += "--version"
|
347
|
+
h.print(" \"#{version_switch.ljust($switchesjust)} Version")
|
348
|
+
if $after_text.nil?
|
349
|
+
h.puts("\";")
|
350
|
+
else
|
351
|
+
h.puts("\\n\" \\", " \"\\n\"")
|
352
|
+
atext = quote_newline_dquotes($after_text, " ")
|
353
|
+
h.puts(" \"#{atext}\";")
|
354
|
+
end
|
355
|
+
h.puts(" }")
|
356
|
+
|
357
|
+
# Hidden help
|
358
|
+
has_hidden = $options.any? { |o| o.hidden }
|
359
|
+
if has_hidden
|
360
|
+
h.puts(<<EOS)
|
361
|
+
static const char* hidden() { return
|
362
|
+
"Hidden options:\\n"
|
363
|
+
EOS
|
364
|
+
output_options_descriptions(h, $options, true)
|
365
|
+
h.puts(<<EOS)
|
366
|
+
"";
|
367
|
+
}
|
368
|
+
EOS
|
369
|
+
else
|
370
|
+
h.puts(<<EOS)
|
371
|
+
static const char* hidden() { return ""; }
|
372
|
+
EOS
|
373
|
+
end
|
374
|
+
|
375
|
+
|
376
|
+
# Version
|
377
|
+
h.puts(" void print_version(::std::ostream &os = std::cout) const {",
|
378
|
+
"#ifndef PACKAGE_VERSION",
|
379
|
+
"#define PACKAGE_VERSION \"0.0.0\"",
|
380
|
+
"#endif",
|
381
|
+
" os << #{$version ? "\"" + $version + "\"" : "PACKAGE_VERSION"} << \"\\n\";",
|
382
|
+
" }")
|
383
|
+
|
384
|
+
# Dump
|
385
|
+
h.puts(" void dump(::std::ostream &os = std::cout) {")
|
386
|
+
($options + $args).each { |o| h.puts(" os << #{o.dump.join(" << ")} << \"\\n\";") }
|
387
|
+
h.puts(" }")
|
388
|
+
|
389
|
+
# Private methods
|
390
|
+
h.puts(<<EOS)
|
391
|
+
};
|
392
|
+
EOS
|
393
|
+
|
394
|
+
# Initialize static members
|
395
|
+
# TODO: Should we have an option to put this in a .cc file?
|
396
|
+
$options.each { |o|
|
397
|
+
next unless o.type == :enum
|
398
|
+
h.puts("const char* const #{class_name}::#{o.var}::strs[#{o.enum.size + 1}] = { #{o.enum.map { |x| "\"#{x}\"" }.join(", ") }, (const char*)0 };")
|
399
|
+
}
|
400
|
+
|
401
|
+
h.puts(<<EOS)
|
402
|
+
#endif // __#{class_name.upcase}_HPP__"
|
403
|
+
EOS
|
404
|
+
end
|