Top Level Namespace

Defined Under Namespace

Classes: Arg, BaseOptArg, NoTarget, Option

Instance Method Summary collapse

Instance Method Details

#access(*types) ⇒ Object



77
# File 'lib/yaggo/dsl.rb', line 77

def access *types; $target.access= types; end

#arg(name, &b) ⇒ Object



559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'lib/yaggo/dsl.rb', line 559

def arg(name, &b)
  a = Arg.new(name)
  $args.any? { |la| la.name == name } and
    raise "#{b.source_location.join(":")}: Arg '#{name}' already exists"
  $args << a
  $target = a
  name = "Arg #{name}"
  run_block(name, b)
  $target = NoTarget.new
  begin
    a.check
  rescue => e
    raise "#{b.source_location.join(":")}: #{e.message}"
  end
end

#at_least(n) ⇒ Object



74
# File 'lib/yaggo/dsl.rb', line 74

def at_least n; $target.at_least = n; end

#c_stringObject



60
# File 'lib/yaggo/dsl.rb', line 60

def c_string; $target.type = :c_string; end

#check_conflict_excludeObject



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/yaggo/general.rb', line 108

def check_conflict_exclude
  $options.each { |o|
    $opt_hash[o.long] = o unless o.long.nil?
    $opt_hash[o.short] = o unless o.short.nil?
  }
  $options.each { |o|
    o.conflict.each { |co|
      $opt_hash[co] or 
      raise "Unknown conflict option '#{co}' for switch #{o.long}|#{o.short}"
    }
  }
  $options.each { |o|
    o.imply.each { |ios|
      io = $opt_hash[ios] or
      raise "Unknown implied option '#{io}' for switch #{o.long}|#{o.short}"
      io.type == :flag or
      raise "Implied option '#{io}' for switch #{o.long}|#{o.short} is not a flag"
    }
  }
end

#conflict(*a) ⇒ Object



75
# File 'lib/yaggo/dsl.rb', line 75

def conflict *a; $target.conflict= a; end

#default(str) ⇒ Object



71
# File 'lib/yaggo/dsl.rb', line 71

def default str; $target.default = str; end

#default_val(val, type, *argv) ⇒ Object



84
85
86
87
88
89
90
91
92
93
# File 'lib/yaggo/dsl.rb', line 84

def default_val(val, type, *argv)
  case type
  when :string, :c_string
    "\"#{val || $type_default[type]}\""
  when :uint32, :uint64, :int32, :int64, :int, :long, :double
    val ? "(#{$type_to_C_type[type]})#{val}" : $type_default[type]
  else
    val.to_s || $type_default[type]
  end
end

#description(str) ⇒ Object



39
# File 'lib/yaggo/dsl.rb', line 39

def description str; $target.description = str; end

#dflt_typestr(type, *argv) ⇒ Object



45
46
47
48
49
50
51
52
53
54
# File 'lib/yaggo/general.rb', line 45

def dflt_typestr(type, *argv)
  case type
  when :c_string
    "string"
  when :enum
    argv[0].join("|")
  else
    type.to_s
  end
end

#display_man_page(out) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/yaggo/man_page.rb', line 18

def display_man_page out
  manual = <<EOS
.TH yaggo 1  "2015-06-24" "version #{$yaggo_version}" "USER COMMANDS"

.SH NAME
yaggo \- command line switch parser generator

.SH SYNOPSIS
.B yaggo
[-o|--output FILE] [-l|--license PATH] [-s|--stub] [--zc PATH] [-e|--extended-syntax] [--man] [-h|--help]

.SH DESCRIPTION
Yaggo stands for Yet Another GenGetOpt. It is inspired by gengetopt
software from the FSF.

Yaggo generates a C++ class to parse command line switches (usually
argc and argv passed to main) using getopt_long. The switches and
arguments to the program are specified in a description file. To each
description file, yaggo generates one C++ header file containing the
parsing code.
.PP
See the EXAMPLES section for a complete and simple example.

.SH OPTIONS
.TP
\-l|\-\-license
Display the file at the top of the generated headers. It usually
contains the license governing the distribution of the headers.
.TP
-m|\-\-man
Display this man page
.TP
\-s|\-\-stub
Generate a stub: a simple yaggo file that can be modified for one's use.
.TP
\-e|--extended-syntax
Use the extended syntax: blocks can be defined on the next line of a command.
.TP
\-h|--help
Display a short help text
.PP

.SH EXAMPLE

Consider the description files 'example_args.yaggo' which defines a
switch "-i" (or "--int") that takes an unsigned integer and defaults
to 42; a switch "-s" (or "--string") that takes a string and can be
given multiple times; a switch "--flag" which does not take any
argument; a switch "--severity" which can take only 3 values: "low",
"middle" and "high".

It takes the following arguments: a string followed by zero or more floating point numbers.

.nf
purpose "Example of yaggo usage"
package "example"
description "This is just an example.
And a multi-line description."

option("int", "i") {
  description "Integer switch"
  uint32; default "42" }
option("string", "s") {
  description "Many strings"
  string; multiple }
option("flag") {
  description "A flag switch"
  flag; off }
option("severity") {
  description "An enum switch"
  enum "low", "middle", "high" }
arg("first") {
  description "First arg"
  c_string }
arg("rest") {
  description "Rest of'em"
  double; multiple }
.fi

The associated simple C++ program 'examples.cpp' which display information about the switches and arguments passed:

.nf
#include <iostream>
#include "example_args.hpp"

int main(int argc, char *argv[]) {
  example_args args(argc, argv);

  std::cout << "Integer switch: " << args.int_arg << "\\\\n";
  if(args.string_given)
    std::cout << "Number of string(s): " << args.string_arg.size() << "\\\\n";
  else
    std::cout << "No string switch\\\\n";
  std::cout << "Flag is " << (args.flag_flag ? "on" : "off") << "\\\\n";
  std::cout << "First arg: " << args.first_arg << "\\\\n";
  std::cout << "Severity arg: " << args.severity_arg << " " << example_args::severity::strs[args.severity_arg] << "\\\\n";
  if(args.severity_arg == example_args::severity::high)
    std::cout << "Warning: severity is high\\\\n";
  std::cout << "Rest:";
  for(example_args::rest_arg_it it = args.rest_arg.begin(); it != args.rest_arg.end(); ++it)
    std::cout << " " << *it;
  std::cout << std::endl;

  return 0;
}
.fi

This can be compiled with the following commands:

.nf
% yaggo example_args.yaggo
% g++ -o example example.cpp
.fi

The yaggo command above will create by default the file
'example_args.hpp' (changed '.yaggo' extension to '.hpp'). The output
file name can be changed with the 'output' keyword explained below.

.SH DESCRIPTION FORMAT

A description file is a sequence of statements. A statement is a
keyword followed by some arguments. Strings must be surrounded by
quotes ("" or '') and can span multiple lines. The order of the
statements is irrelevant. Statements are separated by new lines or
semi-colons ';'.

.IP *
Technically speaking, yaggo is implemented as a DSL (Domain Specific
Language) using ruby. The description file is a valid ruby script and
the keywords are ruby functions.
.PP

The following statements are global, not attached to a particular option or argument.

.TP
purpose
A one line description of the program.
.TP
package
The name of the package for the usage string. Defaults to the name of the class.
.TP
usage
The usage string. If none given a standard one is generated by yaggo.
.TP
description
A longer description of the program displayed before the list of switch. Displayed by the help.
.TP
text
Some text to be displayed after the list of switches. Displayed by the help.
.TP
version
The version string of the software.
.TP
license
The license and copyright string of the software.
.TP
name
The name of the class generated. Defaults to the name of the
description file minus the .yaggo extension.
.TP
posix
Posix correct behavior (instead of GNU behavior): switch processing
stops at the first non-option argument
.TP
output
The name of the output file. Defaults to the name of the
description file with the .yaggo extension changed to .hpp.
.PP

The 'option' statement takes one or two arguments, which must be in
parentheses, and a block of statements surrounded by curly braces
({...}). The arguments are the long and short version of the
option. Either one of the long or short version can be omitted. The
block of statements describe the option in more details, as described
below.

A switch is named after the long version, or the short version if no
long version. An 'option' statement for an option named 'switch'
defines one or two public members in the class. For a flag, it
creates 'switch_flag' as a boolean. Otherwise, it
creates 'switch_arg', with a type as specified, and 'switch_given', a
boolean indicating whether or not the switch was given on the command
line.

For example, the statement:

.nf
option("integer", "i") {
  int; default 5
}
.fi

will add the following members to the C++ class:

.nf
int integer_arg;
bool integer_given;
.fi

where "integer_arg" is initialized to 5 and "integer_given" is
initialized to "false". If the switch "--integer 10" or "-i 10" is
passed on the command line "integer_arg" is set to 10 and
integer_given is set to "true".

The statement:

.nf
option("verbose") {
  off
}
.fi

will add the following member to the C++ class:

.nf
bool verbose_flag;
.fi

where "verbose_flag" is initialized to "false". Passing the switch
"--verbose" on the command line sets "verbose_flag" to true".


In addition to the switch created by 'option', the following switches
are defined by default (unless some option statement overrides them):

.TP
\-h, \-\-help
Display the help message.
.TP
\-\-full\-help
Display hidden options as well.
.TP
\-\-version
Display version string.
.PP

The following statement are recognized in an option block:

.TP
description "str"
A short description for this switch.

.TP
int32, int64, uint32, uint64, double, int, long
This switch is parsed as a number with the corresponding type int32_t,
int64_t, uint32_t, uint64_t, double, int and long.

.TP
suffix
Valid for numerical type switches as above. It can be appended
with a SI suffix (e.g. 1M mean 1000000). The suffixes k, M, G, T, P,
and E are supported for all the numerical types. The suffixes m, u, n,
p, f, and a are supported for the double type.

.TP
c_string, string
This switch is taken as a C string (const char *) or a C++ string
(inherits from std::string). The C++ string type has the extra
methods '<type> as_<type>(bool suffix)', where <type> is any numerical
type as above, to convert the string into that type. If the 'suffix'
boolean is true, parsing is done using SI suffixes.

.TP
enum
This statement must be followed by a comma separated list of strings
(as in 'enum "choice0", "choice1", "choice2"'). This switch takes value
a string in the list and is converted to int. C enum type named
"switchname::enum" is defined with the same choices in the given order.

.TP
required
This switch is required. An error is generated if not given on the
command line.
.TP
conflict
Specify a comma separated list of switches that conflicts with this
one.
.TP
imply
Specify a comma separated list of switches (of type flag) which are
implied by this one.
.TP
hidden
This switch is not shown with --help. Use --full-help to see the
hidden switches, if any.
.TP
secret
This switch is not shown in any help message. Neither --help nor
--full-help.
.TP
multiple
This switch can be passed multiple times. The values are stored in a
std::vector. A type for the iterator is also defined in the class with
the name 'switch_arg_it', where 'switch' is the name of the option.
.TP
flag
This switch is a flag and does not take an argument.
.TP
on, off
The default state for a flag switch. Implies flag. Unless the 'no'
option is used (see below), with 'off', the default value of the flag
is "false" and passing --flag sets it to true. With 'on', the default
value of the flag is "true" and passing --flag sets it to false.
.TP
no
A flag with two switches. If the switch is named "flag", two switches
are generated: --flag and --noflag, respectively setting it to "true"
and "false". The 'on' and 'off' options define the default value.
.TP
default "val"
The default value for this switch. It can be a string or a valid
number. SI suffixes are supported as well (for example "1M" means 1
m`illion).
.TP
typestr "str"
In the help message, by default, the type of the option is
displayed. It can be replaced by the string given to 'typestr'.
.TP
at_least n
The given switch must be given at least n times. Implies multiple.
.TP
access "type"
Make sure that the string passed is a path to which we have
access. "type" is a comma separated list of "read", "write" or
"exec". It is checked with access(2). The same warning applies:

"Warning: Using access() to check if a user is authorized to, for
example, open a file before actually doing so using open(2) creates a
security hole, because the user might exploit the short time interval
between checking and opening the file to manipulate it.  For this
reason, the use of this system call should be avoided.  (In the
example just described, a safer alternative would be to temporarily
switch the process's effective user ID to the real ID and then call
open(2).)"

.PP

A 'arg' statement defines an arg passed to the command line. The
statement takes a single argument, the name of the arg, and a block of
statements. The block of statements are similar to the option block,
except that "hidden", "flag", "on", "off" and "no" are not allowed. At
most one arg can have the 'multiple' statement, and it must be the
last one.

.SH EXAMPLE USAGE

The argument object parses the switches on construction or later on
using the parse method. For example, the two pieces code show these
two different usage.

Using parse method:
.nf
  example_args args; // Global variable with switches

  int main(int argc, char* argv[]) {
    args.parse(argc, argv);
  }
.fi

Parse on construction:
.nf
  int main(int argc, char* argv[]) {
    example_args args(argc, argv);
  }
.fi

The subclass error can be used to output error messsage (and terminate
program). It output an error message, the usage string, etc. The error
class behave like an output stream, it can be used to create
complicated error message. For example:

.nf
  if(false_condition)
    example_args::error() << "Failed to open file '" << args.file_arg << "'";
.fi

An error object prints an error message and terminate the program with
exit upon destruction. An exit code can be passed to error. By default
the exit code (passed to exit) is the constant EXIT_FAILURE (normally
1). For example:

.nf
  example_args::error(77) << "Failed with return code 77";
.fi

.SH LICENSE

There are 2 parts to the software: the yaggo ruby script itself, and
the header files generated by yaggo from the description files. The
licenses are as follow:

.TP
yaggo the ruby script
This software is licensed under the GNU General
Public License version 3 or any later version. Copyright (c) 2011
Guillaume Marcais.

.TP The generated header files.  These files have the license and
copyright that you, the user of yaggo, assign with the 'license'
keyword.  .PP In short: only yaggo the software is GPL. The generated
header files are considered derivative of your work (e.g. the
description), and you define the copyright and license of those as you
see fit.

.SH BUGS
.IP *
The error message returned by ruby can be a little confusing.

.SH AUTHOR
Guillaume Marcais ([email protected])
.SH SEE ALSO
getopt_long(3), gengetopt(1), exit(2)
EOS

  if !out && STDOUT.isatty
    require 'tempfile'
    Tempfile.open("yaggo_man") do |fd|
      begin
        fd.write(manual)
        fd.flush
        system("man", fd.path)
      ensure
        fd.unlink
      end
    end
  elsif !out
    STDOUT.puts(manual)
  else
    path = Pathname.new(out)
    path.write manual
  end
end

#display_stub_yaggo_file(file) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/yaggo/stub.rb', line 17

def display_stub_yaggo_file file
  stub = <<EOS
# Stub file generated by yaggo. Modify to your liking
purpose = "Foo software to do bar and baz, one line description"
description = "A longer multiline description of how Foo does bar and baz

Really, it works great, you should try all the options below
"

option("b", "bar") {
  description "Insist on bar"
  flag }
option("z", "baz") {
  description "Baz parameter"
  int64; default "5" }
option("l", "long") {
  description "Long switch can be used multiple time"
  int32; multiple }
arg("OneArg") {
  description "first arg"
  string }
EOS

  out = file ? open(file, "W") : STDOUT
  out.write(stub)
end

#doubleObject



58
# File 'lib/yaggo/dsl.rb', line 58

def double;   $target.type = :double; end

#enum(*argv) ⇒ Object



62
# File 'lib/yaggo/dsl.rb', line 62

def enum(*argv); $target.type = :enum; $target.enum = argv; end

#find_error_header(bt) ⇒ Object



87
88
89
90
# File 'lib/yaggo/general.rb', line 87

def find_error_header bt
  bt.each { |l| l =~ /^\(eval\):\d+:/ and return $& }
  return ""
end

#flagObject



61
# File 'lib/yaggo/dsl.rb', line 61

def flag;     $target.type = :flag; end

#hiddenObject



66
# File 'lib/yaggo/dsl.rb', line 66

def hidden; $target.hidden = true; end

#imply(*a) ⇒ Object



76
# File 'lib/yaggo/dsl.rb', line 76

def imply *a; $target.imply= a; end

#intObject



56
# File 'lib/yaggo/dsl.rb', line 56

def int;      $target.type = :int; end

#int32Object

def set_type t

raise "More than 1 type specified: '#{$target.type}' and '#{t}'" unless $target.type.nil?
$target.type = t

end



52
# File 'lib/yaggo/dsl.rb', line 52

def int32;    $target.type = :int32; end

#int64Object



53
# File 'lib/yaggo/dsl.rb', line 53

def int64;    $target.type = :int64; end

#license(str) ⇒ Object



42
# File 'lib/yaggo/dsl.rb', line 42

def license str; $license = str; end

#longObject



57
# File 'lib/yaggo/dsl.rb', line 57

def long;     $target.type = :long; end

#mainObject



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/yaggo/main.rb', line 27

def main
  $yaggo_options = {
    :output => nil,
    :license => nil,
    :stub => false,
    :zc => nil,
    :extended => false,
    :debug => false,
  }

  parser = OptionParser.new do |o|
    o.version = $yaggo_version
    o.banner = "Usage: #{$0} [options] [file.yaggo]"
    o.separator ""
    o.separator "Specific options:"

    o.on("-o", "--output FILE", "Output file") { |v|
      $yaggo_options[:output] = v
    }
    o.on("-l", "--license PATH", "License file to copy in header") { |v|
      $yaggo_options[:license] = v
    }
    o.on("-m", "--man [FILE]", "Display or write manpage") { |v|
      display_man_page v
      exit 0;
    }
    o.on("-s", "--stub", "Output a stub yaggo file") {
      $yaggo_options[:stub] = true
    }
    o.on("--zc PATH", "Write zsh completion file") { |v|
      $yaggo_options[:zc] = v
    }
    o.on("-e", "--extended-syntax", "Use extended syntax") {
      $yaggo_options[:extended] = true
    }
    o.on("--debug", "Debug yaggo") {
      $yaggo_options[:debug] = true
    }

    o.on_tail("-h", "--help", "Show this message") {
      puts o
      exit 0
    }
  end
  parser.parse! ARGV

  if $yaggo_options[:stub]
    begin
      display_stub_yaggo_file $yaggo_options[:output]
    rescue => e
      STDERR.puts("Failed to write stub: #{e.message}")
      exit 1
    end

    exit
  end

  if !$yaggo_options[:stub] && !$yaggo_options[:manual] && ARGV.empty?
    STDERR.puts "Error: some yaggo files and/or --lib switch is required", parser
    exit 1
  end
  if !$yaggo_options[:output].nil?
    if $yaggo_options[:stub]
      if ARGV.size > 0
        STDERR.puts "Error: no input file needed with the --stub switch", parser
        exit 1
      end
    elsif ARGV.size != 1
      STDERR.puts "Error: output switch meaningfull only with 1 input file", parser
      exit 1
    end
  end

  ARGV.each do |input_file|
    pid = fork do
      begin
        yaggo_script = File.read(input_file)
        if $yaggo_options[:extended]
          yaggo_script.gsub!(/\)\s*\n\s*\{/, ") {")
        end
        eval(File.read(input_file))
        parsed = true
        check_conflict_exclude
      rescue RuntimeError, SyntaxError, Errno::ENOENT, Errno::EACCES => e
        raise e if $yaggo_options[:debug]
        STDERR.puts(e.message.gsub(/^\(eval\)/, input_file))
        exit 1
      rescue NoMethodError => e
        raise e if $yaggo_options[:debug]
        STDERR.puts("Invalid keyword '#{e.name}'")
        exit 1
      end

      fsplit    = File.basename(input_file).split(/\./)
      $klass  ||= fsplit.size > 1 ? fsplit[0..-2].join(".") : fsplit[0]
      $output   = $yaggo_options[:output] if $yaggo_options[:output]
      $output ||= input_file.gsub(/\.yaggo$/, "") + ".hpp"
      
      begin
        out_fd = open($output, "w")
        output_cpp_parser(out_fd, $klass)
      rescue RuntimeError => e
        raise e if $yaggo_options[:debug]
        STDERR.puts("#{input_file}: #{e.message}")
        exit 1
      ensure
        out_fd.close if out_fd
      end

      if $yaggo_options[:zc]
        begin
          out_fd = open($yaggo_options[:zc], "w")
          output_zsh_completion(out_fd, $yaggo_options[:zc])
        rescue RuntimeError => e
          raise e if $yaggo_options[:debug]
          STDERR.puts("#{input_file}: #{e.message}")
          exit 1
        ensure
          out_fd.close if out_fd
        end
      end
    end
    Process.waitpid pid
    exit 1 if !$?.exited? || ($?.exited? && $?.exitstatus != 0)
  end
end

#multipleObject



73
# File 'lib/yaggo/dsl.rb', line 73

def multiple; $target.multiple = true; end

#name(str) ⇒ Object



34
# File 'lib/yaggo/dsl.rb', line 34

def name str; $klass = str; end

#noObject



70
# File 'lib/yaggo/dsl.rb', line 70

def no; $target.no; end

#offObject



69
# File 'lib/yaggo/dsl.rb', line 69

def off; $target.off; end

#onObject



68
# File 'lib/yaggo/dsl.rb', line 68

def on; $target.on; end

#option(name1, name2 = nil, &b) ⇒ Object



529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
# File 'lib/yaggo/dsl.rb', line 529

def option(name1, name2 = nil, &b)
  long = short = nil
  if name1 =~ /^--/ || name1.length >= 2
    long, short = name1, name2
  elsif !name2.nil? && (name2 =~ /^--/ || name2.length >= 2)
    long, short = name2, name1
  else
    long, short = nil, name1
  end

  long.gsub!(/^--/, "") unless long.nil?
  short.gsub!(/^-/, "") unless short.nil?
  o = Option.new(long, short)
  $options.each { |lo| 
    if (!long.nil? && lo.long == long) || (!short.nil? && lo.short == short)
      raise "#{b.source_location.join(":")}: Option #{long}|#{short} conflicts with existing option #{lo.long}|#{lo.short}"
    end
  }
  $options << o
  $target = o
  name  = "Option #{long || ""}|#{short || ""}"
  run_block(name, b)
  $target = NoTarget.new
  begin
    o.check
  rescue => e
    raise "#{b.source_location.join(":")}: #{e.message}"
  end
end

#output(str) ⇒ Object



33
# File 'lib/yaggo/dsl.rb', line 33

def output str; $output = str; end

#output_conversion_code(file) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/yaggo/library.rb', line 17

def output_conversion_code file
  file.puts(<<EOS)
  static bool adjust_double_si_suffix(double &res, const char *suffix) {
if(*suffix == '\\0')
  return true;
if(*(suffix + 1) != '\\0')
  return false;

switch(*suffix) {
case 'a': res *= 1e-18; break;
case 'f': res *= 1e-15; break;
case 'p': res *= 1e-12; break;
case 'n': res *= 1e-9;  break;
case 'u': res *= 1e-6;  break;
case 'm': res *= 1e-3;  break;
case 'k': res *= 1e3;   break;
case 'M': res *= 1e6;   break;
case 'G': res *= 1e9;   break;
case 'T': res *= 1e12;  break;
case 'P': res *= 1e15;  break;
case 'E': res *= 1e18;  break;
default: return false;
}
return true;
  }

  static double conv_double(const char *str, ::std::string &err, bool si_suffix) {
char *endptr = 0;
errno = 0;
double res = strtod(str, &endptr);
if(endptr == str) {
  err.assign("Invalid floating point string");
  return (double)0.0;
}
if(errno) {
  err.assign(strerror(errno));
  return (double)0.0;
}
bool invalid =
  si_suffix ? !adjust_double_si_suffix(res, endptr) : *endptr != '\\0';
if(invalid) {
  err.assign("Invalid character");
  return (double)0.0;
}
return res;
  }

  static int conv_enum(const char* str, ::std::string& err, const char* const strs[]) {
int res = 0;
for(const char* const* cstr = strs; *cstr; ++cstr, ++res)
  if(!strcmp(*cstr, str))
    return res;
err += "Invalid constant '";
err += str;
err += "'. Expected one of { ";
for(const char* const* cstr = strs; *cstr; ++cstr) {
  if(cstr != strs)
    err += ", ";
  err += *cstr;
}
err += " }";
return -1;
  }

  template<typename T>
  static bool adjust_int_si_suffix(T &res, const char *suffix) {
if(*suffix == '\\0')
  return true;
if(*(suffix + 1) != '\\0')
  return false;

switch(*suffix) {
case 'k': res *= (T)1000; break;
case 'M': res *= (T)1000000; break;
case 'G': res *= (T)1000000000; break;
case 'T': res *= (T)1000000000000; break;
case 'P': res *= (T)1000000000000000; break;
case 'E': res *= (T)1000000000000000000; break;
default: return false;
}
return true;
  }

  template<typename T>
  static T conv_int(const char *str, ::std::string &err, bool si_suffix) {
char *endptr = 0;
errno = 0;
long long int res = strtoll(str, &endptr, 0);
if(endptr == str) {
  err.assign("Invalid signed int string");
  return (T)0;
}
if(errno) {
  err.assign(strerror(errno));
  return (T)0;
}
bool invalid =
  si_suffix ? !adjust_int_si_suffix(res, endptr) : *endptr != '\\0';
if(invalid) {
  err.assign("Invalid character");
  return (T)0;
}
if(res > ::std::numeric_limits<T>::max() ||
   res < ::std::numeric_limits<T>::min()) {
  err.assign("Value out of range");
  return (T)0;
}
return (T)res;
  }

  template<typename T>
  static T conv_uint(const char *str, ::std::string &err, bool si_suffix) {
char *endptr = 0;
errno = 0;
while(isspace(*str)) { ++str; }
if(*str == '-') {
  err.assign("Negative value");
  return (T)0;
}
unsigned long long int res = strtoull(str, &endptr, 0);
if(endptr == str) {
  err.assign("Invalid unsigned int string");
  return (T)0;
}
if(errno) {
  err.assign(strerror(errno));
  return (T)0;
}
bool invalid =
  si_suffix ? !adjust_int_si_suffix(res, endptr) : *endptr != '\\0';
if(invalid) {
  err.assign("Invalid character");
  return (T)0;
}
if(res > ::std::numeric_limits<T>::max()) {
  err.assign("Value out of range");
  return (T)0;
}
return (T)res;
  }

  template<typename T>
  static ::std::string vec_str(const std::vector<T> &vec) {
::std::ostringstream os;
for(typename ::std::vector<T>::const_iterator it = vec.begin();
    it != vec.end(); ++it) {
  if(it != vec.begin())
    os << ",";
  os << *it;
}
return os.str();
  }

  class string : public ::std::string {
  public:
string() : ::std::string() {}
explicit string(const ::std::string &s) : std::string(s) {}
explicit string(const char *s) : ::std::string(s) {}
int as_enum(const char* const strs[]) {
  ::std::string err;
  int res = #{str_conv("this->c_str()", :enum, "strs")};
  if(!err.empty())
    throw ::std::runtime_error(err);
  return res;
}


EOS
  [:uint32, :uint64, :int32, :int64, :int, :long, :double].each do |type|
  file.puts(<<EOS)
#{$type_to_C_type[type]} as_#{type}_suffix() const { return as_#{type}(true); }
#{$type_to_C_type[type]} as_#{type}(bool si_suffix = false) const {
  ::std::string err;
  #{$type_to_C_type[type]} res = #{str_conv("this->c_str()", type, "si_suffix")};
  if(!err.empty()) {
    ::std::string msg("Invalid conversion of '");
    msg += *this;
    msg += "' to #{type}_t: ";
    msg += err;
    throw ::std::runtime_error(msg);
  }
  return res;
}
EOS
  end

  file.puts(<<EOS)
  };

EOS
# }
end

#output_cpp_parser(h, class_name) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/yaggo/parser.rb', line 34

def output_cpp_parser(h, class_name)
  $options.each { |o| o.check }
  $args.each { |a| a.check }
  if $args.size > 1
    mul_args = $args[0..-2].select { |a| a.multiple }
    if mul_args.size > 0
      gram = mul_args.size > 1 ? "s are" : " is"
      raise "The following#{gram} not the last arg but marked multiple: #{mul_args.map { |a| a.name }.join(", ")}"
    end
  end

  # Headers

  h.puts(<<EOS)
/***** This code was generated by Yaggo. Do not edit ******/

EOS

  if $license
    lines = $license.split(/\n/)
    h.puts("/* #{lines[0]}", *(lines[1..-1].map { |l| " * " + l }))
    h.puts(" */", "")
  elsif $yaggo_options[:license]
    open($yaggo_options[:license]) { |fd|
      h.puts(fd.read)
    }
    h.puts("")
  end

h.puts(<<EOS)
#ifndef __#{class_name.upcase()}_HPP__
#define __#{class_name.upcase()}_HPP__

#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <errno.h>
#include <string.h>
#include <stdexcept>
#include <string>
#include <limits>
#include <vector>
#include <iostream>
#include <sstream>
#include <memory>

class #{class_name} {
 // Boiler plate stuff. Conversion from string to other formats
EOS

  output_conversion_code h

  h.puts(<<EOS)
public:
EOS

  static_decl = $options.map { |o| o.static_decl }.flatten
  h.puts("  " + static_decl.join("\n  "), "") unless static_decl.empty?

  ($options + $args).each { |o| h.puts("  " + o.var_decl.join("\n  ")) }
  h.puts("")

  # Create enum if option with no short version
  only_long = $options.map { |o| o.long_enum }.flatten.compact
  need_full = $options.any? { |o| o.hidden }

  help_no_h = $options.any? { |o| o.short == "h" }
  version_no_V = $options.any? { |o| o.short == "V" }
  usage_no_U = $options.any? { |o| o.short == "U" }
  h.print("  enum {\n    START_OPT = 1000")
  h.print(",\n    FULL_HELP_OPT") if need_full
  h.print(",\n    HELP_OPT") if help_no_h
  h.print(",\n    VERSION_OPT") if version_no_V
  h.print(",\n    USAGE_OPT") if usage_no_U
  if only_long.empty?
    h.puts("\n  };")
  else
    h.puts(",", "    " + only_long.join(",\n    "), "  };")
  end

  # Constructors and initialization
  h.puts("", "  #{class_name}() :")
  h.puts("    " + ($options + $args).map { |o| o.init }.join(",\n    "), "  { }")
  h.puts("", "  #{class_name}(int argc, char* argv[]) :")
  h.puts("    " + ($options + $args).map { |o| o.init }.join(",\n    "))
  h.puts("  { parse(argc, argv); }", "");

  # Main arsing function
  h.puts("  void parse(int argc, char* argv[]) {",
         "    static struct option long_options[] = {")
  $options.empty? or
    h.puts("      " + $options.map { |o| o.struct }.flatten.join(",\n      ") + ",")
  h.puts("      {\"help\", 0, 0, #{help_no_h ? "HELP_OPT" : "'h'"}},")
  h.puts("      {\"full-help\", 0, 0, FULL_HELP_OPT},") if need_full
  h.puts("      {\"usage\", 0, 0, #{usage_no_U ? "USAGE_OPT" : "'U'"}},",
         "      {\"version\", 0, 0, #{version_no_V ? "VERSION_OPT" : "'V'"}},",
         "      {0, 0, 0, 0}", "    };")
  short_str = $posix ? "+" : ""
  short_str += "h" unless help_no_h
  short_str += "V" unless version_no_V
  short_str += "U" unless usage_no_U
  short_str += $options.map { |o| o.short_str }.compact.join("")
  
  h.puts("    static const char *short_options = \"#{short_str}\";", "")

  need_err   = $options.any? { |o| o.type != :flag && o.type != :string && o.type != :c_string}
  need_err ||= $args.any? { |a| a.type != :string && a.type != :c_string }
  need_err ||= ($options + $args).any? { |o| !o.access_types.empty? }
  h.puts("    ::std::string err;") if need_err

  # Actual parsing
  h.puts(<<EOS)
#define CHECK_ERR(type,val,which) if(!err.empty()) { ::std::cerr << "Invalid " #type " '" << val << "' for [" which "]: " << err << "\\n"; exit(1); }
    while(true) {
      int index = -1;
      int c = getopt_long(argc, argv, short_options, long_options, &index);
      if(c == -1) break;
      switch(c) {
      case ':':
        ::std::cerr << \"Missing required argument for \"
                  << (index == -1 ? ::std::string(1, (char)optopt) : std::string(long_options[index].name))
                  << ::std::endl;
        exit(1);
      case #{help_no_h ? "HELP_OPT" : "'h'"}:
        ::std::cout << usage() << \"\\n\\n\" << help() << std::endl;
        exit(0);
      case #{usage_no_U ? "USAGE_OPT" : "'U'"}:
        ::std::cout << usage() << \"\\nUse --help for more information.\" << std::endl;
        exit(0);
      case 'V':
        print_version();
        exit(0);
      case '?':
        ::std::cerr << \"Use --usage or --help for some help\\n\";
        exit(1);
EOS
  if need_full
    h.puts(<<EOS)
      case FULL_HELP_OPT:
        ::std::cout << usage() << \"\\n\\n\" << help() << \"\\n\\n\" << hidden() << std::flush;
        exit(0);
EOS
  end
  
  $options.each { |o|
    if o.type == :flag && o.noflag
      h.puts("      case #{o.long_enum[0]}:",
             "        " + o.parse_arg.join("\n        "),
             "        break;",
             "      case #{o.long_enum[1]}:",
             "        " + o.parse_arg(true).join("\n        "),
             "        break;")
    else
      h.puts("      case #{o.long_enum ? o.long_enum[0] : "'" + o.short + "'"}:",
             "        " + o.parse_arg.join("\n        "),
             "        break;")
    end
  }
  h.puts("      }", # close case
         "    }") # close while(true)

  # Check required
  $options.any? { |o| o.required} and
    h.puts("", "    // Check that required switches are present")
  $options.each { |o|
    next unless o.required
    h.puts(<<EOS)
    if(!#{o.var}_given)
      error("[#{o.switches}] required switch");
EOS
  }
  # Check conflict
  $options.any? { |o| !o.conflict.empty? } and
    h.puts("", "    // Check mutually exlusive switches")
  $options.each { |o|
    o_check = o.var + (o.type == :flag ? "_flag" : "_given")
    o.conflict.each { |cos|
      co = $opt_hash[cos]
      co_check = co.var + (co.type == :flag ? "_flag" : "_given")
      h.puts(<<EOS)
    if(#{o_check} && #{co_check})
      error("Switches [#{o.switches}] and [#{co.switches}] are mutually exclusive");
EOS
    }
  }
  # Check at_least
  $options.any? { |o| o.at_least } and
    h.puts("", "    // Check at_least requirements")
  $options.each { |o|
    next unless o.multiple && !o.at_least.nil?
    h.puts(<<EOS)
    if(#{o.var}_arg.size() < #{o.at_least})
      error("[#{o.switches}] must be given at least #{o.at_least} times");
EOS
  }
  
  # Parse arguments
  h.puts("", "    // Parse arguments")
  if $args.size == 0 || !$args[-1].multiple
    h.puts(<<EOS)
    if(argc - optind != #{$args.size})
      error("Requires exactly #{$args.size} argument#{$args.size > 1 ? "s" : ""}.");
EOS
  else
    min_args = $args.size - 1 + $args[-1].at_least
    h.puts(<<EOS)
    if(argc - optind < #{min_args})
      error("Requires at least #{min_args} argument#{min_args > 1 ? "s" : ""}.");
EOS
  end
  $args.each { |a| h.puts("    " + a.parse_arg.join("\n    ")) }

  # Check access rights
  if ($options + $args).any? { |o| !o.access_types.empty? }
    r_to_f = { "read" => "R_OK", "write" => "W_OK", "exec" => "X_OK" }
    h.puts("", "    // Check access rights")
    ($args + $options).each { |o|
      next if o.access_types.empty?
      mode = o.access_types.map { |t| r_to_f[t] }.join("|")
      msg = Arg === o ? "Argument " + o.name : "Switch " + o.switches
      msg += ", access right (#{o.access_types.join("|")}) failed for file '"
      h.puts("    if(access(#{o.var}_arg, #{mode})) {",
             "      err = \"#{msg}\";",
             "      ((err += #{o.var}_arg) += \"': \") += strerror(errno);",
             "      error(err.c_str());",
             "    }")
    }
  end

  h.puts("  }") # close parser

  # Usage
  if !$usage.nil?
    ausage = quote_newline_dquotes($usage, "  ")
  else
    ausage = "Usage: #{$package || class_name} [options]"
    $args.each { |a|
     ausage += " #{a.name}:#{a.typestr || dflt_typestr(a.type)}#{a.multiple ? "+" : ""}"
    }
  end

  h.puts(<<EOS)
  static const char * usage() { return "#{ausage}"; }
  class error {
    int code_;
    std::ostringstream msg_;

    // Select the correct version (GNU or XSI) version of
    // strerror_r. strerror_ behaves like the GNU version of strerror_r,
    // regardless of which version is provided by the system.
    static const char* strerror__(char* buf, int res) {
      return res != -1 ? buf : "Invalid error";
    }
    static const char* strerror__(char* buf, char* res) {
      return res;
    }
    static const char* strerror_(int err, char* buf, size_t buflen) {
      return strerror__(buf, strerror_r(err, buf, buflen));
    }
    struct no_t { };

  public:
    static no_t no;
    error(int code = EXIT_FAILURE) : code_(code) { }
    explicit error(const char* msg, int code = EXIT_FAILURE) : code_(code)
      { msg_ << msg; }
    error(const std::string& msg, int code = EXIT_FAILURE) : code_(code)
      { msg_ << msg; }
    error& operator<<(no_t) {
      char buf[1024];
      msg_ << ": " << strerror_(errno, buf, sizeof(buf));
      return *this;
    }
    template<typename T>
    error& operator<<(const T& x) { msg_ << x; return (*this); }
    ~error() {
      ::std::cerr << "Error: " << msg_.str() << "\\n"
                  << usage() << "\\n"
                  << "Use --help for more information"
                  << ::std::endl;
      exit(code_);
    }
  };
EOS

  # Help
  desc = ""
  unless $purpose.nil?
    desc += $purpose + "\\n\\n"
  end
  unless $description.nil?
    desc += $description.split(/\n/).join("\\n\" \\\n    \"") + "\\n\\n"
  end

  h.puts(<<EOS)
  static const char * help() { return
    "#{desc}"
    "Options (default value in (), *required):\\n"
EOS
  output_options_descriptions(h, $options, false)
  usage_switch = " -U, "
  usage_switch = " " * usage_switch.size if usage_no_U
  usage_switch += "--usage"
  h.puts("    \"#{usage_switch.ljust($switchesjust)}  Usage\\n\"")
  help_switch = " -h, "
  help_switch = " " * help_switch.size if help_no_h
  help_switch += "--help"
  h.puts("    \"#{help_switch.ljust($switchesjust)}  This message\\n\"")
  h.puts("    \"#{"     --full-help".ljust($switchesjust)}  Detailed help\\n\"") if need_full
  version_switch = " -V, "
  version_switch = " " * version_switch.size if version_no_V
  version_switch += "--version"
  h.print("    \"#{version_switch.ljust($switchesjust)}  Version")
  if $after_text.nil?
    h.puts("\";")
  else
    h.puts("\\n\" \\", "  \"\\n\"")
    atext = quote_newline_dquotes($after_text, "  ")
    h.puts("    \"#{atext}\";")
  end
  h.puts("  }")

  # Hidden help
  has_hidden = $options.any? { |o| o.hidden }
  if has_hidden 
    h.puts(<<EOS)
  static const char* hidden() { return
    "Hidden options:\\n"
EOS
  output_options_descriptions(h, $options, true)
  h.puts(<<EOS)
    "";
  }
EOS
  else
    h.puts(<<EOS)
  static const char* hidden() { return ""; }
EOS
  end
  

  # Version
  h.puts("  void print_version(::std::ostream &os = std::cout) const {",
         "#ifndef PACKAGE_VERSION",
         "#define PACKAGE_VERSION \"0.0.0\"",
         "#endif",
         "    os << #{$version ? "\"" + $version + "\"" : "PACKAGE_VERSION"} << \"\\n\";",
         "  }")
  
  # Dump
  h.puts("  void dump(::std::ostream &os = std::cout) {")
  ($options + $args).each { |o| h.puts("    os << #{o.dump.join(" << ")} << \"\\n\";") }
  h.puts("  }")

  # Private methods
  h.puts(<<EOS)
};
EOS

  # Initialize static members
  # TODO: Should we have an option to put this in a .cc file?
  $options.each { |o|
    next unless o.type == :enum
    h.puts("const char* const #{class_name}::#{o.var}::strs[#{o.enum.size + 1}] = { #{o.enum.map { |x| "\"#{x}\"" }.join(", ") }, (const char*)0 };")
  }

h.puts(<<EOS)
#endif // __#{class_name.upcase}_HPP__"
EOS
end

#output_options_descriptions(out, opts, hidden) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/yaggo/parser.rb', line 20

def output_options_descriptions out, opts, hidden
  opts.each { |o|
    # need to be improved. break lines if too long
    next if o.secret || (o.hidden ^ hidden)
    s = " " + o.switches
    if s.size >= $switchesjust
      s += "\\n" + "".ljust($switchesjust)
    else
      s = s.ljust($switchesjust)
    end
    out.puts("    \"#{s} #{o.help}\\n\"") 
  }
end

#output_zsh_completion(fd, filename) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/yaggo/zsh_completion.rb', line 72

def output_zsh_completion(fd, filename)
  cmdname = File.basename(filename).gsub(/^_/, "")
  
  fd.puts("#compdef #{cmdname}", "",
          "local context state state_descr line",
          "typeset -A opt_args", "")
  return if $options.empty? && $args.empty?
  fd.puts("_arguments -s -S \\") 
  $options.each { |o|
    conflicts = zsh_conflict_option o
    switches = zsh_switches_option o
    descr = o.description ? "[#{o.description}]" : ""
    action = zsh_type_completion o, true
    fd.puts("#{conflicts}#{switches}'#{descr}#{action}' \\")
  }
  $args.each { |a|
    descr = a.description || " "
    action = zsh_type_completion a, false
    many = a.multiple ? "*" : ""
    fd.puts("'#{many}:#{descr}#{action}' \\")
  }
  fd.puts(" && return 0")
end

#package(str) ⇒ Object



36
# File 'lib/yaggo/dsl.rb', line 36

def package str; $package = str; end

#posix(*args) ⇒ Object



41
# File 'lib/yaggo/dsl.rb', line 41

def posix *args; $posix = true; end

#purpose(str) ⇒ Object



35
# File 'lib/yaggo/dsl.rb', line 35

def purpose str; $purpose = str; end

#quote_newline_dquotes(str, spaces = "") ⇒ Object

You should have received a copy of the GNU General Public License along with Yaggo. If not, see <www.gnu.org/licenses/>.



16
17
18
# File 'lib/yaggo/parser.rb', line 16

def quote_newline_dquotes str, spaces = ""
  str.gsub(/"/, '\\"').split(/\n/).join("\\n\" \\\n#{spaces}\"")
end

#requiredObject



65
# File 'lib/yaggo/dsl.rb', line 65

def required; $target.required = true; end

#run_block(name, b) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/yaggo/general.rb', line 92

def run_block(name, b)
  eval("#{$option_variables.join(" = ")} = nil", $main_binding)
  b.call
  $option_variables.each { |n| eval("#{n} #{n} unless #{n}.nil?", $main_binding) }
rescue NoMethodError => e
  header = find_error_header(e.backtrace)
  raise "#{header} In #{name}: invalid keyword '#{e.name}' in statement '#{e.name} #{e.args.map { |s| "\"#{s}\"" }.join(" ")}'"
rescue NameError => e
  header = find_error_header(e.backtrace)
  raise "#{header} In #{name}: invalid keyword '#{e.name}'"
rescue RuntimeError, ArgumentError => e
  header = find_error_header(e.backtrace)
  raise "#{header} In #{name}: #{e.message}"
end

#secretObject



67
# File 'lib/yaggo/dsl.rb', line 67

def secret; $target.secret = true; end

#str_conv(arg, type, *argv) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/yaggo/general.rb', line 69

def str_conv(arg, type, *argv)
  case type
  when :string
    "string(#{arg})"
  when :c_string
    arg
  when :uint32, :uint64
    "conv_uint<#{$type_to_C_type[type]}>((const char*)#{arg}, err, #{suffix_arg(argv[0])})"
  when :int32, :int64, :long, :int
    "conv_int<#{$type_to_C_type[type]}>((const char*)#{arg}, err, #{suffix_arg(argv[0])})"
  when :double
    "conv_double((const char*)#{arg}, err, #{suffix_arg(argv[0])})"
  when :enum
    # Convert a string to its equivalent enum value
    "conv_enum((const char*)#{arg}, err, #{argv[0]})"
  end
end

#stringObject



59
# File 'lib/yaggo/dsl.rb', line 59

def string;   $target.type = :string; end

#suffixObject



64
# File 'lib/yaggo/dsl.rb', line 64

def suffix; $target.suffix = true; end

#suffix_arg(suffix) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/yaggo/general.rb', line 56

def suffix_arg(suffix)
  case suffix
  when true
    "true"
  when false
    "false"
  when String
    suffix
  else
    raise "Invalid suffix specifier"
  end
end

#text(str) ⇒ Object



38
# File 'lib/yaggo/dsl.rb', line 38

def text str; $after_text = str; end

#typestr(str) ⇒ Object



72
# File 'lib/yaggo/dsl.rb', line 72

def typestr str; $target.typestr = str; end

#uint32Object



54
# File 'lib/yaggo/dsl.rb', line 54

def uint32;   $target.type = :uint32; end

#uint64Object



55
# File 'lib/yaggo/dsl.rb', line 55

def uint64;   $target.type = :uint64; end

#usage(str) ⇒ Object



37
# File 'lib/yaggo/dsl.rb', line 37

def usage str; $usage = str; end

#version(str) ⇒ Object



40
# File 'lib/yaggo/dsl.rb', line 40

def version str; $version = str; end

#zsh_conflict_option(o) ⇒ Object



17
18
19
20
21
22
23
24
25
26
# File 'lib/yaggo/zsh_completion.rb', line 17

def zsh_conflict_option o
  conflict_options = o.conflict + $options.map { |co|
    (co.conflict.include?(o.short) || co.conflict.include?(o.long)) ? (co.short || co.long) : nil
  }.compact.uniq
  return "" if conflict_options.empty?
  "'(" + conflict_options.map { |co_name|
    co = $opt_hash[co_name]
    [co.short && "-#{co.short}", co.long && "--#{co.long}"]
  }.flatten.compact.uniq.join(" ") + ")'"
end

#zsh_switches_option(o) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
# File 'lib/yaggo/zsh_completion.rb', line 28

def zsh_switches_option o
  switches = if o.type == :flag 
               [o.short && "-#{o.short}", o.long && "--#{o.long}"]
             else
               [o.short && "-#{o.short}+", o.long && "--#{o.long}="]
             end
  switches.compact!
  swstr = switches.size > 1 ? "{#{switches.join(",")}}" : switches[0]
  swstr = "\\*#{swstr}" if o.multiple
  swstr
end

#zsh_type_completion(o, with_type = true) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/yaggo/zsh_completion.rb', line 40

def zsh_type_completion o, with_type = true
  typedescr = o.typestr || o.type.id2name
  typename = with_type ? ":" + typedescr : ""
  guard_help = "#{typedescr} #{o.description || ""}"
  case o.type
  when :flag
    return ""
  when :enum
    return "#{typename}:(#{o.enum.join(" ")})"
  when :string, :c_string
    case o.typestr || ""
    when /file|path/i
      return "#{typename}:_files"
    when /dir/i
      return "#{typename}:_files -/"
    else
      return typename
    end
  when :int32, :int64, :int, :long
    suffixes = o.suffix ? "[kMGTPE]" : ""
    return "#{typename}:_guard \"[0-9+-]##{suffixes}\" \"#{guard_help}\""
  when :uint32, :uint64
    suffixes = o.suffix ? "[kMGTPE]" : ""
    return "#{typename}:_guard \"[0-9+]##{suffixes}\" \"#{guard_help}\""
  when :double
    suffixes = "[munpfakMGTPE]" if o.suffix
    return "#{typename}:_guard \"[0-9.eE+-]##{suffixes}\" \"#{guard_help}\""
  else
    return default
  end
end