X-Git-Url: http://nitlanguage.org diff --git a/lib/opts.nit b/lib/opts.nit index 6b3313e..3d15652 100644 --- a/lib/opts.nit +++ b/lib/opts.nit @@ -26,22 +26,32 @@ abstract class Option var helptext: String # Gathering errors during parsing - var errors: Array[String] + var errors: Array[String] = new Array[String] # Is this option mandatory? - var mandatory: Bool writable + var mandatory: Bool = false is writable + + # Is this option hidden from `usage`? + var hidden: Bool = false is writable # Has this option been read? - var read:Bool writable + var read: Bool = false is writable # Current value of this option - var value: VALUE writable + var value: VALUE is writable # Default value of this option - var default_value: nullable VALUE writable + var default_value: VALUE is writable # Create a new option - init init_opt(help: String, default: nullable VALUE, names: nullable Array[String]) + init(help: String, default: VALUE, names: nullable Array[String]) is old_style_init do + init_opt(help, default, names) + end + + # Init option `helptext`, `default_value` and `names`. + # + # Also set current `value` to `default`. + fun init_opt(help: String, default: VALUE, names: nullable Array[String]) do if names == null then self.names = new Array[String] @@ -49,19 +59,16 @@ abstract class Option self.names = names.to_a end helptext = help - mandatory = false - read = false default_value = default value = default - errors = new Array[String] end # Add new aliases for this option fun add_aliases(names: String...) do names.add_all(names) - + # An help text for this option with default settings redef fun to_s do return pretty(2) - + # A pretty print for this help fun pretty(off: Int): String do @@ -75,49 +82,57 @@ abstract class Option return text.to_s end + # Pretty print the default value. fun pretty_default: String do var dv = default_value - if dv != null then return " ({dv})" + if dv != null then return " ({dv.to_s})" return "" end # Consume parameters for this option - protected fun read_param(it: Iterator[String]) + protected fun read_param(opts: OptionContext, it: Iterator[String]) do read = true end end +# Not really an option. Just add a line of text when displaying the usage class OptionText super Option - init(text: String) do init_opt(text, null, null) + + # Init a new OptionText with `text`. + init(text: String) is old_style_init do super(text, null, null) redef fun pretty(off) do return to_s redef fun to_s do return helptext end +# A boolean option, `true` when present, `false` if not class OptionBool super Option redef type VALUE: Bool - init(help: String, names: String...) do init_opt(help, false, names) + # Init a new OptionBool with a `help` message and `names`. + init(help: String, names: String...) is old_style_init do super(help, false, names) - redef fun read_param(it) + redef fun read_param(opts, it) do super value = true end end +# A count option. Count the number of time this option is present class OptionCount super Option redef type VALUE: Int - init(help: String, names: String...) do init_opt(help, 0, names) + # Init a new OptionCount with a `help` message and `names`. + init(help: String, names: String...) is old_style_init do super(help, 0, names) - redef fun read_param(it) + redef fun read_param(opts, it) do super value += 1 @@ -127,50 +142,68 @@ end # Option with one parameter (mandatory by default) abstract class OptionParameter super Option + + # Convert `str` to a value of type `VALUE`. protected fun convert(str: String): VALUE is abstract # Is the parameter mandatory? - var parameter_mandatory: Bool writable + var parameter_mandatory = true is writable - redef fun read_param(it) + redef fun read_param(opts, it) do super - if it.is_ok and it.item.chars.first != '-' then + + var ok = it.is_ok + if ok and not parameter_mandatory and not it.item.is_empty and it.item.chars.first == '-' then + # The next item may looks like a known command + # Only check if `not parameter_mandatory` + for opt in opts.options do + if opt.names.has(it.item) then + # The next item is a known command + ok = false + break + end + end + end + + if ok then value = convert(it.item) it.next else - if parameter_mandatory then - errors.add("Parameter expected for option {names.first}.") - end + errors.add("Parameter expected for option {names.first}.") end end - - init init_opt(h, d, n) - do - super - parameter_mandatory = true - end end +# An option with a `String` as parameter class OptionString super OptionParameter redef type VALUE: nullable String - init(help: String, names: String...) do init_opt(help, null, names) + # Init a new OptionString with a `help` message and `names`. + init(help: String, names: String...) is old_style_init do super(help, null, names) redef fun convert(str) do return str end +# An option to choose from an enumeration +# +# Declare an enumeration option with all its possible values as an array. +# Once the arguments are processed, `value` is set as the index of the selected value, if any. class OptionEnum super OptionParameter redef type VALUE: Int + + # Values in the enumeration. var values: Array[String] - init(values: Array[String], help: String, default: Int, names: String...) - do + # Init a new OptionEnum from `values` with a `help` message and `names`. + # + # `default` is the index of the default value in `values`. + init(values: Array[String], help: String, default: Int, names: String...) is old_style_init do assert values.length > 0 self.values = values.to_a - init_opt("{help} <{values.join(", ")}>", default, names) + super("{help} <{values.join(", ")}>", default, names) end redef fun convert(str) @@ -184,35 +217,51 @@ class OptionEnum return id end + # Get the value name from `values`. fun value_name: String do return values[value] redef fun pretty_default do - if default_value != null then - return " ({values[default_value.as(not null)]})" - else - return "" - end - end + return " ({values[default_value]})" + end end +# An option with an Int as parameter class OptionInt super OptionParameter redef type VALUE: Int - init(help: String, default: Int, names: String...) do init_opt(help, default, names) - + # Init a new OptionInt with a `help` message, a `default` value and `names`. + init(help: String, default: Int, names: String...) is old_style_init do + super(help, default, names) + end + redef fun convert(str) do return str.to_i end +# An option with a Float as parameter +class OptionFloat + super OptionParameter + redef type VALUE: Float + + # Init a new OptionFloat with a `help` message, a `default` value and `names`. + init(help: String, default: Float, names: String...) is old_style_init do + super(help, default, names) + end + + redef fun convert(str) do return str.to_f +end + +# An option with an array as parameter +# `myprog -optA arg1 -optA arg2` is giving an Array `["arg1", "arg2"]` class OptionArray super OptionParameter redef type VALUE: Array[String] - init(help: String, names: String...) - do + # Init a new OptionArray with a `help` message and `names`. + init(help: String, names: String...) is old_style_init do values = new Array[String] - init_opt(help, values, names) + super(help, values, names) end private var values: Array[String] @@ -223,13 +272,25 @@ class OptionArray end end +# Context where the options process class OptionContext - var options: Array[Option] - var rest: Array[String] - var errors: Array[String] + # Options present in the context + var options = new Array[Option] + + # Rest of the options after `parse` is called + var rest = new Array[String] - private var optmap: Map[String, Option] - + # Errors found in the context after parsing + var context_errors = new Array[String] + + private var optmap = new HashMap[String, Option] + + # Add one or more options to the context + fun add_option(opts: Option...) do + options.add_all(opts) + end + + # Display all the options available fun usage do var lmax = 1 @@ -240,19 +301,30 @@ class OptionContext end if lmax < l then lmax = l end - + for i in options do - print(i.pretty(lmax)) + if not i.hidden then + print(i.pretty(lmax)) + end end end - # Parse ans assign options everywhere is the argument list + # Parse and assign options everywhere in the argument list fun parse(argv: Collection[String]) do var it = argv.iterator parse_intern(it) end + # Must all option be given before the first argument? + # + # When set to `false` (the default), options of the command line are + # all parsed until the end of the list of arguments or until "--" is met (in this case "--" is discarded). + # + # When set to `true` options are parsed until the first non-option is met. + var options_before_rest = false is writable + + # Parse the command line protected fun parse_intern(it: Iterator[String]) do var parseargs = true @@ -269,7 +341,7 @@ class OptionContext # We're looking for packed short options if str.chars.last_index_of('-') == 0 and str.length > 2 then var next_called = false - for i in [1..str.length] do + for i in [1..str.length[ do var short_opt = "-" + str.chars[i].to_s if optmap.has_key(short_opt) then var option = optmap[short_opt] @@ -277,7 +349,7 @@ class OptionContext it.next next_called = true end - option.read_param(it) + option.read_param(self, it) end end if not next_called then it.next @@ -285,10 +357,14 @@ class OptionContext if optmap.has_key(str) then var opt = optmap[str] it.next - opt.read_param(it) + opt.read_param(self, it) else rest.add(it.item) it.next + if options_before_rest then + rest.add_all(it.to_a) + parseargs = false + end end end end @@ -296,26 +372,11 @@ class OptionContext for opt in options do if opt.mandatory and not opt.read then - errors.add("Mandatory option {opt.names.join(", ")} not found.") + context_errors.add("Mandatory option {opt.names.join(", ")} not found.") end end end - fun add_option(opts: Option...) - do - for opt in opts do - options.add(opt) - end - end - - init - do - options = new Array[Option] - optmap = new HashMap[String, Option] - rest = new Array[String] - errors = new Array[String] - end - private fun build do for o in options do @@ -325,18 +386,16 @@ class OptionContext end end - fun get_errors: Array[String] + # Options parsing errors. + fun errors: Array[String] do - var errors: Array[String] = new Array[String] - - errors.add_all(errors) - + var errors = new Array[String] + errors.add_all context_errors for o in options do for e in o.errors do errors.add(e) end end - return errors end end