shellopts 2.0.0.pre.14 → 2.0.2
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/.gitignore +2 -1
- data/.ruby-version +1 -1
- data/README.md +201 -267
- data/TODO +37 -5
- data/doc/format.rb +95 -0
- data/doc/grammar.txt +27 -0
- data/doc/syntax.rb +110 -0
- data/doc/syntax.txt +10 -0
- data/lib/ext/array.rb +62 -0
- data/lib/ext/forward_to.rb +15 -0
- data/lib/ext/lcs.rb +34 -0
- data/lib/shellopts/analyzer.rb +130 -0
- data/lib/shellopts/ansi.rb +8 -0
- data/lib/shellopts/args.rb +29 -21
- data/lib/shellopts/argument_type.rb +139 -0
- data/lib/shellopts/dump.rb +158 -0
- data/lib/shellopts/formatter.rb +292 -92
- data/lib/shellopts/grammar.rb +375 -0
- data/lib/shellopts/interpreter.rb +103 -0
- data/lib/shellopts/lexer.rb +175 -0
- data/lib/shellopts/parser.rb +293 -0
- data/lib/shellopts/program.rb +279 -0
- data/lib/shellopts/renderer.rb +227 -0
- data/lib/shellopts/stack.rb +7 -0
- data/lib/shellopts/token.rb +44 -0
- data/lib/shellopts/version.rb +1 -1
- data/lib/shellopts.rb +360 -3
- data/main +1180 -0
- data/shellopts.gemspec +8 -14
- metadata +86 -41
- data/lib/ext/algorithm.rb +0 -14
- data/lib/ext/ruby_env.rb +0 -8
- data/lib/shellopts/ast/command.rb +0 -112
- data/lib/shellopts/ast/dump.rb +0 -28
- data/lib/shellopts/ast/option.rb +0 -15
- data/lib/shellopts/ast/parser.rb +0 -106
- data/lib/shellopts/constants.rb +0 -88
- data/lib/shellopts/exceptions.rb +0 -21
- data/lib/shellopts/grammar/analyzer.rb +0 -76
- data/lib/shellopts/grammar/command.rb +0 -87
- data/lib/shellopts/grammar/dump.rb +0 -56
- data/lib/shellopts/grammar/lexer.rb +0 -56
- data/lib/shellopts/grammar/option.rb +0 -55
- data/lib/shellopts/grammar/parser.rb +0 -78
    
        data/doc/format.rb
    ADDED
    
    | @@ -0,0 +1,95 @@ | |
| 1 | 
            +
                # One-line format in prioritized order
         | 
| 2 | 
            +
                # ------------------------------------
         | 
| 3 | 
            +
                #
         | 
| 4 | 
            +
                #   cmd -a -b -c [CMD|CMD] ARGS
         | 
| 5 | 
            +
                #   cmd -a -b -c <command> ARGS
         | 
| 6 | 
            +
                #   cmd <options> [CMD|CMD] <ARGS>
         | 
| 7 | 
            +
                #   cmd <options> <command> <ARGS>
         | 
| 8 | 
            +
                #
         | 
| 9 | 
            +
                # Multiline format
         | 
| 10 | 
            +
                # ----------------
         | 
| 11 | 
            +
                #
         | 
| 12 | 
            +
                #   cmd -a -b -c [CMD|CMD] ARGS   # <- only if no subcommand options or arguments
         | 
| 13 | 
            +
                #
         | 
| 14 | 
            +
                #   cmd -a -b -c <command> ARGS
         | 
| 15 | 
            +
                #       subcmd -e -f -g ARGS
         | 
| 16 | 
            +
                #       subcmd -h -i -j ARGS
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                #   cmd -a -b -c 
         | 
| 19 | 
            +
                #       -d -e
         | 
| 20 | 
            +
                #       [CMD|CMD] ARGS
         | 
| 21 | 
            +
                #
         | 
| 22 | 
            +
                #   cmd -a -b -c 
         | 
| 23 | 
            +
                #       -d -e
         | 
| 24 | 
            +
                #       <command> ARGS
         | 
| 25 | 
            +
                #
         | 
| 26 | 
            +
                # Brief format
         | 
| 27 | 
            +
                # ------------
         | 
| 28 | 
            +
                #
         | 
| 29 | 
            +
                #   Name - Brief
         | 
| 30 | 
            +
                #
         | 
| 31 | 
            +
                #   Usage: 
         | 
| 32 | 
            +
                #     cmd -a -b -c [CMD|CMD] ARGS
         | 
| 33 | 
            +
                #
         | 
| 34 | 
            +
                #   Options:
         | 
| 35 | 
            +
                #     -a                    Brief
         | 
| 36 | 
            +
                #     -b                    Brief
         | 
| 37 | 
            +
                #     -c                    Brief
         | 
| 38 | 
            +
                #
         | 
| 39 | 
            +
                #   Commands:
         | 
| 40 | 
            +
                #     CMD --opts ARGS       Brief
         | 
| 41 | 
            +
                #     CMD --opts ARGS_THAT_TAKES_UP_A_LOT_OF_SPACE       
         | 
| 42 | 
            +
                #                           Brief
         | 
| 43 | 
            +
                #
         | 
| 44 | 
            +
                # Brief Command
         | 
| 45 | 
            +
                #     CMD --opts ARGS       Brief
         | 
| 46 | 
            +
                #     CMD --opts ARGS_THAT_TAKES_UP_A_LOT_OF_SPACE       
         | 
| 47 | 
            +
                #                           Brief
         | 
| 48 | 
            +
                #
         | 
| 49 | 
            +
                # Brief Option
         | 
| 50 | 
            +
                #   -a            Brief
         | 
| 51 | 
            +
                #   -b=a_very_long_option
         | 
| 52 | 
            +
                #                 Brief
         | 
| 53 | 
            +
                #    
         | 
| 54 | 
            +
                #
         | 
| 55 | 
            +
                # Doc format
         | 
| 56 | 
            +
                # ----------
         | 
| 57 | 
            +
                #
         | 
| 58 | 
            +
                #   Name
         | 
| 59 | 
            +
                #     Name - Brief
         | 
| 60 | 
            +
                #
         | 
| 61 | 
            +
                #   Usage:
         | 
| 62 | 
            +
                #     cmd -a -b -c [CMD|CMD] ARGS
         | 
| 63 | 
            +
                #
         | 
| 64 | 
            +
                #   Description
         | 
| 65 | 
            +
                #     Descr
         | 
| 66 | 
            +
                #
         | 
| 67 | 
            +
                #   Options:
         | 
| 68 | 
            +
                #     -a
         | 
| 69 | 
            +
                #       Descr
         | 
| 70 | 
            +
                #     -b
         | 
| 71 | 
            +
                #       Descr
         | 
| 72 | 
            +
                #     -c, --aliases
         | 
| 73 | 
            +
                #       Descr
         | 
| 74 | 
            +
                #
         | 
| 75 | 
            +
                #   Commands:
         | 
| 76 | 
            +
                #     CMD -d -e -f ARGS
         | 
| 77 | 
            +
                #       Descr
         | 
| 78 | 
            +
                #
         | 
| 79 | 
            +
                #       -d
         | 
| 80 | 
            +
                #         Descr
         | 
| 81 | 
            +
                #       -e
         | 
| 82 | 
            +
                #         Descr
         | 
| 83 | 
            +
                #       -f 
         | 
| 84 | 
            +
                #         Descr
         | 
| 85 | 
            +
                #
         | 
| 86 | 
            +
                #     CMD -g -h -i ARGS
         | 
| 87 | 
            +
                #       Descr
         | 
| 88 | 
            +
                #
         | 
| 89 | 
            +
                #       -g
         | 
| 90 | 
            +
                #         Descr
         | 
| 91 | 
            +
                #       -h
         | 
| 92 | 
            +
                #         Descr
         | 
| 93 | 
            +
                #       -i 
         | 
| 94 | 
            +
                #         Descr
         | 
| 95 | 
            +
                #
         | 
    
        data/doc/grammar.txt
    ADDED
    
    | @@ -0,0 +1,27 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            Optionn Grammar
         | 
| 3 | 
            +
              [ "+" ] name-list [ "=" [ label ] [ ":" [ "#" | "$" | enum | special-constant ] ] [ "?" ] ]
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              -a=               # Renders as -a=
         | 
| 6 | 
            +
              -a=#              # Renders as -a=INT
         | 
| 7 | 
            +
              -b=$              # Renders as -b=NUM
         | 
| 8 | 
            +
              -c=a,b,c          # Renders as -c=a|b|c
         | 
| 9 | 
            +
              -d=3..5           # Renders as -d=3..5
         | 
| 10 | 
            +
              -e=:DIR           # Renders as -e=DIR
         | 
| 11 | 
            +
              -f=:FILE          # Renders as -f=FILE
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              -o=COUNT          
         | 
| 14 | 
            +
              -a=COUNT:#
         | 
| 15 | 
            +
              -b=COUNT:$
         | 
| 16 | 
            +
              -c=COUNT:a,b,c
         | 
| 17 | 
            +
              -e=DIR:DIRPATH
         | 
| 18 | 
            +
              -f=FILE:FILEPATH
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            Special constants
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      Exist   Missing   Optional
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              File    FILE    -         FILEPATH
         | 
| 25 | 
            +
              Dir     DIR     -         DIRPATH
         | 
| 26 | 
            +
              Node    NODE    NEW       PATH
         | 
| 27 | 
            +
             | 
    
        data/doc/syntax.rb
    ADDED
    
    | @@ -0,0 +1,110 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            # MAN pages
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # NAME
         | 
| 5 | 
            +
            #   #{$PROGRAM_NAME} - #{BRIEF || spec.summary}
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            # USAGE
         | 
| 8 | 
            +
            #   #{USAGE || shellopts.usage}
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # DESCRIPTION
         | 
| 11 | 
            +
            #   #{DESCRIPTION || spec.description}
         | 
| 12 | 
            +
            #
         | 
| 13 | 
            +
            # OPTIONS
         | 
| 14 | 
            +
            #   #{OPTIONS || shellopts.options}
         | 
| 15 | 
            +
            #
         | 
| 16 | 
            +
            # COMMANDS
         | 
| 17 | 
            +
            #   #{COMMANDS || shellopts.commands}
         | 
| 18 | 
            +
            #   
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            # Help output
         | 
| 21 | 
            +
            #
         | 
| 22 | 
            +
            # #{BRIEF}
         | 
| 23 | 
            +
            #
         | 
| 24 | 
            +
            # Usage: #{USAGE || shellopts.usage}
         | 
| 25 | 
            +
            #
         | 
| 26 | 
            +
            # Options:
         | 
| 27 | 
            +
            #   #{OPTIONS_IN_SHORT_FORMAT | shellopts.options_in_short_format}
         | 
| 28 | 
            +
            #
         | 
| 29 | 
            +
            # Commands
         | 
| 30 | 
            +
            #   #{COMMANDS_IN_SHORT_FORMMAT | shellopts.commands_in_short_format}
         | 
| 31 | 
            +
            #
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            h = %(
         | 
| 34 | 
            +
              -a,all # Include all files
         | 
| 35 | 
            +
              -f=FILE # Use this file
         | 
| 36 | 
            +
            )
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            # Default options
         | 
| 39 | 
            +
            #   <defaults>
         | 
| 40 | 
            +
            #     Can be processed by ShellOpts.process_defaults
         | 
| 41 | 
            +
            #     ShellOpts.make(SPEC, ARGV, defaults: true)
         | 
| 42 | 
            +
            #
         | 
| 43 | 
            +
            # Options
         | 
| 44 | 
            +
            #   -a,b,long
         | 
| 45 | 
            +
            #   --long
         | 
| 46 | 
            +
            #   -a=FILE
         | 
| 47 | 
            +
            #   -e,env,environment=ENVIRONMENT:p,d,t,prod,dev,test,production,development
         | 
| 48 | 
            +
            #   -i=# # Integer
         | 
| 49 | 
            +
            #   -o=optional?
         | 
| 50 | 
            +
            #   --verbose* # Repeated
         | 
| 51 | 
            +
            #   -a=V* # Repeated with argument
         | 
| 52 | 
            +
            #   -a=V?* # Repeated, optionally with argument
         | 
| 53 | 
            +
            #
         | 
| 54 | 
            +
            # Arguments
         | 
| 55 | 
            +
            #   Arguments has to be on one line (per ++ or --)
         | 
| 56 | 
            +
            #
         | 
| 57 | 
            +
            #   ++ ARG...
         | 
| 58 | 
            +
            #     Subcommand arguments
         | 
| 59 | 
            +
            #   -- ARG...
         | 
| 60 | 
            +
            #     Command arguments. Multiple -- definitions are allowed
         | 
| 61 | 
            +
            #
         | 
| 62 | 
            +
            #   [ARG]
         | 
| 63 | 
            +
            #     Optional argument
         | 
| 64 | 
            +
            #   ARG
         | 
| 65 | 
            +
            #     Mandatory argument
         | 
| 66 | 
            +
            #   ARG...
         | 
| 67 | 
            +
            #     Repeated argument. At least one argument is mandatory
         | 
| 68 | 
            +
            #   [ARG...]
         | 
| 69 | 
            +
            #     Optionally repeated argument
         | 
| 70 | 
            +
            #
         | 
| 71 | 
            +
            # SPECIAL ALTERNATE VALUES OR ARGUMENTS
         | 
| 72 | 
            +
            #   FILE:EFILE
         | 
| 73 | 
            +
            #     Existing file. "FILE" will be used as name of the value
         | 
| 74 | 
            +
            #   PATH:EPATH
         | 
| 75 | 
            +
            #     Existing file or directory. "PATH" will be used as the name of the value
         | 
| 76 | 
            +
            #   DIRECTORY:EDIR
         | 
| 77 | 
            +
            #     Existing directory. "DIRECTORY" will be used as name of the value
         | 
| 78 | 
            +
            #
         | 
| 79 | 
            +
            # SPEC = %(
         | 
| 80 | 
            +
            #   # Common options
         | 
| 81 | 
            +
            #   -f,file=FILE
         | 
| 82 | 
            +
            #   -- ARG ARG
         | 
| 83 | 
            +
            #
         | 
| 84 | 
            +
            #   # More options
         | 
| 85 | 
            +
            #   -m,mode=MODE
         | 
| 86 | 
            +
            #   -- ARG ARG ARG
         | 
| 87 | 
            +
            # )
         | 
| 88 | 
            +
            #
         | 
| 89 | 
            +
            # How to make
         | 
| 90 | 
            +
            #   cp [OPTION]... [-T] SOURCE DEST
         | 
| 91 | 
            +
            #   cp [OPTION]... SOURCE... DIRECTORY
         | 
| 92 | 
            +
            #   cp [OPTION]... -t DIRECTORY SOURCE...
         | 
| 93 | 
            +
            #
         | 
| 94 | 
            +
            #   USAGE = %(
         | 
| 95 | 
            +
            #     cp [OPTION]... [-T] SOURCE DEST
         | 
| 96 | 
            +
            #     cp [OPTION]... SOURCE... DIRECTORY
         | 
| 97 | 
            +
            #     cp [OPTION]... -t DIRECTORY SOURCE...
         | 
| 98 | 
            +
            #   )
         | 
| 99 | 
            +
            #
         | 
| 100 | 
            +
            #   SPEC = %(
         | 
| 101 | 
            +
            #     -r,recursive
         | 
| 102 | 
            +
            #  
         | 
| 103 | 
            +
            #     -T,no-target-directory
         | 
| 104 | 
            +
            #  
         | 
| 105 | 
            +
            #     -t,target_directory=DIRECTORY:EDIR
         | 
| 106 | 
            +
            #   )
         | 
| 107 | 
            +
            #   
         | 
| 108 | 
            +
             | 
| 109 | 
            +
             | 
| 110 | 
            +
             | 
    
        data/doc/syntax.txt
    ADDED
    
    
    
        data/lib/ext/array.rb
    ADDED
    
    | @@ -0,0 +1,62 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            module Ext
         | 
| 3 | 
            +
              module Array
         | 
| 4 | 
            +
                module ShiftWhile
         | 
| 5 | 
            +
                  refine ::Array do
         | 
| 6 | 
            +
                    # The algorithm ensures that the block sees the array as if the current
         | 
| 7 | 
            +
                    # element has already been removed
         | 
| 8 | 
            +
                    def shift_while(&block)
         | 
| 9 | 
            +
                      r = []
         | 
| 10 | 
            +
                      while value = self.shift
         | 
| 11 | 
            +
                        if !block.call(value)
         | 
| 12 | 
            +
                          self.unshift value
         | 
| 13 | 
            +
                          break
         | 
| 14 | 
            +
                        else
         | 
| 15 | 
            +
                          r << value
         | 
| 16 | 
            +
                        end
         | 
| 17 | 
            +
                      end
         | 
| 18 | 
            +
                      r
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                module PopWhile
         | 
| 24 | 
            +
                  refine ::Array do
         | 
| 25 | 
            +
                    # The algorithm ensures that the block sees the array as if the current
         | 
| 26 | 
            +
                    # element has already been removed
         | 
| 27 | 
            +
                    def pop_while(&block)
         | 
| 28 | 
            +
                      r = []
         | 
| 29 | 
            +
                      while value = self.pop
         | 
| 30 | 
            +
                        if !block.call(value)
         | 
| 31 | 
            +
                          self.push value
         | 
| 32 | 
            +
                          break
         | 
| 33 | 
            +
                        else
         | 
| 34 | 
            +
                          r << value
         | 
| 35 | 
            +
                        end
         | 
| 36 | 
            +
                      end
         | 
| 37 | 
            +
                      r
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                module Wrap
         | 
| 43 | 
            +
                  refine ::Array do
         | 
| 44 | 
            +
                    # Concatenate strings into lines that are at most +width+ characters wide
         | 
| 45 | 
            +
                    def wrap(width, curr = 0)
         | 
| 46 | 
            +
                      lines = [[]]
         | 
| 47 | 
            +
                      curr -= 1 # Simplifies conditions below
         | 
| 48 | 
            +
                      each { |word|
         | 
| 49 | 
            +
                        if curr + 1 + word.size <= width
         | 
| 50 | 
            +
                          lines.last << word
         | 
| 51 | 
            +
                          curr += 1 + word.size
         | 
| 52 | 
            +
                        else
         | 
| 53 | 
            +
                          lines << [word]
         | 
| 54 | 
            +
                          curr = word.size
         | 
| 55 | 
            +
                        end
         | 
| 56 | 
            +
                      }
         | 
| 57 | 
            +
                      lines.map! { |words| words.join(" ") }
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            require 'forward_to'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module ForwardTo
         | 
| 5 | 
            +
              def forward_self_to(target, *methods)
         | 
| 6 | 
            +
                for method in Array(methods).flatten
         | 
| 7 | 
            +
                  if method =~ /=$/
         | 
| 8 | 
            +
                    class_eval("def self.#{method}(*args) #{target}.#{method}(*args) end")
         | 
| 9 | 
            +
                  else
         | 
| 10 | 
            +
                    class_eval("def self.#{method}(*args, &block) #{target}.#{method}(*args, &block) end")
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
             | 
    
        data/lib/ext/lcs.rb
    ADDED
    
    | @@ -0,0 +1,34 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            # https://gist.github.com/Joseph-N/fbf061aa2347ed2c104f0b3fe1a5b9f2
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # TODO: Move to String and make find_longest_command_substring_index return a
         | 
| 5 | 
            +
            # range
         | 
| 6 | 
            +
            module LCS
         | 
| 7 | 
            +
              def self.find_longest_common_substring(s1, s2)
         | 
| 8 | 
            +
                i, z = find_longest_command_substring_index(s1, s2)
         | 
| 9 | 
            +
                s1[i .. i + z]
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def self.find_longest_common_substring_index(s1, s2)
         | 
| 13 | 
            +
                if (s1 == "" || s2 == "")
         | 
| 14 | 
            +
                  return [0,0]
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
                m = Array.new(s1.length){ [0] * s2.length }
         | 
| 17 | 
            +
                longest_length, longest_end_pos = 0,0
         | 
| 18 | 
            +
                (0 .. s1.length - 1).each do |x|
         | 
| 19 | 
            +
                  (0 .. s2.length - 1).each do |y|
         | 
| 20 | 
            +
                    if s1[x] == s2[y]
         | 
| 21 | 
            +
                      m[x][y] = 1
         | 
| 22 | 
            +
                      if (x > 0 && y > 0)
         | 
| 23 | 
            +
                        m[x][y] += m[x-1][y-1]
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
                      if m[x][y] > longest_length
         | 
| 26 | 
            +
                        longest_length = m[x][y]
         | 
| 27 | 
            +
                        longest_end_pos = x
         | 
| 28 | 
            +
                      end
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
                [longest_end_pos - longest_length + 1, longest_length]
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
| @@ -0,0 +1,130 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            module ShellOpts
         | 
| 3 | 
            +
              module Grammar
         | 
| 4 | 
            +
                class Node
         | 
| 5 | 
            +
                  def remove_brief_nodes
         | 
| 6 | 
            +
                    children.delete_if { |node| node.is_a?(Brief) }
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def remove_arg_descr_nodes
         | 
| 10 | 
            +
                    children.delete_if { |node| node.is_a?(ArgDescr) }
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def remove_arg_spec_nodes
         | 
| 14 | 
            +
                    children.delete_if { |node| node.is_a?(ArgSpec) }
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def analyzer_error(token, message) 
         | 
| 18 | 
            +
                    raise AnalyzerError.new(token), message 
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                class Command
         | 
| 23 | 
            +
                  def set_supercommand
         | 
| 24 | 
            +
                    commands.each { |child| child.instance_variable_set(:@supercommand, self) }
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def collect_options
         | 
| 28 | 
            +
                    @options = option_groups.map(&:options).flatten
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  # Move options before first command
         | 
| 32 | 
            +
                  def reorder_options
         | 
| 33 | 
            +
                    if commands.any?
         | 
| 34 | 
            +
                      if i = children.find_index { |child| child.is_a?(Command) }
         | 
| 35 | 
            +
                        options, rest = children[i+1..-1].partition { |child| child.is_a?(OptionGroup) }
         | 
| 36 | 
            +
                        @children = children[0, i] + options + children[i..i] + rest
         | 
| 37 | 
            +
                      end
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def compute_option_hashes
         | 
| 42 | 
            +
                    options.each { |option|
         | 
| 43 | 
            +
                      option.idents.zip(option.names).each { |ident, name|
         | 
| 44 | 
            +
                        !@options_hash.key?(name) or 
         | 
| 45 | 
            +
                            analyzer_error option.token, "Duplicate option name: #{name}"
         | 
| 46 | 
            +
                        @options_hash[name] = option
         | 
| 47 | 
            +
                        !@options_hash.key?(ident) or 
         | 
| 48 | 
            +
                            analyzer_error option.token, "Can't use both #{@options_hash[ident].name} and #{name}"
         | 
| 49 | 
            +
                        @options_hash[ident] = option
         | 
| 50 | 
            +
                      }
         | 
| 51 | 
            +
                    }
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def compute_command_hashes
         | 
| 55 | 
            +
                    commands.each { |command|
         | 
| 56 | 
            +
                      # TODO Check for dash-collision
         | 
| 57 | 
            +
                      !@commands_hash.key?(command.name) or 
         | 
| 58 | 
            +
                          analyzer_error command.token, "Duplicate command name: #{command.name}"
         | 
| 59 | 
            +
                      @commands_hash[command.name] = command
         | 
| 60 | 
            +
                      @commands_hash[command.ident] = command
         | 
| 61 | 
            +
                      command.compute_command_hashes
         | 
| 62 | 
            +
                    }
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              class Analyzer
         | 
| 68 | 
            +
                include Grammar
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                attr_reader :grammar
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def initialize(grammar)
         | 
| 73 | 
            +
                  @grammar = grammar
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                # Move commands that are nested within a different command than it belongs to
         | 
| 77 | 
            +
                def move_commands
         | 
| 78 | 
            +
                  # We can't use Command#[] at this point so we collect the commands here
         | 
| 79 | 
            +
                  h = {}
         | 
| 80 | 
            +
                  @grammar.traverse(Grammar::Command) { |command|
         | 
| 81 | 
            +
                    h[command.path] = command
         | 
| 82 | 
            +
                  }
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  # Find commands to move
         | 
| 85 | 
            +
                  #
         | 
| 86 | 
            +
                  # Commands are moved in two steps because the behaviour of #traverse is
         | 
| 87 | 
            +
                  # not defined when the data structure changes beneath it
         | 
| 88 | 
            +
                  move = []
         | 
| 89 | 
            +
                  @grammar.traverse(Grammar::Command) { |command|
         | 
| 90 | 
            +
                    if command.path.size > 1 && command.parent && command.parent.path != command.path[0..-2]
         | 
| 91 | 
            +
                      move << command
         | 
| 92 | 
            +
                    else
         | 
| 93 | 
            +
                      command.instance_variable_set(:@command, command.parent)
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
                  }
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  # Move commands but do not change parent/child relationship
         | 
| 98 | 
            +
                  move.each { |command|
         | 
| 99 | 
            +
                    supercommand = h[command.path[0..-2]] or analyzer_error "Can't find #{command.ident}!"
         | 
| 100 | 
            +
                    command.parent.commands.delete(command)
         | 
| 101 | 
            +
                    supercommand.commands << command
         | 
| 102 | 
            +
                    command.instance_variable_set(:@command, supercommand)
         | 
| 103 | 
            +
                  }
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                def analyze()
         | 
| 107 | 
            +
                  move_commands
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  @grammar.traverse(Grammar::Command) { |command|
         | 
| 110 | 
            +
                    command.set_supercommand
         | 
| 111 | 
            +
                    command.reorder_options
         | 
| 112 | 
            +
                    command.collect_options
         | 
| 113 | 
            +
                    command.compute_option_hashes
         | 
| 114 | 
            +
                  }
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  @grammar.compute_command_hashes
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                  @grammar.traverse { |node| 
         | 
| 119 | 
            +
                    node.remove_brief_nodes 
         | 
| 120 | 
            +
                    node.remove_arg_descr_nodes
         | 
| 121 | 
            +
                    node.remove_arg_spec_nodes
         | 
| 122 | 
            +
                  }
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  @grammar
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                def Analyzer.analyze(source) self.new(source).analyze end
         | 
| 128 | 
            +
              end
         | 
| 129 | 
            +
            end
         | 
| 130 | 
            +
             | 
    
        data/lib/shellopts/args.rb
    CHANGED
    
    | @@ -3,36 +3,36 @@ module ShellOpts | |
| 3 3 | 
             
              # Specialization of Array for arguments lists. Args extends Array with a
         | 
| 4 4 | 
             
              # #extract and an #expect method to extract elements from the array. The
         | 
| 5 5 | 
             
              # methods raise a ShellOpts::UserError exception in case of errors
         | 
| 6 | 
            +
              #
         | 
| 6 7 | 
             
              class Args < Array
         | 
| 7 | 
            -
                def initialize(shellopts, *args)
         | 
| 8 | 
            -
                  @shellopts = shellopts
         | 
| 9 | 
            -
                  super(*args)
         | 
| 10 | 
            -
                end
         | 
| 11 | 
            -
             | 
| 12 8 | 
             
                # Remove and return elements from beginning of the array
         | 
| 13 9 | 
             
                #
         | 
| 14 10 | 
             
                # If +count_or_range+ is a number, that number of elements will be
         | 
| 15 11 | 
             
                # returned.  If the count is one, a simple value is returned instead of an
         | 
| 16 | 
            -
                # array. | 
| 12 | 
            +
                # array. If the count is negative, the elements will be removed from the
         | 
| 17 13 | 
             
                # end of the array. If +count_or_range+ is a range, the number of elements
         | 
| 18 14 | 
             
                # returned will be in that range. The range can't contain negative numbers 
         | 
| 19 15 | 
             
                #
         | 
| 20 16 | 
             
                # #extract raise a ShellOpts::UserError exception if there's is not enough
         | 
| 21 17 | 
             
                # elements in the array to satisfy the request
         | 
| 18 | 
            +
                #
         | 
| 22 19 | 
             
                def extract(count_or_range, message = nil) 
         | 
| 23 | 
            -
                   | 
| 24 | 
            -
                     | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
                     | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 20 | 
            +
                  case count_or_range
         | 
| 21 | 
            +
                    when Range
         | 
| 22 | 
            +
                      range = count_or_range
         | 
| 23 | 
            +
                      range.min <= self.size or inoa(message)
         | 
| 24 | 
            +
                      n_extract = [self.size, range.max].min
         | 
| 25 | 
            +
                      n_extend = range.max > self.size ? range.max - self.size : 0
         | 
| 26 | 
            +
                      r = self.shift(n_extract) + Array.new(n_extend)
         | 
| 27 | 
            +
                      range.max <= 1 ? r.first : r
         | 
| 28 | 
            +
                    when Integer
         | 
| 29 | 
            +
                      count = count_or_range
         | 
| 30 | 
            +
                      count.abs <= self.size or inoa(message)
         | 
| 31 | 
            +
                      start = count >= 0 ? 0 : size + count
         | 
| 32 | 
            +
                      r = slice!(start, count.abs)
         | 
| 33 | 
            +
                      r.size <= 0 ? nil : (r.size == 1 ? r.first : r)
         | 
| 34 | 
            +
                    else
         | 
| 35 | 
            +
                      raise ArgumentError
         | 
| 36 36 | 
             
                  end
         | 
| 37 37 | 
             
                end
         | 
| 38 38 |  | 
| @@ -41,14 +41,22 @@ module ShellOpts | |
| 41 41 | 
             
                #
         | 
| 42 42 | 
             
                # #expect raise a ShellOpts::UserError exception if the array is not emptied 
         | 
| 43 43 | 
             
                # by the operation
         | 
| 44 | 
            +
                #
         | 
| 44 45 | 
             
                def expect(count_or_range, message = nil)
         | 
| 45 | 
            -
                  count_or_range | 
| 46 | 
            +
                  case count_or_range
         | 
| 47 | 
            +
                    when Range
         | 
| 48 | 
            +
                      count_or_range === self.size or inoa(message)
         | 
| 49 | 
            +
                    when Integer
         | 
| 50 | 
            +
                      count_or_range >= 0 or raise ArgumentError, "Count can't be negative"
         | 
| 51 | 
            +
                      count_or_range.abs == self.size or inoa(message)
         | 
| 52 | 
            +
                  end
         | 
| 46 53 | 
             
                  extract(count_or_range) # Can't fail
         | 
| 47 54 | 
             
                end
         | 
| 48 55 |  | 
| 49 56 | 
             
              private
         | 
| 50 57 | 
             
                def inoa(message = nil) 
         | 
| 51 | 
            -
                  raise  | 
| 58 | 
            +
                  raise ArgumentError, message || "Illegal number of arguments"
         | 
| 52 59 | 
             
                end
         | 
| 53 60 | 
             
              end
         | 
| 54 61 | 
             
            end
         | 
| 62 | 
            +
             |