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.
@@ -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
@@ -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