#! /usr/bin/env ruby

if !$load_self
  $load_self = true
  load(__FILE__)
  main
  exit(0)
end

# This file is part of Yaggo.

# Yaggo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# Yaggo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

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

require 'optparse'


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

# Loading yaggo/version

$yaggo_version = "1.5.10"

# Loading yaggo/man_page

# This file is part of Yaggo.

# Yaggo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# Yaggo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

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

require 'pathname'

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 (gmarcais@umd.edu)
.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

# Loading yaggo/stub

# This file is part of Yaggo.

# Yaggo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# Yaggo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

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


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

# Loading yaggo/general

# This file is part of Yaggo.

# Yaggo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# Yaggo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

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


$typejust = 30
$switchesjust = 40

$type_to_C_type = { 
  :uint32 => "uint32_t",
  :uint64 => "uint64_t",
  :int32 => "int32_t",
  :int64 => "int64_t",
  :int => "int",
  :long => "long",
  :double => "double",
  :string => "string",
  :c_string => "const char *",
  :enum => "int",
}
$type_default = {
  :uint32 => "0",
  :uint64 => "0",
  :int32 => "0",
  :int64 => "0",
  :int => "0",
  :long => "0",
  :double => "0.0",
  :string => "",
  :c_string => "",
  :enum => "0",
}

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

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

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

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

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


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

# Loading yaggo/library

# This file is part of Yaggo.

# Yaggo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# Yaggo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

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


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


# Loading yaggo/dsl

# This file is part of Yaggo.

# Yaggo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# Yaggo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

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


##############################
# Process an input files. Define the Domain Specific Language.
##############################
$options = []
$opt_hash = {}
$args = []

class NoTarget
  def description str; $description = str; end
  def description= str; $description = str; end
  def method_missing(m, *args)
    raise "'#{m}' used outside of option or arg description"
  end
end
$target = NoTarget.new

def output str; $output = str; end
def name str; $klass = str; end
def purpose str; $purpose = str; end
def package str; $package = str; end
def usage str; $usage = str; end
def text str; $after_text = str; end
def description str; $target.description = str; end
def version str; $version = str; end
def posix *args; $posix = true; end
def license str; $license = str; end
$global_variables = [:output, :name, :purpose, :package,
                     :description, :version, :license, :posix]
output = name = purpose = package = description = version = license = nil

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

def int32;    $target.type = :int32; end
def int64;    $target.type = :int64; end
def uint32;   $target.type = :uint32; end
def uint64;   $target.type = :uint64; end
def int;      $target.type = :int; end
def long;     $target.type = :long; end
def double;   $target.type = :double; end
def string;   $target.type = :string; end
def c_string; $target.type = :c_string; end
def flag;     $target.type = :flag; end
def enum(*argv); $target.type = :enum; $target.enum = argv; end

def suffix; $target.suffix = true; end
def required; $target.required = true; end
def hidden; $target.hidden = true; end
def secret; $target.secret = true; end
def on; $target.on; end
def off; $target.off; end
def no; $target.no; end
def default str; $target.default = str; end
def typestr str; $target.typestr = str; end
def multiple; $target.multiple = true; end
def at_least n; $target.at_least = n; end
def conflict *a; $target.conflict= a; end
def imply *a; $target.imply= a; end
def access *types; $target.access= types; end
# Define the following local variables and check their value after
# yielding the block to catch syntax such as default="value".
$option_variables = [:default, :typestr, :at_least]
default = typestr = at_least = nil
$main_binding = binding

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

class BaseOptArg
  def at_least=(n)
    multiple = true
    nb = case n
         when Integer
           n
         when String
           n =~ /^\d+$/ ? n.to_i : nil
         else
           nil
         end
    raise "Invalid minimum number for at_least (#{n})" if nb.nil?
    self.multiple = true
    @at_least = nb
  end

  def type=(t)
    raise "More than 1 type specified: '#{type}' and '#{t}'" unless @type.nil? || @type == t
    @type = t
  end

  def suffix=(t)
    case type
    when nil
      raise "A numerical type must be specify before suffix"
    when :flag, :string, :c_string
      raise "Suffix is meaningless with the type #{type}"
    end
    @suffix = t
  end

  def access=(types)
    types.all? { |t| ["read", "write", "exec"].include?(t) } or
      raise "Invalid access type(s): #{types.join(", ")}"
    @access_types = types
  end

  def check
    if !@access_types.empty? && @type != :c_string
      raise "Access checking is valid only with a path (a c_string)"
    end
  end
end

class Option < BaseOptArg
  attr_accessor :description, :required, :typestr
  attr_accessor :hidden, :secret, :conflict, :multiple, :access_types, :noflag
  attr_reader :long, :short, :var, :type, :at_least, :default, :suffix, :enum
  attr_reader :imply

  def initialize(long, short)
    @long, @short = long, short
    @var = (@long || @short).gsub(/[^a-zA-Z0-9_]/, "_")
    @type = nil
    @no = false # Also generate the --noswitch for a flag
    @default = nil
    @suffix = false
    @at_least = nil
    @conflict = []
    @enum = []
    @imply = []
    @access_types = []
  end

  def on
    self.type = :flag
    self.default = "true"
  end

  def off
    self.type = :flag
    self.default = "false"
  end

  def no
    self.type = :flag
    self.noflag = true
  end

  def tf_to_on_off v
    case v
    when "true"
      "on"
    when "false"
      "off"
    else
      v
    end
  end

  def convert_int(x, signed = true)
    x =~ /^([+-]?\d+)([kMGTPE]?)$/ or return nil
    v = $1.to_i
    return nil if v < 0 && !signed
    case $2
    when "k"
      v *= 1000
    when "M"
      v *= 1000_000
    when "G"
      v *= 1000_000_000
    when "T"
      v *= 1000_000_000_000
    when "P"
      v *= 1000_000_000_000_000
    when "E"
      v *= 1000_000_000_000_000_000
    end
    return v
  end

  def convert_double(x)
    x =~ /^([+-]?[\d]+(?:\.\d*))?(?:([afpnumkMGTPE])|([eE][+-]?\d+))?$/ or return nil
    v = "#{$1}#{$3}".to_f
    case $2
    when "a"
      v *= 1e-18
    when "f"
      v *= 1e-15
    when "p"
      v *= 1e-12
    when "n"
      v *= 1e-9
    when "u"
      v *= 1e-6
    when "m"
      v *= 1e-3
    when "k"
      v *= 1e3
    when "M"
      v *= 1e6
    when "G"
      v *= 1e9
    when "T"
      v *= 1e12
    when "P"
      v *= 1e15
    when "E"
      v *= 1e18
    end
    return v
  end

  def default=(v)
    type.nil? and raise "A type must be specified before defining a default value"
    unless default.nil?
      if type == :flag
        v1, v2 = tf_to_on_off(default), tf_to_on_off(v)
      else
        v1, v2 = default, v
      end
      raise "More than 1 default value specified: '#{v1}' and '#{v2}'"
    end
    pref = "Option #{long || ""}|#{short || ""}:"
    bv = v # Backup v for display
    case @type
    when nil
      raise "#{pref} No type specified"
    when :uint32, :uint64
      (Integer === v && v >= 0) || (String === v && v = convert_int(v, false)) or
        raise "#{pref} Invalid unsigned integer '#{bv}'"
    when :int32, :int64, :int, :long
      (Integer === v) || (String === v && v = convert_int(v, true)) or
        raise "#{pref} Invalid integer #{bv}"
    when :double
      (Float === v) || (String === v && v = convert_double(v)) or
        raise "#{pref} Invalid double #{bv}"
    when :enum
      v = v.to_i if v =~ /^\d+$/
      case v
      when Integer
        (v >= 0 && v < @enum.size) or
          raise "Default is out of range [0, #{@enum.size-1}]"
      when String
        nv = @enum.index(v) or
          raise "Unknown constant '#{v}'. Should be one of { #{@enum.join(", ")} }"
        v = nv
      else
        raise "Expected an Integer or a String"
      end
    end
    @default = v
  end

  def enum=(*argv)
    @type == :enum or raise "#{pref} Enum valid only for enum types."
    @enum = argv.flatten
  end

  def conflict= a; @conflict += a.map { |x| x.gsub(/^-+/, "") }; end
  def imply= a; @imply += a.map { |x| x.gsub(/^-+/, "") }; end

  def check
    pref = "Option #{long || ""}|#{short || ""}:"
    raise "#{pref} No type specified" if type.nil?

    if multiple
      raise "#{pref} Multiple is meaningless with a flag" if type == :flag
      raise "#{pref} An option marked multiple cannot have a default value" unless default.nil?
      raise "#{pref} Multiple is incompatible with enum type" if type == :enum
    end

    if @type == :flag && noflag && !short.nil?
      raise "#{pref} flag with 'no' option cannot have a short switch"
    end

    super

    # case @type
    # when nil
    #   raise "#{pref} No type specified"
    # when :uint32, :uint64
    #   @default.nil? || @default =~ /^\d+$/ or
    #     raise "#{pref} Invalid unsigned integer #{@default}"
    # when :int32, :int64, :int, :long
    #   @default.nil? || @default =~ /^[+-]?\d+$/ or
    #     raise "#{pref} Invalid integer #{@default}"
    # when :double
    #   @default.nil? || @default =~ /^[+-]?[\d.]+([eE][+-]?\d+)?$/ or
    #     raise "#{pref} Invalid double #{@default}"
    # when :flag
    #   raise "#{pref} A flag cannot be declared multiple" if @multiple
    #   raise "#{pref} Suffix is meaningless for a flag" if @suffix
    # end
  end

  def static_decl
    a = []
    if @type == :enum
      a << "struct #{@var} {"
      a << "  enum { #{@enum.map { |x| x.gsub(/[^a-zA-Z0-9_]/, "_") }.join(", ")} };"
      a << "  static const char* const  strs[#{@enum.size + 1}];"
      a << "};"
    end
    a
  end

  def var_decl
    if @type == :flag
      ["#{"bool".ljust($typejust)} #{@var}_flag;"]
    else
      a = []
      if @multiple
        c_type = "::std::vector<#{$type_to_C_type[@type]}>"
        a << (c_type.ljust($typejust) + " #{@var}_arg;")
        a << ("typedef #{c_type}::iterator #{@var}_arg_it;")
        a << ("typedef #{c_type}::const_iterator #{@var}_arg_const_it;")
      else
        a << "#{$type_to_C_type[@type].ljust($typejust)} #{@var}_arg;"
      end
      a << "#{"bool".ljust($typejust)} #{@var}_given;"
    end
  end

  def init
    s = "#{@var}_#{@type == :flag ? "flag" : "arg"}("
    s += default_val(@default, @type, @enum) unless @multiple
    s += ")"
    unless @type == :flag
      s += ", #{@var}_given(false)"
    end
    s
  end

  def long_enum
    return nil if !@short.nil?
    res = [@var.upcase + "_OPT"]
    if @type == :flag && noflag
      res << "NO#{@var.upcase}_OPT"
    end
    res
  end

  def struct
    res = ["{\"#{long}\", #{@type == :flag ? 0 : 1}, 0, #{@short ? "'" + @short + "'" : long_enum[0]}}"]
    if @type == :flag && noflag
      res << "{\"no#{long}\", 0, 0, #{long_enum()[1]}}"
    end
    res
  end
  def short_str
    return nil if @short.nil?
    @short + (@type == :flag ? "" : ":")
  end
  def switches
    s  = @short.nil? ? "    " : "-#{@short}"
    s += ", " unless @short.nil? || @long.nil?
    unless @long.nil?
      if @type == :flag && @noflag
        s += "--[no]#{@long}"
      else
        s += "--#{@long}"
      end
      s += "=#{@typestr || dflt_typestr(@type, @enum)}" unless @type == :flag
    end
    s
  end

  def default_str
    return @default unless @type == :enum
    @enum[@default || 0]
  end

  def help
    s  = @required ? "*" : " "
    @description ||= "Switch #{switches}"
    s += @description.gsub(/"/, '\"') || ""
    default = default_str
    s += " (#{default})" unless default.nil?
    s
  end

  def dump
    case @type
    when :flag
      ["\"#{@var}_flag:\"", "#{@var}_flag"]
    when :enum
      ["\"#{@var}_given:\"", "#{@var}_given",
       "\" #{@var}_arg:\"", "#{@var}_arg", '"|"', "#{@var}::strs[#{@var}_arg]"]
    else
      ["\"#{@var}_given:\"", "#{@var}_given", 
       "\" #{@var}_arg:\"", @multiple ? "vec_str(#{@var}_arg)" : "#{@var}_arg"]
    end
  end

  def parse_arg(no = false)
    a = @imply.map { |ios| "#{$opt_hash[ios].var}_flag = true;" }
    a << "#{@var}_given = true;" unless @type == :flag
    case @type
    when :flag
      if @noflag
        a << ["#{@var}_flag = #{no ? "false" : "true"};"]
      else
        a << ["#{@var}_flag = #{@default == "true" ? "false" : "true"};"]
      end
    when :string
      a << (@multiple ? "#{@var}_arg.push_back(#{str_conv("optarg", @type, false)});" : "#{@var}_arg.assign(optarg);")
    when :c_string
      a << (@multiple ? "#{@var}_arg.push_back(#{str_conv("optarg", @type, false)});" : "#{@var}_arg = optarg;")
    when :uint32, :uint64, :int32, :int64, :int, :long, :double
      a << (@multiple ? "#{@var}_arg.push_back(#{str_conv("optarg", @type, @suffix)});" : "#{@var}_arg = #{str_conv("optarg", @type, @suffix)};")
      a << "CHECK_ERR(#{@type}_t, optarg, \"#{switches}\")" 
    when :enum
      a << "#{@var}_arg = #{str_conv("optarg", @type, "#{@var}::strs")};"
      a << "CHECK_ERR(#{@type}, optarg, \"#{switches}\")"
    end
    a
  end
end

class Arg < BaseOptArg
  attr_accessor :description, :type, :typestr, :multiple, :access_types
  attr_reader :name, :at_least, :suffix, :var
  def initialize(str)
    @name = str
    @var = @name.gsub(/[^a-zA-Z0-9_]/, "_")
    @type = nil
    @at_least = 0
    @suffix = false
    @access_types = []
  end

  def type=(t)
    super
    raise "An arg cannot be of type '#{t}'" if t == :flag
  end

  def on; raise "An arg cannot be a flag with default value on"; end
  def off; raise "An arg cannot be a flag with default value off"; end

  def default=(*args)
    raise "An arg cannot have a default value (#{args[0]})"
  end

  def hidden=(*args)
    raise "An arg cannot be marked hidden"
  end

  def secret=(*args)
    raise "An arg cannot be marked secret"
  end

  def required=(*args)
    raise "An arg cannot be marked required"
  end

  def check
    super

    pref = "Arg #{name}:"
    raise "#{pref} No type specified" if type.nil?
  end

  def var_decl
    if @multiple
      c_type = "::std::vector<#{$type_to_C_type[@type]}>"
      [c_type.ljust($typejust) + " #{@var}_arg;",
       "typedef #{c_type}::iterator #{@var}_arg_it;",
       "typedef #{c_type}::const_iterator #{@var}_arg_const_it;"]
    else
      ["#{$type_to_C_type[@type]}".ljust($typejust) + " #{@var}_arg;"]
    end
  end

  def init
    s = "#{@var}_arg("
    s += default_val(@default, @type) unless @multiple
    s += ")"
    s
  end

  def dump
    ["\"#{@var}_arg:\"",
     @multiple ? "vec_str(#{@var}_arg)" : "#{@var}_arg"]
  end

  def parse_arg
    a = []
    off = ""
    if @multiple
      a << "for( ; optind < argc; ++optind) {"
      a << "  #{@var}_arg.push_back(#{str_conv("argv[optind]", @type, @suffix)});"
      off = "  "
    else
      a << "#{@var}_arg = #{str_conv("argv[optind]", @type, @suffix)};"
    end
    unless @type == :string || @type == :c_string
      a << (off + "CHECK_ERR(#{@type}_t, argv[optind], \"#{@var}\")")
    end
    a << (@multiple ? "}" : "++optind;")
    a
  end
end

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

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

# Loading yaggo/parser

# This file is part of Yaggo.

# Yaggo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# Yaggo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

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

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

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

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

# Loading yaggo/zsh_completion

# This file is part of Yaggo.

# Yaggo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# Yaggo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

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


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

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

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

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
