shellopts 2.4.3 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +19 -14
- data/TODO +22 -0
- data/lib/shellopts/analyzer.rb +10 -10
- data/lib/shellopts/args.rb +2 -2
- data/lib/shellopts/argument_type.rb +8 -8
- data/lib/shellopts/dump.rb +8 -8
- data/lib/shellopts/formatter.rb +8 -8
- data/lib/shellopts/grammar.rb +5 -4
- data/lib/shellopts/interpreter.rb +12 -4
- data/lib/shellopts/lexer.rb +9 -9
- data/lib/shellopts/option.rb +27 -0
- data/lib/shellopts/parser.rb +8 -8
- data/lib/shellopts/program.rb +15 -40
- data/lib/shellopts/renderer.rb +13 -6
- data/lib/shellopts/token.rb +4 -4
- data/lib/shellopts/version.rb +1 -1
- data/lib/shellopts.rb +19 -18
- metadata +3 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: dc8c49d6d58b38a3fdff578630ef13abe8f3c6c5939cf5ff8098198eb8a17fbe
         | 
| 4 | 
            +
              data.tar.gz: 181c71564cd5a755bc3e17a19652748b8a276c2cd910a25c1f8d3d39bd6d27c6
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: c6fb75cc8baf4738276d25ebddb0dd7827ff816cd124486e5ac681f3fe2d9f80aee1bd9bc2d409eec4e65d476d897c481505f95ca60eb27b70259aac548657b1
         | 
| 7 | 
            +
              data.tar.gz: ef3e4129aea6baab3b683b25c51bd77d0df222c0cfb18effc0b26e9d8497e66fc2735c32e53e1b67030030824e78bc4fe59c7fd8f17d6e0c8daffb31949f9434
         | 
    
        data/README.md
    CHANGED
    
    | @@ -74,11 +74,11 @@ to check presence and return an optional argument. Given the options "--alpha | |
| 74 74 |  | 
| 75 75 | 
             
            ```ruby
         | 
| 76 76 | 
             
              # Returns true if the option is present and false otherwise
         | 
| 77 | 
            -
              opts.alpha?() | 
| 77 | 
            +
              opts.alpha?()
         | 
| 78 78 | 
             
              opts.beta?()
         | 
| 79 79 |  | 
| 80 80 | 
             
              # Returns the argument of the beta option or nil if missing
         | 
| 81 | 
            -
              opts.beta() | 
| 81 | 
            +
              opts.beta()
         | 
| 82 82 | 
             
            ```
         | 
| 83 83 |  | 
| 84 84 | 
             
            Given the commands "cmd1! cmd2!" the following methods are available:
         | 
| @@ -87,7 +87,7 @@ Given the commands "cmd1! cmd2!" the following methods are available: | |
| 87 87 | 
             
              # Returns the sub-command object or nil if not present
         | 
| 88 88 | 
             
              opts.cmd1!
         | 
| 89 89 | 
             
              opts.cmd2!
         | 
| 90 | 
            -
             | 
| 90 | 
            +
             | 
| 91 91 | 
             
              opts.subcommand!  # Returns the sub-command object or nil if not present
         | 
| 92 92 | 
             
              opts.subcommand   # Returns the sub-command's identifier (eg. :cmd1!)
         | 
| 93 93 | 
             
            ```
         | 
| @@ -96,7 +96,7 @@ It is used like this | |
| 96 96 |  | 
| 97 97 | 
             
            ```ruby
         | 
| 98 98 | 
             
              case opts.subcommand
         | 
| 99 | 
            -
                when :cmd1 | 
| 99 | 
            +
                when :cmd1
         | 
| 100 100 | 
             
                  # opts.cmd1 is defined here
         | 
| 101 101 | 
             
                when :cmd2
         | 
| 102 102 | 
             
                  # opts.cmd2 is defined here
         | 
| @@ -160,7 +160,7 @@ is paragraphs | |
| 160 160 | 
             
              -a,alpha @ Brief comment for -a and --alpha options
         | 
| 161 161 | 
             
                Longer description of the option that is used by `::help`
         | 
| 162 162 |  | 
| 163 | 
            -
              cmd! | 
| 163 | 
            +
              cmd!
         | 
| 164 164 | 
             
                @ Alternative style of brief comment
         | 
| 165 165 |  | 
| 166 166 | 
             
                Longer description of the command
         | 
| @@ -179,23 +179,28 @@ error messages: | |
| 179 179 | 
             
            The general syntax for options is
         | 
| 180 180 |  | 
| 181 181 | 
             
            ```
         | 
| 182 | 
            -
              <prefix><optionlist>[=argspec][?]
         | 
| 182 | 
            +
              <prefix><optionlist>[=argspec][,][?]
         | 
| 183 183 | 
             
            ```
         | 
| 184 184 |  | 
| 185 | 
            +
            (TODO: Restructure: prefix,options,argument-spec,argument-spec-modifier,etc.)
         | 
| 186 | 
            +
             | 
| 185 187 | 
             
            The option list is a comma-separated list of option names. It is prefixed with
         | 
| 186 188 | 
             
            a '-' if the option list starts with a short option name and '--' if the option
         | 
| 187 189 | 
             
            list starts with a long name. '-' and '--' can be replaced with '+' or '++' to
         | 
| 188 190 | 
             
            indicate that the option can be repeated
         | 
| 189 191 |  | 
| 190 192 | 
             
            ```
         | 
| 191 | 
            -
              -a,alpha | 
| 192 | 
            -
              ++beta | 
| 193 | 
            -
              --gamma=ARG? | 
| 193 | 
            +
              -a,alpha          @ '-a' and '--alpha'
         | 
| 194 | 
            +
              ++beta            @ '--beta', can be repeated
         | 
| 195 | 
            +
              --gamma=ARG?      @ '--gamma', takes an optional argument
         | 
| 196 | 
            +
              --delta=ARG,      @ '--delta', takes a mandatory comma-separated list of arguments
         | 
| 197 | 
            +
              --epsilon=ARG,?   @ '--delta', takes an optional list
         | 
| 194 198 | 
             
            ```
         | 
| 195 199 |  | 
| 196 200 | 
             
            An option argument has a name and a type. The type can be specified as '#'
         | 
| 197 | 
            -
            (integer), '$' (float), or as a comma-separated list of allowed | 
| 198 | 
            -
             | 
| 201 | 
            +
            (integer), '$' (float), ',' (list) or as a comma-separated list of allowed
         | 
| 202 | 
            +
            values (enum). The name should be in capital letters. Some names are keywords
         | 
| 203 | 
            +
            with a special meaning:
         | 
| 199 204 |  | 
| 200 205 | 
             
              | Keyword   | Type |
         | 
| 201 206 | 
             
              | --------- | ---- |
         | 
| @@ -219,7 +224,7 @@ explicitly by separating it from the type with a ':'. Examples: | |
| 219 224 | 
             
              -c=red,blue,green     @ -c takes one of the listed words
         | 
| 220 225 | 
             
              -d=FILE               @ Fails if file exists and is not a file
         | 
| 221 226 | 
             
              -d=EDIR               @ Fails if directory doesn't exist or is not a directory
         | 
| 222 | 
            -
              -d=INPUT:EFILE        @  | 
| 227 | 
            +
              -d=INPUT:EFILE        @ Expects an existing file. Shown as '-d=INPUT' in messages
         | 
| 223 228 | 
             
            ```
         | 
| 224 229 |  | 
| 225 230 | 
             
            ## Commands
         | 
| @@ -234,7 +239,7 @@ command line like this | |
| 234 239 | 
             
              cmd!
         | 
| 235 240 | 
             
                -b @ Command level option
         | 
| 236 241 | 
             
                subcmd!
         | 
| 237 | 
            -
                  -c @ Sub-command level option | 
| 242 | 
            +
                  -c @ Sub-command level option
         | 
| 238 243 | 
             
            ```
         | 
| 239 244 |  | 
| 240 245 | 
             
            In single-line format, subcommands are specified by prefixing the supercommand's name:
         | 
| @@ -256,7 +261,7 @@ SPEC = %( | |
| 256 261 | 
             
              -f,force            @ ignore nonexisten files and arguments, never prompt
         | 
| 257 262 | 
             
              -i                  @ prompt before every removal
         | 
| 258 263 |  | 
| 259 | 
            -
              -I | 
| 264 | 
            +
              -I
         | 
| 260 265 | 
             
                  @ prompt once
         | 
| 261 266 |  | 
| 262 267 | 
             
                  prompt once before removing more than three files, or when  removing
         | 
    
        data/TODO
    CHANGED
    
    | @@ -1,3 +1,25 @@ | |
| 1 | 
            +
            # TODO
         | 
| 2 | 
            +
            o A program framework to make it easier to handle commands:
         | 
| 3 | 
            +
                class Program
         | 
| 4 | 
            +
                  def check!() end
         | 
| 5 | 
            +
                  def fix!() end
         | 
| 6 | 
            +
                  def list!() end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(spec, argv)
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def command(cmd)
         | 
| 12 | 
            +
                    self.send(cmd.subcommand)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            o We really need a global #indent/#outdent set of methods
         | 
| 17 | 
            +
            o Make it possible to override the documentation of built-in options (ex. --verbose)
         | 
| 18 | 
            +
            o Create global IO-like objects for each output channel: $mesg, $verb, $notice,
         | 
| 19 | 
            +
              $warn, $error, $failure so that they can be used together with the usual
         | 
| 20 | 
            +
              output functions #puts, #print, #printf, #indent etc.
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                ShellOpts.mesg "string" -> $mesg.puts "string"
         | 
| 1 23 |  | 
| 2 24 | 
             
            o Remove IFILE and OFILE. Alternatively document them
         | 
| 3 25 | 
             
            o Document special-case file argument '-'
         | 
    
        data/lib/shellopts/analyzer.rb
    CHANGED
    
    | @@ -14,8 +14,8 @@ module ShellOpts | |
| 14 14 | 
             
                    children.delete_if { |node| node.is_a?(ArgSpec) }
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 |  | 
| 17 | 
            -
                  def analyzer_error(token, message) | 
| 18 | 
            -
                    raise AnalyzerError.new(token), message | 
| 17 | 
            +
                  def analyzer_error(token, message)
         | 
| 18 | 
            +
                    raise AnalyzerError.new(token), message
         | 
| 19 19 | 
             
                  end
         | 
| 20 20 | 
             
                end
         | 
| 21 21 |  | 
| @@ -27,7 +27,7 @@ module ShellOpts | |
| 27 27 | 
             
                  # Move options before first command or before explicit COMMAND section
         | 
| 28 28 | 
             
                  def reorder_options
         | 
| 29 29 | 
             
                    if commands.any?
         | 
| 30 | 
            -
                      i = children.find_index { |child| | 
| 30 | 
            +
                      i = children.find_index { |child|
         | 
| 31 31 | 
             
                        child.is_a?(Command) || child.is_a?(Section) && child.name == "COMMAND"
         | 
| 32 32 | 
             
                      }
         | 
| 33 33 | 
             
                      if i
         | 
| @@ -40,10 +40,10 @@ module ShellOpts | |
| 40 40 | 
             
                  def compute_option_hashes
         | 
| 41 41 | 
             
                    options.each { |option|
         | 
| 42 42 | 
             
                      option.idents.zip(option.names).each { |ident, name|
         | 
| 43 | 
            -
                        !@options_hash.key?(name) or | 
| 43 | 
            +
                        !@options_hash.key?(name) or
         | 
| 44 44 | 
             
                            analyzer_error option.token, "Duplicate option name: #{name}"
         | 
| 45 45 | 
             
                        @options_hash[name] = option
         | 
| 46 | 
            -
                        !@options_hash.key?(ident) or | 
| 46 | 
            +
                        !@options_hash.key?(ident) or
         | 
| 47 47 | 
             
                            analyzer_error option.token, "Can't use both #{@options_hash[ident].name} and #{name}"
         | 
| 48 48 | 
             
                        @options_hash[ident] = option
         | 
| 49 49 | 
             
                      }
         | 
| @@ -53,7 +53,7 @@ module ShellOpts | |
| 53 53 | 
             
                  # TODO Check for dash-collision
         | 
| 54 54 | 
             
                  def compute_command_hashes
         | 
| 55 55 | 
             
                    commands.each { |command|
         | 
| 56 | 
            -
                      !@commands_hash.key?(command.name) or | 
| 56 | 
            +
                      !@commands_hash.key?(command.name) or
         | 
| 57 57 | 
             
                          analyzer_error command.token, "Duplicate command name: #{command.name}"
         | 
| 58 58 | 
             
                      @commands_hash[command.name] = command
         | 
| 59 59 | 
             
                      @commands_hash[command.ident] = command
         | 
| @@ -75,7 +75,7 @@ module ShellOpts | |
| 75 75 | 
             
                def create_implicit_commands(cmd)
         | 
| 76 76 | 
             
                  path = cmd.path[0..-2]
         | 
| 77 77 |  | 
| 78 | 
            -
             | 
| 78 | 
            +
             | 
| 79 79 | 
             
                end
         | 
| 80 80 |  | 
| 81 81 | 
             
                # Link up commands with supercommands. This is only done for commands that
         | 
| @@ -83,7 +83,7 @@ module ShellOpts | |
| 83 83 | 
             
                # parent/child relationship is not changed Example:
         | 
| 84 84 | 
             
                #
         | 
| 85 85 | 
             
                #   cmd!
         | 
| 86 | 
            -
                #   cmd.subcmd! | 
| 86 | 
            +
                #   cmd.subcmd!
         | 
| 87 87 | 
             
                #
         | 
| 88 88 | 
             
                # Here subcmd is added to cmd's list of commands. It keeps its position in
         | 
| 89 89 | 
             
                # the program's parent/child relationship so that documentation will print the
         | 
| @@ -147,8 +147,8 @@ module ShellOpts | |
| 147 147 |  | 
| 148 148 | 
             
                  @grammar.compute_command_hashes
         | 
| 149 149 |  | 
| 150 | 
            -
                  @grammar.traverse { |node| | 
| 151 | 
            -
                    node.remove_brief_nodes | 
| 150 | 
            +
                  @grammar.traverse { |node|
         | 
| 151 | 
            +
                    node.remove_brief_nodes
         | 
| 152 152 | 
             
                    node.remove_arg_descr_nodes
         | 
| 153 153 | 
             
                    node.remove_arg_spec_nodes
         | 
| 154 154 | 
             
                  }
         | 
    
        data/lib/shellopts/args.rb
    CHANGED
    
    | @@ -21,7 +21,7 @@ module ShellOpts | |
| 21 21 | 
             
                # array. If the count is negative, the elements will be removed from the
         | 
| 22 22 | 
             
                # end of the array. If +count_or_range+ is a range, the number of elements
         | 
| 23 23 | 
             
                # returned will be in that range. Note that the range can't contain
         | 
| 24 | 
            -
                # negative numbers | 
| 24 | 
            +
                # negative numbers
         | 
| 25 25 | 
             
                #
         | 
| 26 26 | 
             
                # #extract raise a ShellOpts::Error exception if there's is not enough
         | 
| 27 27 | 
             
                # elements in the array to satisfy the request
         | 
| @@ -51,7 +51,7 @@ module ShellOpts | |
| 51 51 | 
             
                # As #extract except the array is expected to be emptied by the operation.
         | 
| 52 52 | 
             
                # Raise a #inoa exception if count is negative
         | 
| 53 53 | 
             
                #
         | 
| 54 | 
            -
                # #expect raise a ShellOpts::Error exception if the array is not emptied | 
| 54 | 
            +
                # #expect raise a ShellOpts::Error exception if the array is not emptied
         | 
| 55 55 | 
             
                # by the operation
         | 
| 56 56 | 
             
                #
         | 
| 57 57 | 
             
                # TODO: Better handling of ranges. Allow: 2..-1, -2..-4, etc.
         | 
| @@ -37,9 +37,9 @@ module ShellOpts | |
| 37 37 | 
             
                end
         | 
| 38 38 |  | 
| 39 39 | 
             
                class IntegerArgument < ArgumentType
         | 
| 40 | 
            -
                  def match?(name, literal) | 
| 41 | 
            -
                    literal =~ /^-?\d+$/ or | 
| 42 | 
            -
                        set_message "Illegal integer value in #{name}: #{literal}" | 
| 40 | 
            +
                  def match?(name, literal)
         | 
| 41 | 
            +
                    literal =~ /^-?\d+$/ or
         | 
| 42 | 
            +
                        set_message "Illegal integer value in #{name}: #{literal}"
         | 
| 43 43 | 
             
                  end
         | 
| 44 44 |  | 
| 45 45 | 
             
                  def value?(value) value.is_a?(Integer) end
         | 
| @@ -47,9 +47,9 @@ module ShellOpts | |
| 47 47 | 
             
                end
         | 
| 48 48 |  | 
| 49 49 | 
             
                class FloatArgument < ArgumentType
         | 
| 50 | 
            -
                  def match?(name, literal) | 
| 50 | 
            +
                  def match?(name, literal)
         | 
| 51 51 | 
             
                    # https://stackoverflow.com/a/21891705/2130986
         | 
| 52 | 
            -
                    literal =~ /^[+-]?(?:0|[1-9]\d*)(?:\.(?:\d*[1-9]|0))?$/ or | 
| 52 | 
            +
                    literal =~ /^[+-]?(?:0|[1-9]\d*)(?:\.(?:\d*[1-9]|0))?$/ or
         | 
| 53 53 | 
             
                        set_message "Illegal decimal value in #{name}: #{literal}"
         | 
| 54 54 | 
             
                  end
         | 
| 55 55 |  | 
| @@ -61,7 +61,7 @@ module ShellOpts | |
| 61 61 | 
             
                  attr_reader :kind
         | 
| 62 62 |  | 
| 63 63 | 
             
                  def subject # Used in error messages
         | 
| 64 | 
            -
                    @subject ||= | 
| 64 | 
            +
                    @subject ||=
         | 
| 65 65 | 
             
                        case kind
         | 
| 66 66 | 
             
                          when :file, :efile; "regular file"
         | 
| 67 67 | 
             
                          when :nfile, :ifile, :ofile; "file"
         | 
| @@ -74,7 +74,7 @@ module ShellOpts | |
| 74 74 |  | 
| 75 75 | 
             
                  def initialize(kind)
         | 
| 76 76 | 
             
                    constrain kind, :file, :dir, :path, :efile, :edir, :epath, :nfile, :ndir, :npath, :ifile, :ofile
         | 
| 77 | 
            -
                    @kind = kind | 
| 77 | 
            +
                    @kind = kind
         | 
| 78 78 | 
             
                  end
         | 
| 79 79 |  | 
| 80 80 | 
             
                  def match?(name, literal)
         | 
| @@ -168,7 +168,7 @@ module ShellOpts | |
| 168 168 | 
             
                      end
         | 
| 169 169 |  | 
| 170 170 | 
             
                    # file does not exist
         | 
| 171 | 
            -
                    else | 
| 171 | 
            +
                    else
         | 
| 172 172 | 
             
                      if [:default, :new].include? mode
         | 
| 173 173 | 
             
                        dir = File.dirname(literal)
         | 
| 174 174 | 
             
                        if !File.directory?(dir)
         | 
    
        data/lib/shellopts/dump.rb
    CHANGED
    
    | @@ -79,9 +79,9 @@ module ShellOpts | |
| 79 79 | 
             
                  def dump_idr(short = false)
         | 
| 80 80 | 
             
                    if short
         | 
| 81 81 | 
             
                      s = [
         | 
| 82 | 
            -
                          name, | 
| 83 | 
            -
                          argument? ? argument_type.name : nil, | 
| 84 | 
            -
                          optional? ? "?" : nil, | 
| 82 | 
            +
                          name,
         | 
| 83 | 
            +
                          argument? ? argument_type.name : nil,
         | 
| 84 | 
            +
                          optional? ? "?" : nil,
         | 
| 85 85 | 
             
                          repeatable? ? "*" : nil
         | 
| 86 86 | 
             
                      ].compact.join(" ")
         | 
| 87 87 | 
             
                      puts s
         | 
| @@ -89,9 +89,9 @@ module ShellOpts | |
| 89 89 | 
             
                      puts "#{name}: #{classname}"
         | 
| 90 90 | 
             
                      dump_attrs(
         | 
| 91 91 | 
             
                          :uid, :path, :attr, :ident, :name, :idents, :names,
         | 
| 92 | 
            -
                          :repeatable?, | 
| 93 | 
            -
                          :argument?, argument? && :argument_name, argument? && :argument_type, | 
| 94 | 
            -
                          :enum?, enum? && :argument_enum, | 
| 92 | 
            +
                          :repeatable?,
         | 
| 93 | 
            +
                          :argument?, argument? && :argument_name, argument? && :argument_type,
         | 
| 94 | 
            +
                          :enum?, enum? && :argument_enum,
         | 
| 95 95 | 
             
                          :optional?)
         | 
| 96 96 | 
             
                      indent { puts "brief: #{group.brief}" }
         | 
| 97 97 | 
             
                    end
         | 
| @@ -102,11 +102,11 @@ module ShellOpts | |
| 102 102 | 
             
                  def dump_idr(short = false)
         | 
| 103 103 | 
             
                    if short
         | 
| 104 104 | 
             
                      puts name
         | 
| 105 | 
            -
                      indent { | 
| 105 | 
            +
                      indent {
         | 
| 106 106 | 
             
                        options.each { |option| option.dump_idr(short) }
         | 
| 107 107 | 
             
                        commands.each { |command| command.dump_idr(short) }
         | 
| 108 108 | 
             
                        descrs.each { |descr| descr.dump_idr(short) }
         | 
| 109 | 
            -
                      } | 
| 109 | 
            +
                      }
         | 
| 110 110 | 
             
                    else
         | 
| 111 111 | 
             
                      puts "#{name}: #{classname}"
         | 
| 112 112 | 
             
                      dump_attrs :uid, :path, :ident, :name, :options, :commands, :specs, :descrs, :brief
         | 
    
        data/lib/shellopts/formatter.rb
    CHANGED
    
    | @@ -34,15 +34,15 @@ module ShellOpts | |
| 34 34 | 
             
                    width = [Formatter.rest, max_width].min
         | 
| 35 35 | 
             
                    if descrs.size == 0
         | 
| 36 36 | 
             
                      print (lead = Formatter.command_prefix || "")
         | 
| 37 | 
            -
                      indent(lead.size, ' ', bol: bol && lead == "") { | 
| 38 | 
            -
                        puts render(:multi, width) | 
| 37 | 
            +
                      indent(lead.size, ' ', bol: bol && lead == "") {
         | 
| 38 | 
            +
                        puts render(:multi, width)
         | 
| 39 39 | 
             
                      }
         | 
| 40 40 | 
             
                    else
         | 
| 41 41 | 
             
                      lead = Formatter.command_prefix || ""
         | 
| 42 42 | 
             
                      descrs.each { |descr|
         | 
| 43 43 | 
             
                        print lead
         | 
| 44 | 
            -
                        puts render(:multi, width, args: descr.text.split(' ')) | 
| 45 | 
            -
                      } | 
| 44 | 
            +
                        puts render(:multi, width, args: descr.text.split(' '))
         | 
| 45 | 
            +
                      }
         | 
| 46 46 | 
             
                    end
         | 
| 47 47 | 
             
                  end
         | 
| 48 48 |  | 
| @@ -120,7 +120,7 @@ module ShellOpts | |
| 120 120 | 
             
                      Command => "COMMAND"
         | 
| 121 121 | 
             
                    }
         | 
| 122 122 | 
             
                    seen_sections = {}
         | 
| 123 | 
            -
                    newline = false # True if a newline should be printed before child | 
| 123 | 
            +
                    newline = false # True if a newline should be printed before child
         | 
| 124 124 | 
             
                    indent {
         | 
| 125 125 | 
             
                      children.each { |child|
         | 
| 126 126 | 
             
                        klass = child.is_a?(Section) ?  section.key(child.name) : child.class
         | 
| @@ -174,7 +174,7 @@ module ShellOpts | |
| 174 174 | 
             
                module WrappedNode
         | 
| 175 175 | 
             
                  def puts_descr
         | 
| 176 176 | 
             
                    width = [Formatter.rest, Formatter::HELP_MAX_WIDTH].min
         | 
| 177 | 
            -
                    puts lines(width) | 
| 177 | 
            +
                    puts lines(width)
         | 
| 178 178 | 
             
                  end
         | 
| 179 179 | 
             
                end
         | 
| 180 180 |  | 
| @@ -220,7 +220,7 @@ module ShellOpts | |
| 220 220 | 
             
                HELP_INDENT = 4
         | 
| 221 221 |  | 
| 222 222 | 
             
                # Max. width of help text (not including indent)
         | 
| 223 | 
            -
                HELP_MAX_WIDTH = 85 | 
| 223 | 
            +
                HELP_MAX_WIDTH = 85
         | 
| 224 224 |  | 
| 225 225 | 
             
                # Command prefix when subject is a sub-command
         | 
| 226 226 | 
             
                def self.command_prefix() @command_prefix end
         | 
| @@ -279,7 +279,7 @@ module ShellOpts | |
| 279 279 | 
             
                # +fields+ is an array of [subject-string, descr-text] tuples where the
         | 
| 280 280 | 
             
                # descr is an array of words
         | 
| 281 281 | 
             
                def self.compute_columns(width, fields)
         | 
| 282 | 
            -
                  first_max = | 
| 282 | 
            +
                  first_max =
         | 
| 283 283 | 
             
                      fields.map { |first, _| first.size }.select { |size| size <= BRIEF_COL1_MAX_WIDTH }.max ||
         | 
| 284 284 | 
             
                      BRIEF_COL1_MIN_WIDTH
         | 
| 285 285 | 
             
                  second_max = fields.map { |_, second| second ? second&.map(&:size).sum + second.size - 1 : 0 }.max
         | 
    
        data/lib/shellopts/grammar.rb
    CHANGED
    
    | @@ -54,8 +54,8 @@ module ShellOpts | |
| 54 54 | 
             
                  # for the Program object. It is the dot-joined elements of path with
         | 
| 55 55 | 
             
                  # internal exclamation marks removed (eg. "cmd.opt" or "cmd.cmd!").
         | 
| 56 56 | 
             
                  # Initialize by the analyzer
         | 
| 57 | 
            -
                  def uid() | 
| 58 | 
            -
                    @uid ||= command && [command.uid, ident].compact.join(".").sub(/!\./, ".") | 
| 57 | 
            +
                  def uid()
         | 
| 58 | 
            +
                    @uid ||= command && [command.uid, ident].compact.join(".").sub(/!\./, ".")
         | 
| 59 59 | 
             
                  end
         | 
| 60 60 |  | 
| 61 61 | 
             
                  # Path from Program object and down to this node. Array of identifiers.
         | 
| @@ -142,6 +142,7 @@ module ShellOpts | |
| 142 142 | 
             
                  def repeatable?() @repeatable end
         | 
| 143 143 | 
             
                  def argument?() @argument end
         | 
| 144 144 | 
             
                  def optional?() @optional end
         | 
| 145 | 
            +
                  def list?() @list end
         | 
| 145 146 |  | 
| 146 147 | 
             
                  def integer?() @argument_type.is_a? IntegerArgument end
         | 
| 147 148 | 
             
                  def float?() @argument_type.is_a? FloatArgument end
         | 
| @@ -250,7 +251,7 @@ module ShellOpts | |
| 250 251 |  | 
| 251 252 | 
             
                  def key?(key) !self.[](key).nil? end
         | 
| 252 253 |  | 
| 253 | 
            -
                  # Mostly for debug. Has questional semantics because it only lists local keys | 
| 254 | 
            +
                  # Mostly for debug. Has questional semantics because it only lists local keys
         | 
| 254 255 | 
             
                  def keys() @options_hash.keys + @commands_hash.keys end
         | 
| 255 256 |  | 
| 256 257 | 
             
                  # Shorthand to get the associated Grammar::Command object from a Program
         | 
| @@ -312,7 +313,7 @@ module ShellOpts | |
| 312 313 | 
             
                  alias_method :spec, :parent
         | 
| 313 314 | 
             
                end
         | 
| 314 315 |  | 
| 315 | 
            -
                # DocNode object has no children but lines. | 
| 316 | 
            +
                # DocNode object has no children but lines.
         | 
| 316 317 | 
             
                #
         | 
| 317 318 | 
             
                class DocNode < Node
         | 
| 318 319 | 
             
                  # Array of :text tokens. Assigned by the parser
         | 
| @@ -80,15 +80,23 @@ module ShellOpts | |
| 80 80 | 
             
                  elsif !value.nil?
         | 
| 81 81 | 
             
                    error "No argument allowed for option '#{opt_name}'"
         | 
| 82 82 | 
             
                  end
         | 
| 83 | 
            -
             | 
| 83 | 
            +
             | 
| 84 84 | 
             
                  Command.add_option(option_command, Option.new(option, name, value))
         | 
| 85 85 | 
             
                end
         | 
| 86 86 |  | 
| 87 87 | 
             
                def interpret_option_value(option, name, value)
         | 
| 88 | 
            +
                  if option.list?
         | 
| 89 | 
            +
                    value.split(",").map { |elem| interpret_option_value_element(option, name, elem) }
         | 
| 90 | 
            +
                  else
         | 
| 91 | 
            +
                    interpret_option_value_element(option, name, value)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def interpret_option_value_element(option, name, elem)
         | 
| 88 96 | 
             
                  type = option.argument_type
         | 
| 89 | 
            -
                  if type.match?(name,  | 
| 90 | 
            -
                    type.convert( | 
| 91 | 
            -
                  elsif  | 
| 97 | 
            +
                  if type.match?(name, elem)
         | 
| 98 | 
            +
                    type.convert(elem)
         | 
| 99 | 
            +
                  elsif elem == ""
         | 
| 92 100 | 
             
                    nil
         | 
| 93 101 | 
             
                  else
         | 
| 94 102 | 
             
                    error type.message
         | 
    
        data/lib/shellopts/lexer.rb
    CHANGED
    
    | @@ -2,7 +2,7 @@ | |
| 2 2 | 
             
            module ShellOpts
         | 
| 3 3 | 
             
              class Line
         | 
| 4 4 | 
             
                attr_reader :source
         | 
| 5 | 
            -
                attr_reader :lineno | 
| 5 | 
            +
                attr_reader :lineno
         | 
| 6 6 | 
             
                attr_reader :charno
         | 
| 7 7 | 
             
                attr_reader :text
         | 
| 8 8 |  | 
| @@ -52,7 +52,7 @@ module ShellOpts | |
| 52 52 | 
             
                attr_reader :name # Name of program
         | 
| 53 53 | 
             
                attr_reader :source
         | 
| 54 54 | 
             
                attr_reader :tokens
         | 
| 55 | 
            -
             | 
| 55 | 
            +
             | 
| 56 56 | 
             
                def oneline?() @oneline end
         | 
| 57 57 |  | 
| 58 58 | 
             
                def initialize(name, source, oneline)
         | 
| @@ -88,7 +88,7 @@ module ShellOpts | |
| 88 88 | 
             
                      @tokens << Token.new(:blank, line.lineno, line.charno, "")
         | 
| 89 89 | 
             
                      next
         | 
| 90 90 | 
             
                    end
         | 
| 91 | 
            -
             | 
| 91 | 
            +
             | 
| 92 92 | 
             
                    # Ignore meta comments
         | 
| 93 93 | 
             
                    if line.charno < initial_indent
         | 
| 94 94 | 
             
                      next if line =~ /^#/
         | 
| @@ -129,8 +129,8 @@ module ShellOpts | |
| 129 129 | 
             
                            @tokens << Token.new(:usage_string, line.lineno, charno, source)
         | 
| 130 130 | 
             
                          when "++" # FIXME Rename argspec
         | 
| 131 131 | 
             
                            @tokens << Token.new(:spec, line.lineno, charno, "++")
         | 
| 132 | 
            -
                            words.shift_while { |c,w| | 
| 133 | 
            -
                              w =~ SPEC_RE and @tokens << Token.new(:argument, line.lineno, c, w) | 
| 132 | 
            +
                            words.shift_while { |c,w|
         | 
| 133 | 
            +
                              w =~ SPEC_RE and @tokens << Token.new(:argument, line.lineno, c, w)
         | 
| 134 134 | 
             
                            }
         | 
| 135 135 | 
             
                          when /^-|\+/
         | 
| 136 136 | 
             
                            @tokens << Token.new(:option, line.lineno, charno, word)
         | 
| @@ -143,7 +143,7 @@ module ShellOpts | |
| 143 143 | 
             
                      end
         | 
| 144 144 |  | 
| 145 145 | 
             
                      # TODO: Move to parser and remove @oneline from Lexer
         | 
| 146 | 
            -
                      (token = @tokens.last).kind != :brief || !oneline? or | 
| 146 | 
            +
                      (token = @tokens.last).kind != :brief || !oneline? or
         | 
| 147 147 | 
             
                          lexer_error token, "Briefs are only allowed in multi-line specifications"
         | 
| 148 148 |  | 
| 149 149 | 
             
                    # Paragraph lines
         | 
| @@ -151,13 +151,13 @@ module ShellOpts | |
| 151 151 | 
             
                      @tokens << Token.new(:text, line.lineno, line.charno, source)
         | 
| 152 152 | 
             
                    end
         | 
| 153 153 | 
             
                    # FIXME Not sure about this
         | 
| 154 | 
            -
            #       last_nonblank = @tokens.last | 
| 155 | 
            -
                    last_nonblank = @tokens.last if ![:blank, :usage_string, :argument].include? @tokens.last.kind | 
| 154 | 
            +
            #       last_nonblank = @tokens.last
         | 
| 155 | 
            +
                    last_nonblank = @tokens.last if ![:blank, :usage_string, :argument].include? @tokens.last.kind
         | 
| 156 156 | 
             
                  end
         | 
| 157 157 |  | 
| 158 158 | 
             
                  # Move arguments and briefs before first command if one-line source
         | 
| 159 159 | 
             
            #     if oneline? && cmd_index = @tokens.index { |token| token.kind == :command }
         | 
| 160 | 
            -
            #       @tokens = | 
| 160 | 
            +
            #       @tokens =
         | 
| 161 161 | 
             
            #           @tokens[0...cmd_index] +
         | 
| 162 162 | 
             
            #           @tokens[cmd_index..-1].partition { |token| ![:command, :option].include?(token.kind) }.flatten
         | 
| 163 163 | 
             
            #     end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            module ShellOpts
         | 
| 2 | 
            +
              # Option models an option as given by the user on the subcommand line.
         | 
| 3 | 
            +
              # Compiled options (and possibly aggregated) options are stored in the
         | 
| 4 | 
            +
              # Command#__option_values__ array
         | 
| 5 | 
            +
              class Option
         | 
| 6 | 
            +
                # Associated Grammar::Option object
         | 
| 7 | 
            +
                attr_reader :grammar
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                # The actual name used on the shell command-line (String)
         | 
| 10 | 
            +
                attr_reader :name
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                # Argument value or nil if not present. The value is a String, Integer,
         | 
| 13 | 
            +
                # or Float depending the on the type of the option
         | 
| 14 | 
            +
                attr_accessor :argument
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                forward_to :grammar,
         | 
| 17 | 
            +
                    :uid, :ident,
         | 
| 18 | 
            +
                    :repeatable?, :argument?, :integer?, :float?,
         | 
| 19 | 
            +
                    :file?, :enum?, :string?, :optional?, :list?,
         | 
| 20 | 
            +
                    :argument_name, :argument_type, :argument_enum,
         | 
| 21 | 
            +
                    :short_idents, :long_idents
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def initialize(grammar, name, argument)
         | 
| 24 | 
            +
                  @grammar, @name, @argument = grammar, name, argument
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
    
        data/lib/shellopts/parser.rb
    CHANGED
    
    | @@ -1,5 +1,6 @@ | |
| 1 1 |  | 
| 2 2 | 
             
            module ShellOpts
         | 
| 3 | 
            +
              # Extend Grammar classes with parse methods
         | 
| 3 4 | 
             
              module Grammar
         | 
| 4 5 | 
             
                class Node
         | 
| 5 6 | 
             
                  def parse() end
         | 
| @@ -28,13 +29,13 @@ module ShellOpts | |
| 28 29 | 
             
                  NAME_RE = /(?:#{SHORT_NAME_RE}|#{LONG_NAME_RE})(?:,#{LONG_NAME_RE})*/
         | 
| 29 30 |  | 
| 30 31 | 
             
                  def parse
         | 
| 31 | 
            -
                    token.source =~ /^(-|--|\+|\+\+)(#{NAME_RE})(?:=(.+?)( | 
| 32 | 
            +
                    token.source =~ /^(-|--|\+|\+\+)(#{NAME_RE})(?:=(.+?)(,\??|\?,?)?)?$/ or
         | 
| 32 33 | 
             
                        parser_error token, "Illegal option: #{token.source.inspect}"
         | 
| 33 34 | 
             
                    initial = $1
         | 
| 34 35 | 
             
                    name_list = $2
         | 
| 35 36 | 
             
                    arg = $3
         | 
| 36 | 
            -
                    optional = $4
         | 
| 37 | 
            -
             | 
| 37 | 
            +
                    @optional = $4&.include?(??) || false
         | 
| 38 | 
            +
                    @list = $4&.include?(?,) || false
         | 
| 38 39 | 
             
                    @repeatable = %w(+ ++).include?(initial)
         | 
| 39 40 |  | 
| 40 41 | 
             
                    @short_idents = []
         | 
| @@ -48,8 +49,8 @@ module ShellOpts | |
| 48 49 | 
             
                      end
         | 
| 49 50 | 
             
                    end
         | 
| 50 51 |  | 
| 51 | 
            -
                    names.each { |name| | 
| 52 | 
            -
                      name.size > 1 or | 
| 52 | 
            +
                    names.each { |name|
         | 
| 53 | 
            +
                      name.size > 1 or
         | 
| 53 54 | 
             
                          parser_error token, "Long names should be at least two characters long: '#{name}'"
         | 
| 54 55 | 
             
                    }
         | 
| 55 56 |  | 
| @@ -94,7 +95,6 @@ module ShellOpts | |
| 94 95 | 
             
                          @argument_name = arg
         | 
| 95 96 | 
             
                          @argument_type = StringType.new
         | 
| 96 97 | 
             
                      end
         | 
| 97 | 
            -
                      @optional = !optional.nil?
         | 
| 98 98 | 
             
                    else
         | 
| 99 99 | 
             
                      @argument_type = StringType.new
         | 
| 100 100 | 
             
                    end
         | 
| @@ -182,7 +182,7 @@ module ShellOpts | |
| 182 182 | 
             
                      when :option
         | 
| 183 183 | 
             
                        # Collect options into option groups if on the same line and not in
         | 
| 184 184 | 
             
                        # oneline mode
         | 
| 185 | 
            -
                        options = [token] + @tokens.shift_while { |follow| | 
| 185 | 
            +
                        options = [token] + @tokens.shift_while { |follow|
         | 
| 186 186 | 
             
                          !oneline && follow.kind == :option && follow.lineno == token.lineno
         | 
| 187 187 | 
             
                        }
         | 
| 188 188 | 
             
                        group = Grammar::OptionGroup.new(cmds.top, token)
         | 
| @@ -266,7 +266,7 @@ module ShellOpts | |
| 266 266 | 
             
                        else
         | 
| 267 267 | 
             
                          if nodes.top.is_a?(Grammar::Command) || nodes.top.is_a?(Grammar::OptionGroup)
         | 
| 268 268 | 
             
                            Grammar::Brief.new(nodes.top, token, token.source.sub(/\..*/, "")) if !nodes.top.brief
         | 
| 269 | 
            -
                            parent = nodes.top | 
| 269 | 
            +
                            parent = nodes.top
         | 
| 270 270 | 
             
                          else
         | 
| 271 271 | 
             
                            parent = nodes.top.parent
         | 
| 272 272 | 
             
                          end
         | 
    
        data/lib/shellopts/program.rb
    CHANGED
    
    | @@ -174,7 +174,7 @@ module ShellOpts | |
| 174 174 |  | 
| 175 175 | 
             
                # The parent command or nil. Initialized by #add_command
         | 
| 176 176 | 
             
                attr_accessor :__supercommand__
         | 
| 177 | 
            -
             | 
| 177 | 
            +
             | 
| 178 178 | 
             
                # The subcommand identifier (a Symbol incl. the exclamation mark) or nil
         | 
| 179 179 | 
             
                # if not present. Use #subcommand!, or the dynamically generated
         | 
| 180 180 | 
             
                # '#<identifier>!' method to get the actual subcommand object
         | 
| @@ -197,7 +197,7 @@ module ShellOpts | |
| 197 197 | 
             
                def __initialize__(grammar)
         | 
| 198 198 | 
             
                  @__grammar__ = grammar
         | 
| 199 199 | 
             
                  @__option_values__ = {}
         | 
| 200 | 
            -
                  @__option_list__ = [] | 
| 200 | 
            +
                  @__option_list__ = []
         | 
| 201 201 | 
             
                  @__option_hash__ = {}
         | 
| 202 202 | 
             
                  @__option_values__ = {}
         | 
| 203 203 | 
             
                  @__subcommand__ = nil
         | 
| @@ -209,22 +209,22 @@ module ShellOpts | |
| 209 209 | 
             
                  @__grammar__.options.each { |opt|
         | 
| 210 210 | 
             
                    if !opt.repeatable?
         | 
| 211 211 | 
             
                      self.instance_eval %(
         | 
| 212 | 
            -
                        def #{opt.attr}?() | 
| 213 | 
            -
                          @__option_values__.key?(:#{opt.attr}) | 
| 212 | 
            +
                        def #{opt.attr}?()
         | 
| 213 | 
            +
                          @__option_values__.key?(:#{opt.attr})
         | 
| 214 214 | 
             
                        end
         | 
| 215 215 | 
             
                      )
         | 
| 216 216 | 
             
                    end
         | 
| 217 | 
            -
             | 
| 217 | 
            +
             | 
| 218 218 | 
             
                    if opt.repeatable?
         | 
| 219 219 | 
             
                      if opt.argument?
         | 
| 220 220 | 
             
                        self.instance_eval %(
         | 
| 221 | 
            -
                          def #{opt.attr}?() | 
| 222 | 
            -
                            (@__option_values__[:#{opt.attr}]&.size || 0) > 0 | 
| 221 | 
            +
                          def #{opt.attr}?()
         | 
| 222 | 
            +
                            (@__option_values__[:#{opt.attr}]&.size || 0) > 0
         | 
| 223 223 | 
             
                          end
         | 
| 224 224 | 
             
                        )
         | 
| 225 225 | 
             
                        self.instance_eval %(
         | 
| 226 226 | 
             
                          def #{opt.attr}(default = [])
         | 
| 227 | 
            -
                            if @__option_values__.key?(:#{opt.attr}) | 
| 227 | 
            +
                            if @__option_values__.key?(:#{opt.attr})
         | 
| 228 228 | 
             
                              @__option_values__[:#{opt.attr}]
         | 
| 229 229 | 
             
                            else
         | 
| 230 230 | 
             
                              default
         | 
| @@ -233,12 +233,12 @@ module ShellOpts | |
| 233 233 | 
             
                        )
         | 
| 234 234 | 
             
                      else
         | 
| 235 235 | 
             
                        self.instance_eval %(
         | 
| 236 | 
            -
                          def #{opt.attr}?() | 
| 237 | 
            -
                            (@__option_values__[:#{opt.attr}] || 0) > 0 | 
| 236 | 
            +
                          def #{opt.attr}?()
         | 
| 237 | 
            +
                            (@__option_values__[:#{opt.attr}] || 0) > 0
         | 
| 238 238 | 
             
                          end
         | 
| 239 239 | 
             
                        )
         | 
| 240 240 | 
             
                        self.instance_eval %(
         | 
| 241 | 
            -
                          def #{opt.attr}(default = 0) | 
| 241 | 
            +
                          def #{opt.attr}(default = 0)
         | 
| 242 242 | 
             
                            if default > 0 && (@__option_values__[:#{opt.attr}] || 0) == 0
         | 
| 243 243 | 
             
                              default
         | 
| 244 244 | 
             
                            else
         | 
| @@ -251,7 +251,7 @@ module ShellOpts | |
| 251 251 | 
             
                    elsif opt.argument?
         | 
| 252 252 | 
             
                      self.instance_eval %(
         | 
| 253 253 | 
             
                        def #{opt.attr}(default = nil)
         | 
| 254 | 
            -
                          if @__option_values__.key?(:#{opt.attr}) | 
| 254 | 
            +
                          if @__option_values__.key?(:#{opt.attr})
         | 
| 255 255 | 
             
                            @__option_values__[:#{opt.attr}]
         | 
| 256 256 | 
             
                          else
         | 
| 257 257 | 
             
                            default
         | 
| @@ -261,8 +261,8 @@ module ShellOpts | |
| 261 261 |  | 
| 262 262 | 
             
                    else
         | 
| 263 263 | 
             
                      self.instance_eval %(
         | 
| 264 | 
            -
                        def #{opt.attr}() | 
| 265 | 
            -
                          @__option_values__.key?(:#{opt.attr}) | 
| 264 | 
            +
                        def #{opt.attr}()
         | 
| 265 | 
            +
                          @__option_values__.key?(:#{opt.attr})
         | 
| 266 266 | 
             
                        end
         | 
| 267 267 | 
             
                      )
         | 
| 268 268 | 
             
                    end
         | 
| @@ -279,7 +279,7 @@ module ShellOpts | |
| 279 279 | 
             
                  @__grammar__.commands.each { |cmd|
         | 
| 280 280 | 
             
                    next if cmd.attr.nil?
         | 
| 281 281 | 
             
                    self.instance_eval %(
         | 
| 282 | 
            -
                      def #{cmd.attr}() | 
| 282 | 
            +
                      def #{cmd.attr}()
         | 
| 283 283 | 
             
                        :#{cmd.attr} == __subcommand__ ? __subcommand__! : nil
         | 
| 284 284 | 
             
                      end
         | 
| 285 285 | 
             
                    )
         | 
| @@ -328,29 +328,4 @@ module ShellOpts | |
| 328 328 | 
             
                  @__debug__ = false
         | 
| 329 329 | 
             
                end
         | 
| 330 330 | 
             
              end
         | 
| 331 | 
            -
             | 
| 332 | 
            -
              # Option models an option as given by the user on the subcommand line.
         | 
| 333 | 
            -
              # Compiled options (and possibly aggregated) options are stored in the
         | 
| 334 | 
            -
              # Command#__option_values__ array
         | 
| 335 | 
            -
              class Option
         | 
| 336 | 
            -
                # Associated Grammar::Option object
         | 
| 337 | 
            -
                attr_reader :grammar
         | 
| 338 | 
            -
             | 
| 339 | 
            -
                # The actual name used on the shell command-line (String)
         | 
| 340 | 
            -
                attr_reader :name 
         | 
| 341 | 
            -
             | 
| 342 | 
            -
                # Argument value or nil if not present. The value is a String, Integer,
         | 
| 343 | 
            -
                # or Float depending the on the type of the option
         | 
| 344 | 
            -
                attr_accessor :argument
         | 
| 345 | 
            -
             | 
| 346 | 
            -
                forward_to :grammar, 
         | 
| 347 | 
            -
                    :uid, :ident,
         | 
| 348 | 
            -
                    :repeatable?, :argument?, :integer?, :float?,
         | 
| 349 | 
            -
                    :file?, :enum?, :string?, :optional?,
         | 
| 350 | 
            -
                    :argument_name, :argument_type, :argument_enum
         | 
| 351 | 
            -
             | 
| 352 | 
            -
                def initialize(grammar, name, argument)
         | 
| 353 | 
            -
                  @grammar, @name, @argument = grammar, name, argument
         | 
| 354 | 
            -
                end
         | 
| 355 | 
            -
              end
         | 
| 356 331 | 
             
            end
         | 
    
        data/lib/shellopts/renderer.rb
    CHANGED
    
    | @@ -26,7 +26,7 @@ require 'terminfo' | |
| 26 26 | 
             
            #       [cmd1|cmd2] ARG1 ARG2
         | 
| 27 27 | 
             
            #   cmd --all --beta
         | 
| 28 28 | 
             
            #       <commands> ARGS
         | 
| 29 | 
            -
            # | 
| 29 | 
            +
            #
         | 
| 30 30 | 
             
            module ShellOpts
         | 
| 31 31 | 
             
              module Grammar
         | 
| 32 32 | 
             
                class Option
         | 
| @@ -38,16 +38,23 @@ module ShellOpts | |
| 38 38 | 
             
                  #
         | 
| 39 39 | 
             
                  def render(format)
         | 
| 40 40 | 
             
                    constrain format, :enum, :long, :short
         | 
| 41 | 
            -
                    s = | 
| 41 | 
            +
                    s =
         | 
| 42 42 | 
             
                        case format
         | 
| 43 43 | 
             
                          when :enum; names.join(", ")
         | 
| 44 44 | 
             
                          when :long; name
         | 
| 45 45 | 
             
                          when :short; short_names.first || name
         | 
| 46 46 | 
             
                        else
         | 
| 47 47 | 
             
                          raise ArgumentError, "Illegal format: #{format.inspect}"
         | 
| 48 | 
            -
                        end | 
| 48 | 
            +
                        end
         | 
| 49 49 | 
             
                    if argument?
         | 
| 50 | 
            -
                       | 
| 50 | 
            +
                      short = long_idents.empty? || format == :short
         | 
| 51 | 
            +
                      arg = ""
         | 
| 52 | 
            +
                      arg += "=" if !short
         | 
| 53 | 
            +
                      arg += argument_name
         | 
| 54 | 
            +
                      arg += "..." if list?
         | 
| 55 | 
            +
                      arg = "[#{arg}]" if optional?
         | 
| 56 | 
            +
                      arg = " " + arg if short
         | 
| 57 | 
            +
                      s += arg
         | 
| 51 58 | 
             
                    else
         | 
| 52 59 | 
             
                      s
         | 
| 53 60 | 
             
                    end
         | 
| @@ -56,7 +63,7 @@ module ShellOpts | |
| 56 63 |  | 
| 57 64 | 
             
                class OptionGroup
         | 
| 58 65 | 
             
                  # Formats:
         | 
| 59 | 
            -
                  # | 
| 66 | 
            +
                  #
         | 
| 60 67 | 
             
                  #     :enum   -a, --all -r, --recursive
         | 
| 61 68 | 
             
                  #     :long   --all --recursive
         | 
| 62 69 | 
             
                  #     :short  -a -r
         | 
| @@ -115,7 +122,7 @@ module ShellOpts | |
| 115 122 |  | 
| 116 123 | 
             
                  # Force one line and compact options to "[OPTIONS]"
         | 
| 117 124 | 
             
                  def render_abbr
         | 
| 118 | 
            -
                    args = get_args | 
| 125 | 
            +
                    args = get_args
         | 
| 119 126 | 
             
                    ([name] + [options.empty? ? nil : "[OPTIONS]"] + args).compact.join(" ")
         | 
| 120 127 | 
             
                  end
         | 
| 121 128 |  | 
    
        data/lib/shellopts/token.rb
    CHANGED
    
    | @@ -4,7 +4,7 @@ module ShellOpts | |
| 4 4 | 
             
                # Each kind should have a corresponding Grammar class with the same name
         | 
| 5 5 | 
             
                KINDS = [
         | 
| 6 6 | 
             
                    :program, :section, :option, :command, :spec, :argument, :usage,
         | 
| 7 | 
            -
                    :usage_string, :brief, :text, :blank | 
| 7 | 
            +
                    :usage_string, :brief, :text, :blank
         | 
| 8 8 | 
             
                ]
         | 
| 9 9 |  | 
| 10 10 | 
             
                # Kind of token
         | 
| @@ -27,13 +27,13 @@ module ShellOpts | |
| 27 27 |  | 
| 28 28 | 
             
                forward_to :source, :to_s, :empty?
         | 
| 29 29 |  | 
| 30 | 
            -
                def pos(start_lineno = 1, start_charno = 1) | 
| 31 | 
            -
                  "#{start_lineno + lineno - 1}:#{start_charno + charno - 1}" | 
| 30 | 
            +
                def pos(start_lineno = 1, start_charno = 1)
         | 
| 31 | 
            +
                  "#{start_lineno + lineno - 1}:#{start_charno + charno - 1}"
         | 
| 32 32 | 
             
                end
         | 
| 33 33 |  | 
| 34 34 | 
             
                def to_s() source end
         | 
| 35 35 |  | 
| 36 | 
            -
                def inspect() | 
| 36 | 
            +
                def inspect()
         | 
| 37 37 | 
             
                  "<#{self.class.to_s.sub(/.*::/, "")} #{pos} #{kind.inspect} #{source.inspect}>"
         | 
| 38 38 | 
             
                end
         | 
| 39 39 |  | 
    
        data/lib/shellopts/version.rb
    CHANGED
    
    
    
        data/lib/shellopts.rb
    CHANGED
    
    | @@ -16,6 +16,7 @@ require_relative 'shellopts/stack.rb' | |
| 16 16 | 
             
            require_relative 'shellopts/token.rb'
         | 
| 17 17 | 
             
            require_relative 'shellopts/grammar.rb'
         | 
| 18 18 | 
             
            require_relative 'shellopts/program.rb'
         | 
| 19 | 
            +
            require_relative 'shellopts/option.rb'
         | 
| 19 20 | 
             
            require_relative 'shellopts/args.rb'
         | 
| 20 21 | 
             
            require_relative 'shellopts/lexer.rb'
         | 
| 21 22 | 
             
            require_relative 'shellopts/argument_type.rb'
         | 
| @@ -32,7 +33,7 @@ require_relative 'shellopts/dump.rb' | |
| 32 33 | 
             
            # Notes
         | 
| 33 34 | 
             
            #   * Two kinds of exceptions: Expected & unexpected. Expected exceptions are
         | 
| 34 35 | 
             
            #     RuntimeError or IOError. Unexpected exceptions are the rest. Both results
         | 
| 35 | 
            -
            #     in shellopts.failure messages if shellopts error handling is enabled | 
| 36 | 
            +
            #     in shellopts.failure messages if shellopts error handling is enabled
         | 
| 36 37 | 
             
            #   * Describe the difference between StandardError, RuntimeError, and IOError
         | 
| 37 38 | 
             
            #   * Add an #internal error handling for the production environment that
         | 
| 38 39 | 
             
            #     prints an intelligble error message and prettyfies stack dump. This
         | 
| @@ -60,7 +61,7 @@ module ShellOpts | |
| 60 61 | 
             
              #   <program>: <message>
         | 
| 61 62 | 
             
              #   Usage: <program> ...
         | 
| 62 63 | 
             
              #
         | 
| 63 | 
            -
              class Error < ShellOptsError; end | 
| 64 | 
            +
              class Error < ShellOptsError; end
         | 
| 64 65 |  | 
| 65 66 | 
             
              # Default class for program failures. Failures are raised on missing files or
         | 
| 66 67 | 
             
              # illegal paths. When ShellOpts handles the exception a message with the
         | 
| @@ -74,7 +75,7 @@ module ShellOpts | |
| 74 75 | 
             
              # source. Messages are formatted as '<file> <lineno>:<charno> <message>' when
         | 
| 75 76 | 
             
              # handled by ShellOpts
         | 
| 76 77 | 
             
              class CompilerError < ShellOptsError; end
         | 
| 77 | 
            -
              class LexerError < CompilerError; end | 
| 78 | 
            +
              class LexerError < CompilerError; end
         | 
| 78 79 | 
             
              class ParserError < CompilerError; end
         | 
| 79 80 | 
             
              class AnalyzerError < CompilerError; end
         | 
| 80 81 |  | 
| @@ -92,7 +93,7 @@ module ShellOpts | |
| 92 93 | 
             
                attr_reader :spec
         | 
| 93 94 |  | 
| 94 95 | 
             
                # Array of arguments. Initialized by #interpret
         | 
| 95 | 
            -
                attr_reader :argv | 
| 96 | 
            +
                attr_reader :argv
         | 
| 96 97 |  | 
| 97 98 | 
             
                # Grammar. Grammar::Program object. Initialized by #compile
         | 
| 98 99 | 
             
                attr_reader :grammar
         | 
| @@ -139,10 +140,10 @@ module ShellOpts | |
| 139 140 | 
             
                attr_reader :tokens
         | 
| 140 141 | 
             
                alias_method :ast, :grammar
         | 
| 141 142 |  | 
| 142 | 
            -
                def initialize(name: nil, | 
| 143 | 
            +
                def initialize(name: nil,
         | 
| 143 144 | 
             
                    # Options
         | 
| 144 | 
            -
                    help: true, | 
| 145 | 
            -
                    version: true, | 
| 145 | 
            +
                    help: true,
         | 
| 146 | 
            +
                    version: true,
         | 
| 146 147 | 
             
                    silent: nil,
         | 
| 147 148 | 
             
                    quiet: nil,
         | 
| 148 149 | 
             
                    verbose: nil,
         | 
| @@ -157,7 +158,7 @@ module ShellOpts | |
| 157 158 | 
             
                    # Let exceptions through
         | 
| 158 159 | 
             
                    exception: false
         | 
| 159 160 | 
             
                  )
         | 
| 160 | 
            -
             | 
| 161 | 
            +
             | 
| 161 162 | 
             
                  @name = name || File.basename($PROGRAM_NAME)
         | 
| 162 163 | 
             
                  @help = help
         | 
| 163 164 | 
             
                  @version = version || (version.nil? && !version_number.nil?)
         | 
| @@ -187,24 +188,24 @@ module ShellOpts | |
| 187 188 | 
             
                    verbose_spec = (@verbose == true ? "+v,verbose" : @verbose)
         | 
| 188 189 | 
             
                    debug_spec = (@debug == true ? "--debug" : @debug)
         | 
| 189 190 |  | 
| 190 | 
            -
                    @silent_option = | 
| 191 | 
            +
                    @silent_option =
         | 
| 191 192 | 
             
                        ast.inject_option(silent_spec, "Quiet", "Do not write anything to standard output") if @silent
         | 
| 192 | 
            -
                    @quiet_option = | 
| 193 | 
            +
                    @quiet_option =
         | 
| 193 194 | 
             
                        ast.inject_option(quiet_spec, "Quiet", "Do not write anything to standard output") if @quiet
         | 
| 194 | 
            -
                    @verbose_option = | 
| 195 | 
            +
                    @verbose_option =
         | 
| 195 196 | 
             
                        ast.inject_option(verbose_spec, "Increase verbosity", "Write verbose output") if @verbose
         | 
| 196 | 
            -
                    @debug_option = | 
| 197 | 
            +
                    @debug_option =
         | 
| 197 198 | 
             
                        ast.inject_option(debug_spec, "Write debug information") if @debug
         | 
| 198 | 
            -
                    @help_option = | 
| 199 | 
            +
                    @help_option =
         | 
| 199 200 | 
             
                        ast.inject_option(help_spec, "Write short or long help") { |option|
         | 
| 200 | 
            -
                          short_option = option.short_names.first | 
| 201 | 
            +
                          short_option = option.short_names.first
         | 
| 201 202 | 
             
                          long_option = option.long_names.first
         | 
| 202 203 | 
             
                          [
         | 
| 203 204 | 
             
                            short_option && "#{short_option} prints a brief help text",
         | 
| 204 205 | 
             
                            long_option && "#{long_option} prints a longer man-style description of the command"
         | 
| 205 206 | 
             
                          ].compact.join(", ")
         | 
| 206 207 | 
             
                        } if @help
         | 
| 207 | 
            -
                    @version_option = | 
| 208 | 
            +
                    @version_option =
         | 
| 208 209 | 
             
                        ast.inject_option(version_spec, "Write version number and exit") if @version
         | 
| 209 210 |  | 
| 210 211 | 
             
                    @grammar = Analyzer.analyze(ast)
         | 
| @@ -216,7 +217,7 @@ module ShellOpts | |
| 216 217 | 
             
                # ShellOpts::Args tuple
         | 
| 217 218 | 
             
                #
         | 
| 218 219 | 
             
                def interpret(argv)
         | 
| 219 | 
            -
                  handle_exceptions { | 
| 220 | 
            +
                  handle_exceptions {
         | 
| 220 221 | 
             
                    @argv = argv.dup
         | 
| 221 222 | 
             
                    @program, @args = Interpreter.interpret(grammar, argv, float: float, exception: exception)
         | 
| 222 223 |  | 
| @@ -356,7 +357,7 @@ module ShellOpts | |
| 356 357 | 
             
                    char_z = 0
         | 
| 357 358 |  | 
| 358 359 | 
             
                    (0 ... text_lines.size).each { |text_i|
         | 
| 359 | 
            -
                      curr_char_i, curr_char_z = | 
| 360 | 
            +
                      curr_char_i, curr_char_z =
         | 
| 360 361 | 
             
                          LCS.find_longest_common_substring_index(text_lines[text_i], spec_lines.first.strip)
         | 
| 361 362 | 
             
                      if curr_char_z > char_z
         | 
| 362 363 | 
             
                        line_i = text_i
         | 
| @@ -372,7 +373,7 @@ module ShellOpts | |
| 372 373 | 
             
                        compare_lines(text_lines[text_i + spec_i], spec_lines[spec_i])
         | 
| 373 374 | 
             
                      }
         | 
| 374 375 | 
             
                    } or return [nil, nil]
         | 
| 375 | 
            -
                    char_i, char_z = | 
| 376 | 
            +
                    char_i, char_z =
         | 
| 376 377 | 
             
                        LCS.find_longest_common_substring_index(text_lines[line_i], spec_lines.first.strip)
         | 
| 377 378 | 
             
                    [line_i, char_i || 0]
         | 
| 378 379 | 
             
                  end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: shellopts
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2. | 
| 4 | 
            +
              version: 2.5.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Claus Rasmussen
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-09-15 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: forward_to
         | 
| @@ -147,6 +147,7 @@ files: | |
| 147 147 | 
             
            - lib/shellopts/grammar.rb
         | 
| 148 148 | 
             
            - lib/shellopts/interpreter.rb
         | 
| 149 149 | 
             
            - lib/shellopts/lexer.rb
         | 
| 150 | 
            +
            - lib/shellopts/option.rb
         | 
| 150 151 | 
             
            - lib/shellopts/parser.rb
         | 
| 151 152 | 
             
            - lib/shellopts/program.rb
         | 
| 152 153 | 
             
            - lib/shellopts/renderer.rb
         |