examples: annotate examples
[nit.git] / lib / opts.nit
index 19b8ff3..a7789f8 100644 (file)
@@ -29,26 +29,28 @@ abstract class Option
        var errors: Array[String] = new Array[String]
 
        # Is this option mandatory?
-       var mandatory: Bool writable = false
+       var mandatory: Bool = false is writable
 
        # Is this option hidden from `usage`?
-       var hidden: Bool writable = false
+       var hidden: Bool = false is writable
 
        # Has this option been read?
-       var read: Bool writable = false
+       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: VALUE writable
+       var default_value: VALUE is writable
 
        # Create a new option
-       init(help: String, default: VALUE, names: nullable Array[String])
-       do
+       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
@@ -80,6 +82,7 @@ abstract class Option
                return text.to_s
        end
 
+       # Pretty print the default value.
        fun pretty_default: String
        do
                var dv = default_value
@@ -88,7 +91,7 @@ abstract class Option
        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
@@ -97,7 +100,9 @@ end
 # Not really an option. Just add a line of text when displaying the usage
 class OptionText
        super Option
-       init(text: String) do super(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
 
@@ -109,9 +114,10 @@ class OptionBool
        super Option
        redef type VALUE: Bool
 
-       init(help: String, names: String...) do super(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
@@ -121,11 +127,12 @@ end
 # A count option. Count the number of time this option is present
 class OptionCount
        super Option
-       redef type VALUE: Int
+       redef type VALUE: Int is fixed
 
-       init(help: String, names: String...) do super(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
@@ -135,45 +142,65 @@ 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 = true
+       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
 end
 
-# An option with a String as parameter
+# An option with a `String` as parameter
 class OptionString
        super OptionParameter
        redef type VALUE: nullable String
 
-       init(help: String, names: String...) do super(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 with an enum as parameter
-# In the code, declaring an option enum (-e) with an enum like `["zero", "one", "two"]
-# In the command line, typing `myprog -e one` is giving 1 as value
+# 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
                super("{help} <{values.join(", ")}>", default, names)
@@ -190,15 +217,12 @@ 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]})"
-               else
-                       return ""
-               end
+               return " ({values[default_value]})"
        end
 end
 
@@ -207,9 +231,18 @@ class OptionInt
        super OptionParameter
        redef type VALUE: Int
 
-       init(help: String, default: Int, names: String...) do super(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
+               if str.is_int then return str.to_i
 
-       redef fun convert(str) do return str.to_i
+               errors.add "Expected an integer for option {names.join(", ")}."
+               return 0
+       end
 end
 
 # An option with a Float as parameter
@@ -217,7 +250,10 @@ class OptionFloat
        super OptionParameter
        redef type VALUE: Float
 
-       init(help: String, default: Float, names: String...) do super(help, default, names)
+       # 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
@@ -228,8 +264,8 @@ 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]
                super(help, values, names)
        end
@@ -251,14 +287,12 @@ class OptionContext
        var rest = new Array[String]
 
        # Errors found in the context after parsing
-       var errors = new Array[String]
+       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
+       fun add_option(opts: Option...) do options.add_all(opts)
 
        # Display all the options available
        fun usage
@@ -279,13 +313,22 @@ class OptionContext
                end
        end
 
-       # Parse and assign options everywhere in the argument list
-       fun parse(argv: Collection[String])
+       # Parse and assign options in `argv` or `args`
+       fun parse(argv: nullable Collection[String])
        do
+               if argv == null then argv = args
                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
@@ -311,7 +354,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
@@ -319,10 +362,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
@@ -330,7 +377,7 @@ 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
@@ -344,10 +391,11 @@ 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)