optitron 0.0.11 → 0.1.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.
- data/Gemfile.lock +1 -1
- data/README.rdoc +106 -90
- data/lib/optitron.rb +0 -6
- data/lib/optitron/class_dsl.rb +6 -6
- data/lib/optitron/dsl.rb +3 -3
- data/lib/optitron/help.rb +20 -7
- data/lib/optitron/option.rb +12 -8
- data/lib/optitron/parser.rb +7 -5
- data/lib/optitron/response.rb +7 -15
- data/lib/optitron/version.rb +1 -1
- data/spec/arg_spec.rb +21 -2
- data/spec/cli_spec.rb +18 -1
- data/spec/help_spec.rb +2 -2
- data/spec/option_spec.rb +21 -0
- metadata +4 -5
- data/spec/dispatch_spec.rb +0 -32
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.rdoc
    CHANGED
    
    | @@ -2,7 +2,108 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            == Sensible options parsing
         | 
| 4 4 |  | 
| 5 | 
            -
             | 
| 5 | 
            +
            Optitron strives to be simple, minimal and to do the "right thing" most of the time. The structure is simple, you have global options, a list of commands, and each of those commands takes a number of arguments and options.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            == Usage
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            To create an Optitron CLI, start with a class you want to wire up as a CLI. In our example, we'll use the class Runner, which we want to work as a CLI.
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              class Runner
         | 
| 12 | 
            +
                def start
         | 
| 13 | 
            +
                  # ... starts
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
                
         | 
| 16 | 
            +
                def stop
         | 
| 17 | 
            +
                  # ... stops
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
                
         | 
| 20 | 
            +
                def status
         | 
| 21 | 
            +
                  # ... reports status
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            To make this class suitable for use as a CLI, either extend <tt>Optitron::CLI</tt> or include <tt>Optitron::ClassDsl</tt>. Then, describe each argument as follows:
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              class Runner < Optitron::CLI
         | 
| 28 | 
            +
                desc "Starts the process"
         | 
| 29 | 
            +
                def start
         | 
| 30 | 
            +
                  # ... starts
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
                
         | 
| 33 | 
            +
                desc "Stops the process"
         | 
| 34 | 
            +
                def stop
         | 
| 35 | 
            +
                  # ... stops
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
                
         | 
| 38 | 
            +
                desc "Report the status"
         | 
| 39 | 
            +
                def status
         | 
| 40 | 
            +
                  # ... reports status
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            Only methods described will be available to the CLI. If you have a method that needs arguments, simply include them as method arguments. It will respect splats and defaults. Options are specified with the +opt+ method in your class, as follows:
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              class Runner < Optitron::CLI
         | 
| 47 | 
            +
                desc "Starts the process"
         | 
| 48 | 
            +
                opt "verbose"
         | 
| 49 | 
            +
                opt "environment", :in => ['development', 'production', 'staging', 'test']
         | 
| 50 | 
            +
                def start
         | 
| 51 | 
            +
                  # ... starts
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                
         | 
| 54 | 
            +
                # .. more methods
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
             | 
| 59 | 
            +
            If you need an option available for all methods, use +class_opt+ to specify it.
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              class Runner < Optitron::CLI
         | 
| 62 | 
            +
                class_opt "verbose", "Be loud"
         | 
| 63 | 
            +
              
         | 
| 64 | 
            +
                # ... your methods
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            The last line in your runner has to be the class name and dispatch. This will parse <tt>ARGV</tt> and execute normal dispatching on it.
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              class Runner < Optitron::CLI
         | 
| 70 | 
            +
                # ... your methods
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
              Runner.dispatch
         | 
| 73 | 
            +
              
         | 
| 74 | 
            +
            As well, help is added by default, but, if you don't want to use this, include the command +dont_use_help+ in your class.
         | 
| 75 | 
            +
              
         | 
| 76 | 
            +
            == How to configure options
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            Options can have defaults, types and inclusion checks. Here are all the options available on an opt:
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            === <tt>:default</tt>
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            This allows you to specify a default value. The type of value is used to infer the type required for this option. This can be over-ridden with <tt>:type</tt>.
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            === <tt>:short_name</tt>
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            This allows you to set a short name for the option, though, one will be assigned automatically from the short names available.
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            === <tt>:run</tt>
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            This allows you to run an arbitrary block for an option. The proc will be called with the value, and the response object.
         | 
| 91 | 
            +
             | 
| 92 | 
            +
            === <tt>:in</tt>
         | 
| 93 | 
            +
             | 
| 94 | 
            +
            This allows you to test for inclusion in a range or array (or anything that responds to <tt>#include?</tt> and <tt>#first</tt>). The first item in the object will be used to infer the type. This can be over-ridden with <tt>:type</tt>.
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            === <tt>:required</tt>
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            This allows you to force an option to be required. False by default.
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            ==== <tt>:type</tt>
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            This allows you to specify the type. Acceptable options are <tt>:numeric</tt>, <tt>:array</tt>, <tt>:hash</tt>, <tt>:string</tt> or <tt>:boolean</tt>.
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            == Stand alone usage
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            You can create parsers and parse using them.
         | 
| 6 107 |  | 
| 7 108 | 
             
              @parser = Optitron.new {
         | 
| 8 109 | 
             
                help
         | 
| @@ -24,11 +125,11 @@ You can specify lots of different commands with options | |
| 24 125 | 
             
                end
         | 
| 25 126 | 
             
              }
         | 
| 26 127 |  | 
| 27 | 
            -
             | 
| 128 | 
            +
            To generate help, use the <tt>#help</tt> method.
         | 
| 28 129 |  | 
| 29 130 | 
             
              @parser.help
         | 
| 30 131 |  | 
| 31 | 
            -
             | 
| 132 | 
            +
            Which returns,
         | 
| 32 133 |  | 
| 33 134 | 
             
              Commands
         | 
| 34 135 |  | 
| @@ -44,7 +145,7 @@ Will output: | |
| 44 145 |  | 
| 45 146 | 
             
              -v/--verbose                   # Be very loud
         | 
| 46 147 |  | 
| 47 | 
            -
             | 
| 148 | 
            +
            The parse method can parse a list of arguments. For example, <tt>@parser.parse(%w(-v install file))</tt> gives back:
         | 
| 48 149 |  | 
| 49 150 | 
             
              response = @parser.parse(%w(-v install file))
         | 
| 50 151 | 
             
              response.command
         | 
| @@ -54,7 +155,7 @@ And <tt>@parser.parse(%w(-v install file))</tt> gives back: | |
| 54 155 | 
             
              response.params
         | 
| 55 156 | 
             
              => {"verbose" => true}
         | 
| 56 157 |  | 
| 57 | 
            -
            If you try parsing invalid parameters, get back friendly error messages
         | 
| 158 | 
            +
            If you try parsing invalid parameters, you can get back friendly error messages using <tt>#error_messages</tt>.
         | 
| 58 159 |  | 
| 59 160 | 
             
              @parser.parse(%w()).error_messages
         | 
| 60 161 | 
             
              => ["Unknown command"]
         | 
| @@ -64,88 +165,3 @@ If you try parsing invalid parameters, get back friendly error messages | |
| 64 165 | 
             
              => ["File is required"]
         | 
| 65 166 | 
             
              @parser.parse(%w(kill --pid=something)).error_messages
         | 
| 66 167 | 
             
              => ["Pid is invalid"]
         | 
| 67 | 
            -
             | 
| 68 | 
            -
            == Usage in a binary
         | 
| 69 | 
            -
             | 
| 70 | 
            -
            To use this in a file, create a parser, and tell it to dispatch to your favourite object. For instance, save this down to <tt>test.rb</tt>
         | 
| 71 | 
            -
             | 
| 72 | 
            -
              class Runner
         | 
| 73 | 
            -
                def install(file, opts)
         | 
| 74 | 
            -
                  puts "installing #{file} with #{opts}!"
         | 
| 75 | 
            -
                end
         | 
| 76 | 
            -
              end
         | 
| 77 | 
            -
             | 
| 78 | 
            -
              Optitron.dispatch(Runner.new) {
         | 
| 79 | 
            -
                opt 'verbose', "Be very loud"
         | 
| 80 | 
            -
                cmd "install", "This installs things" do
         | 
| 81 | 
            -
                  arg "file", "The file to install"
         | 
| 82 | 
            -
                end
         | 
| 83 | 
            -
                cmd "show", "This shows things" do
         | 
| 84 | 
            -
                  arg "first", "The first thing to show"
         | 
| 85 | 
            -
                  arg "second", "The second optional thing to show", :required => false
         | 
| 86 | 
            -
                end
         | 
| 87 | 
            -
                cmd "kill", "This kills things" do
         | 
| 88 | 
            -
                  opt "pids", "A list of pids to kill", :type => :array
         | 
| 89 | 
            -
                  opt "pid", "A pid to kill", :type => :numeric
         | 
| 90 | 
            -
                  opt "names", "Some sort of hash", :type => :hash
         | 
| 91 | 
            -
                end
         | 
| 92 | 
            -
                cmd "join", "This joins things" do
         | 
| 93 | 
            -
                  arg "thing", "Stuff to join", :type => :greedy
         | 
| 94 | 
            -
                end
         | 
| 95 | 
            -
              }
         | 
| 96 | 
            -
             | 
| 97 | 
            -
            Now, try running it.
         | 
| 98 | 
            -
             | 
| 99 | 
            -
              crapbook-pro:optitron joshua$ ruby test.rb 
         | 
| 100 | 
            -
              Unknown command
         | 
| 101 | 
            -
              
         | 
| 102 | 
            -
              crapbook-pro:optitron joshua$ ruby test.rb install
         | 
| 103 | 
            -
              File is required
         | 
| 104 | 
            -
              
         | 
| 105 | 
            -
              crapbook-pro:optitron joshua$ ruby test.rb install file
         | 
| 106 | 
            -
              installing file with {"verbose"=>false}!
         | 
| 107 | 
            -
             | 
| 108 | 
            -
              crapbook-pro:optitron joshua$ ruby test.rb install file --verbose
         | 
| 109 | 
            -
              installing file with {"verbose"=>true}!
         | 
| 110 | 
            -
             | 
| 111 | 
            -
            == Usage in a class
         | 
| 112 | 
            -
             | 
| 113 | 
            -
              require 'optitron'
         | 
| 114 | 
            -
              
         | 
| 115 | 
            -
              class Runner < Optitron::CLI
         | 
| 116 | 
            -
              
         | 
| 117 | 
            -
                class_opt 'verbose'
         | 
| 118 | 
            -
              
         | 
| 119 | 
            -
                desc "Install stuff"
         | 
| 120 | 
            -
                opt 'force'
         | 
| 121 | 
            -
                def install(file, source)
         | 
| 122 | 
            -
                  puts "install some things #{file} from #{source.inspect} #{params.inspect}"
         | 
| 123 | 
            -
                end
         | 
| 124 | 
            -
              end
         | 
| 125 | 
            -
              
         | 
| 126 | 
            -
              Runner.dispatch
         | 
| 127 | 
            -
              
         | 
| 128 | 
            -
            Running this gives you
         | 
| 129 | 
            -
             | 
| 130 | 
            -
              crapbook-pro:optitron joshua$ ruby ideal.rb --help
         | 
| 131 | 
            -
              Commands
         | 
| 132 | 
            -
             | 
| 133 | 
            -
              install [file] <required="yourmom">       # Install stuff
         | 
| 134 | 
            -
                -f/--force                              
         | 
| 135 | 
            -
             | 
| 136 | 
            -
              Global options
         | 
| 137 | 
            -
             | 
| 138 | 
            -
              -v/--verbose                              
         | 
| 139 | 
            -
              -?/--help                                 # Print help message
         | 
| 140 | 
            -
             | 
| 141 | 
            -
              crapbook-pro:optitron joshua$ ruby ideal.rb install
         | 
| 142 | 
            -
              File is required
         | 
| 143 | 
            -
             | 
| 144 | 
            -
              crapbook-pro:optitron joshua$ ruby ideal.rb install file
         | 
| 145 | 
            -
              installing file from yourmom with params: {"help"=>false, "force"=>false, "verbose"=>false}
         | 
| 146 | 
            -
             | 
| 147 | 
            -
              crapbook-pro:optitron joshua$ ruby ideal.rb install file yourdad
         | 
| 148 | 
            -
              installing file from yourdad with params: {"help"=>false, "force"=>false, "verbose"=>false}
         | 
| 149 | 
            -
             | 
| 150 | 
            -
              crapbook-pro:optitron joshua$ ruby ideal.rb install file yourdad -v
         | 
| 151 | 
            -
              installing file from yourdad with params: {"help"=>false, "force"=>false, "verbose"=>true}
         | 
    
        data/lib/optitron.rb
    CHANGED
    
    | @@ -25,12 +25,6 @@ class Optitron | |
| 25 25 | 
             
                Optitron.new(&blk).parse(args)
         | 
| 26 26 | 
             
              end
         | 
| 27 27 |  | 
| 28 | 
            -
              def self.dispatch(target = nil, args = ARGV, &blk)
         | 
| 29 | 
            -
                optitron = Optitron.new(&blk)
         | 
| 30 | 
            -
                optitron.parser.target = target
         | 
| 31 | 
            -
                optitron.parser.parse(args).dispatch
         | 
| 32 | 
            -
              end
         | 
| 33 | 
            -
             | 
| 34 28 | 
             
              def help
         | 
| 35 29 | 
             
                @parser.help
         | 
| 36 30 | 
             
              end
         | 
    
        data/lib/optitron/class_dsl.rb
    CHANGED
    
    | @@ -96,12 +96,12 @@ class Optitron | |
| 96 96 | 
             
                  end
         | 
| 97 97 |  | 
| 98 98 | 
             
                  def build_method_args(file)
         | 
| 99 | 
            -
                    unless  | 
| 99 | 
            +
                    unless send(:class_variable_defined?, :@@method_args)
         | 
| 100 100 | 
             
                      parser = RubyParser.new
         | 
| 101 101 | 
             
                      sexp = parser.process(File.read(file))
         | 
| 102 102 | 
             
                      method_args = MethodArgs.new(self)
         | 
| 103 103 | 
             
                      method_args.process(sexp)
         | 
| 104 | 
            -
                       | 
| 104 | 
            +
                      send(:class_variable_set, :@@method_args, method_args.method_map)
         | 
| 105 105 | 
             
                    end
         | 
| 106 106 | 
             
                    send(:class_variable_get, :@@method_args)
         | 
| 107 107 | 
             
                  end
         | 
| @@ -111,7 +111,7 @@ class Optitron | |
| 111 111 | 
             
                  end
         | 
| 112 112 |  | 
| 113 113 | 
             
                  def dont_use_help
         | 
| 114 | 
            -
                     | 
| 114 | 
            +
                    send(:class_variable_set, :@@suppress_help, true)
         | 
| 115 115 | 
             
                  end
         | 
| 116 116 |  | 
| 117 117 | 
             
                  def desc(desc)
         | 
| @@ -126,7 +126,7 @@ class Optitron | |
| 126 126 |  | 
| 127 127 | 
             
                  def build
         | 
| 128 128 | 
             
                    unless @built
         | 
| 129 | 
            -
                      optitron_dsl.root.help
         | 
| 129 | 
            +
                      optitron_dsl.root.help# if send(:class_variable_defined?, :@@suppress_help)
         | 
| 130 130 | 
             
                      @cmds.each do |(cmd_name, cmd_desc, opts)|
         | 
| 131 131 | 
             
                        args = method_args[cmd_name.to_sym]
         | 
| 132 132 | 
             
                        arity = instance_method(cmd_name).arity
         | 
| @@ -156,8 +156,8 @@ class Optitron | |
| 156 156 | 
             
                    if response.valid?
         | 
| 157 157 | 
             
                      optitron_parser.target.params = response.params
         | 
| 158 158 | 
             
                      args = response.args
         | 
| 159 | 
            -
                      while (args.size < optitron_parser.commands | 
| 160 | 
            -
                        args << optitron_parser. | 
| 159 | 
            +
                      while (args.size < optitron_parser.commands.assoc(response.command).last.args.size)
         | 
| 160 | 
            +
                        args << optitron_parser.commandsassoc(response.command).last.args[args.size].default
         | 
| 161 161 | 
             
                      end
         | 
| 162 162 | 
             
                      optitron_parser.target.send(response.command.to_sym, *response.args)
         | 
| 163 163 | 
             
                    else
         | 
    
        data/lib/optitron/dsl.rb
    CHANGED
    
    | @@ -40,8 +40,8 @@ class Optitron | |
| 40 40 | 
             
                  end
         | 
| 41 41 |  | 
| 42 42 | 
             
                  def arg(name, description = nil, opts = nil)
         | 
| 43 | 
            -
                    arg_option = Option::Arg.new(name, description, opts)
         | 
| 44 | 
            -
                    raise InvalidParser.new if @target.args.last and !@target.args.last.required? and arg_option.required?
         | 
| 43 | 
            +
                    arg_option = Option::Arg.new(name, description, opts)                                                                         
         | 
| 44 | 
            +
                    raise InvalidParser.new if @target.args.last and !@target.args.last.required? and arg_option.required? and arg_option.type != :greedy
         | 
| 45 45 | 
             
                    raise InvalidParser.new if @target.args.last and @target.args.last.type == :greedy
         | 
| 46 46 | 
             
                    @target.args << arg_option
         | 
| 47 47 | 
             
                    arg_option
         | 
| @@ -100,7 +100,7 @@ class Optitron | |
| 100 100 | 
             
                  def cmd(name, description = nil, opts = nil, &blk)
         | 
| 101 101 | 
             
                    command_option = Option::Cmd.new(name, description, opts)
         | 
| 102 102 | 
             
                    CmdParserDsl.new(self, command_option).configure_with(&blk) if blk
         | 
| 103 | 
            -
                    @target.commands[name | 
| 103 | 
            +
                    @target.commands << [name, command_option]
         | 
| 104 104 | 
             
                  end
         | 
| 105 105 | 
             
                end
         | 
| 106 106 | 
             
              end
         | 
    
        data/lib/optitron/help.rb
    CHANGED
    
    | @@ -4,10 +4,23 @@ class Optitron | |
| 4 4 | 
             
                  @parser = parser
         | 
| 5 5 | 
             
                end
         | 
| 6 6 |  | 
| 7 | 
            +
                def help_line_for_opt_value(opt)
         | 
| 8 | 
            +
                  if opt.inclusion_test
         | 
| 9 | 
            +
                    case opt.inclusion_test
         | 
| 10 | 
            +
                    when Array
         | 
| 11 | 
            +
                      opt.inclusion_test.join(', ')
         | 
| 12 | 
            +
                    else
         | 
| 13 | 
            +
                      opt.inclusion_test.inspect
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
                  else
         | 
| 16 | 
            +
                    opt.type.to_s.upcase
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 7 20 | 
             
                def help_line_for_opt(opt)
         | 
| 8 21 | 
             
                  opt_line = ''
         | 
| 9 22 | 
             
                  opt_line << [opt.short_name ? "-#{opt.short_name}" : nil, "--#{opt.name}"].compact.join('/')
         | 
| 10 | 
            -
                  opt_line << "=[#{opt | 
| 23 | 
            +
                  opt_line << "=[#{help_line_for_opt_value(opt)}]" unless opt.boolean?
         | 
| 11 24 | 
             
                  [opt_line, opt.desc]
         | 
| 12 25 | 
             
                end
         | 
| 13 26 |  | 
| @@ -27,15 +40,15 @@ class Optitron | |
| 27 40 | 
             
                end
         | 
| 28 41 |  | 
| 29 42 | 
             
                def generate
         | 
| 30 | 
            -
                  cmds =  | 
| 31 | 
            -
                  @parser.commands.each do |cmd_name, cmd|
         | 
| 43 | 
            +
                  cmds = []
         | 
| 44 | 
            +
                  @parser.commands.each do |(cmd_name, cmd)|
         | 
| 32 45 | 
             
                    cmd_line = "#{cmd_name}"
         | 
| 33 46 | 
             
                    cmd.args.each do |arg|
         | 
| 34 47 | 
             
                      cmd_line << " " << help_line_for_arg(arg)
         | 
| 35 48 | 
             
                    end
         | 
| 36 | 
            -
                    cmds[cmd_line | 
| 49 | 
            +
                    cmds << [cmd_line, cmd.desc]
         | 
| 37 50 | 
             
                    cmd.options.each do |opt|
         | 
| 38 | 
            -
                      cmds | 
| 51 | 
            +
                      cmds.assoc(cmd_line) << help_line_for_opt(opt)
         | 
| 39 52 | 
             
                    end
         | 
| 40 53 | 
             
                  end
         | 
| 41 54 | 
             
                  opts_lines = @parser.options.map do |opt|
         | 
| @@ -45,7 +58,7 @@ class Optitron | |
| 45 58 | 
             
                  args_lines = @parser.args.empty? ? nil : [@parser.args.map{|arg| help_line_for_arg(arg)}.join(' '), @parser.args.map{|arg| arg.desc}.join(', ')]
         | 
| 46 59 |  | 
| 47 60 | 
             
                  longest_line = 0
         | 
| 48 | 
            -
                  longest_line = [longest_line, cmds. | 
| 61 | 
            +
                  longest_line = [longest_line, cmds.map{|cmd| cmd.first.size}.max].max unless cmds.empty?
         | 
| 49 62 | 
             
                  opt_lines = cmds.map{|k,v| k.size + 2}.flatten
         | 
| 50 63 | 
             
                  longest_line = [longest_line, args_lines.first.size].max if args_lines
         | 
| 51 64 | 
             
                  longest_line = [longest_line, opt_lines.max].max unless opt_lines.empty?
         | 
| @@ -53,7 +66,7 @@ class Optitron | |
| 53 66 | 
             
                  help_output = []
         | 
| 54 67 |  | 
| 55 68 | 
             
                  unless cmds.empty?
         | 
| 56 | 
            -
                    help_output << "Commands\n\n" + cmds.map do |cmd, opts|
         | 
| 69 | 
            +
                    help_output << "Commands\n\n" + cmds.map do |(cmd, *opts)|
         | 
| 57 70 | 
             
                      cmd_text = ""
         | 
| 58 71 | 
             
                      cmd_text << "%-#{longest_line}s     " % cmd
         | 
| 59 72 | 
             
                      cmd_desc = opts.shift
         | 
    
        data/lib/optitron/option.rb
    CHANGED
    
    | @@ -1,5 +1,6 @@ | |
| 1 1 | 
             
            class Optitron
         | 
| 2 2 | 
             
              class Option
         | 
| 3 | 
            +
                attr_reader :inclusion_test
         | 
| 3 4 | 
             
                attr_accessor :required, :name, :default, :parameterize, :type, :desc, :has_default
         | 
| 4 5 | 
             
                alias_method :required?, :required
         | 
| 5 6 | 
             
                alias_method :has_default?, :has_default
         | 
| @@ -89,16 +90,18 @@ class Optitron | |
| 89 90 | 
             
                end
         | 
| 90 91 |  | 
| 91 92 | 
             
                class Opt < Option
         | 
| 92 | 
            -
                  attr_accessor :short_name, :run, :parent_cmd
         | 
| 93 | 
            +
                  attr_accessor :short_name, :run, :parent_cmd, :include_in_params
         | 
| 94 | 
            +
                  alias_method :include_in_params?, :include_in_params
         | 
| 93 95 | 
             
                  def initialize(name, desc = nil, opts = nil)
         | 
| 94 96 | 
             
                    if desc.is_a?(Hash)
         | 
| 95 97 | 
             
                      desc, opts = nil, desc
         | 
| 96 98 | 
             
                    end
         | 
| 97 99 | 
             
                    @name, @desc = name, desc
         | 
| 98 | 
            -
                     | 
| 100 | 
            +
                    self.type = opts && opts[:type] || :boolean
         | 
| 99 101 | 
             
                    self.short_name = opts[:short_name] if opts && opts[:short_name]
         | 
| 100 102 | 
             
                    self.run = opts[:run] if opts && opts[:run]
         | 
| 101 103 | 
             
                    self.inclusion_test = opts[:in] if opts && opts[:in]
         | 
| 104 | 
            +
                    self.required = opts && opts.key?(:required) ? opts[:required] : false
         | 
| 102 105 | 
             
                    self.default = opts && opts.key?(:default) ? opts[:default] : (@type == :boolean ? false : nil)
         | 
| 103 106 | 
             
                  end
         | 
| 104 107 |  | 
| @@ -180,16 +183,15 @@ class Optitron | |
| 180 183 |  | 
| 181 184 | 
             
                class Arg < Option
         | 
| 182 185 | 
             
                  attr_accessor :greedy, :inclusion_test, :parent_cmd
         | 
| 183 | 
            -
                  alias_method :greedy?, :greedy
         | 
| 184 186 | 
             
                  def initialize(name = nil, desc = nil, opts = nil)
         | 
| 185 187 | 
             
                    if desc.is_a?(Hash)
         | 
| 186 188 | 
             
                      desc, opts = nil, desc
         | 
| 187 189 | 
             
                    end
         | 
| 188 190 | 
             
                    @name, @desc = name, desc
         | 
| 189 191 | 
             
                    self.inclusion_test = opts[:in] if opts && opts[:in]
         | 
| 190 | 
            -
                     | 
| 191 | 
            -
                     | 
| 192 | 
            -
                     | 
| 192 | 
            +
                    self.default = opts && opts[:default]
         | 
| 193 | 
            +
                    self.type = opts && opts[:type]
         | 
| 194 | 
            +
                    self.required = opts && opts.key?(:required) ? opts[:required] : (@default.nil? and !greedy?)
         | 
| 193 195 | 
             
                  end
         | 
| 194 196 |  | 
| 195 197 | 
             
                  def consume(response, tokens)
         | 
| @@ -199,7 +201,7 @@ class Optitron | |
| 199 201 | 
             
                      while !arg_tokens.size.zero?
         | 
| 200 202 | 
             
                        arg_tok = arg_tokens.shift
         | 
| 201 203 | 
             
                        tokens.delete_at(tokens.index(arg_tok))
         | 
| 202 | 
            -
                        response.args_with_tokens.last.last << arg_tok
         | 
| 204 | 
            +
                        response.args_with_tokens.last.last << arg_tok.lit
         | 
| 203 205 | 
             
                      end
         | 
| 204 206 | 
             
                      if required? and response.args_with_tokens.last.last.size.zero?
         | 
| 205 207 | 
             
                        response.add_error("required", name)
         | 
| @@ -210,7 +212,9 @@ class Optitron | |
| 210 212 | 
             
                      elsif !arg_tokens.size.zero?
         | 
| 211 213 | 
             
                        arg_tok = arg_tokens.shift
         | 
| 212 214 | 
             
                        tokens.delete_at(tokens.index(arg_tok))
         | 
| 213 | 
            -
                        response.args_with_tokens << [self, arg_tok]
         | 
| 215 | 
            +
                        response.args_with_tokens << [self, arg_tok.lit]
         | 
| 216 | 
            +
                      elsif has_default
         | 
| 217 | 
            +
                        response.args_with_tokens << [self, default]
         | 
| 214 218 | 
             
                      end
         | 
| 215 219 | 
             
                    end
         | 
| 216 220 | 
             
                  end
         | 
    
        data/lib/optitron/parser.rb
    CHANGED
    
    | @@ -5,7 +5,7 @@ class Optitron | |
| 5 5 |  | 
| 6 6 | 
             
                def initialize
         | 
| 7 7 | 
             
                  @options = []
         | 
| 8 | 
            -
                  @commands =  | 
| 8 | 
            +
                  @commands = []
         | 
| 9 9 | 
             
                  @args = []
         | 
| 10 10 | 
             
                  @short_opts = {}
         | 
| 11 11 | 
             
                  @help = Help.new(self)
         | 
| @@ -22,11 +22,11 @@ class Optitron | |
| 22 22 | 
             
                  args = @args
         | 
| 23 23 | 
             
                  unless @commands.empty?
         | 
| 24 24 | 
             
                    potential_cmd_toks = tokens.select { |t| t.respond_to?(:lit) }
         | 
| 25 | 
            -
                    if cmd_tok = potential_cmd_toks.find { |t| @commands | 
| 25 | 
            +
                    if cmd_tok = potential_cmd_toks.find { |t| @commands.assoc(t.lit) }
         | 
| 26 26 | 
             
                      tokens.delete(cmd_tok)
         | 
| 27 27 | 
             
                      response.command = cmd_tok.lit
         | 
| 28 | 
            -
                      options += @commands | 
| 29 | 
            -
                      args = @commands | 
| 28 | 
            +
                      options += @commands.assoc(cmd_tok.lit).last.options
         | 
| 29 | 
            +
                      args = @commands.assoc(cmd_tok.lit).last.args
         | 
| 30 30 | 
             
                    else
         | 
| 31 31 | 
             
                      potential_cmd_toks.first ?
         | 
| 32 32 | 
             
                        response.add_error('an unknown command', potential_cmd_toks.first.lit) :
         | 
| @@ -45,12 +45,14 @@ class Optitron | |
| 45 45 | 
             
                    if opt_tok = tokens.find { |tok| opt.match?(tok) }
         | 
| 46 46 | 
             
                      opt_tok_index = tokens.index(opt_tok)
         | 
| 47 47 | 
             
                      opt.consume(response, tokens)
         | 
| 48 | 
            +
                    elsif opt.required?
         | 
| 49 | 
            +
                      response.add_error("required", opt.name)
         | 
| 48 50 | 
             
                    end
         | 
| 49 51 | 
             
                  end
         | 
| 50 52 | 
             
                end
         | 
| 51 53 |  | 
| 52 54 | 
             
                def parse_args(tokens, args, response)
         | 
| 53 | 
            -
                  args.each { |arg| arg.consume(response, tokens) } | 
| 55 | 
            +
                  args.each { |arg| arg.consume(response, tokens) }
         | 
| 54 56 | 
             
                end
         | 
| 55 57 | 
             
              end
         | 
| 56 58 | 
             
            end
         | 
    
        data/lib/optitron/response.rb
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            class Optitron
         | 
| 2 2 | 
             
              class Response
         | 
| 3 | 
            -
                attr_reader :params_array, :args, :params, :args_with_tokens, :errors
         | 
| 3 | 
            +
                attr_reader :params_array, :args, :params, :args_with_tokens, :errors, :args_hash
         | 
| 4 4 | 
             
                attr_accessor :command
         | 
| 5 5 | 
             
                def initialize(parser, tokens)
         | 
| 6 6 | 
             
                  @parser, @tokens = parser, tokens
         | 
| @@ -34,15 +34,17 @@ class Optitron | |
| 34 34 | 
             
                def validate
         | 
| 35 35 | 
             
                  compile_params
         | 
| 36 36 | 
             
                  @params_array.each { |(key, value)| key.run.call(params[key.name], self) if key.run }
         | 
| 37 | 
            -
                  @ | 
| 37 | 
            +
                  @args_array = @args_with_tokens.map { |(arg, val)| 
         | 
| 38 38 | 
             
                    begin
         | 
| 39 | 
            -
                       | 
| 39 | 
            +
                      [arg, arg.validate(val)]
         | 
| 40 40 | 
             
                    rescue
         | 
| 41 41 | 
             
                      add_error('invalid', arg.name)
         | 
| 42 | 
            -
                       | 
| 42 | 
            +
                      [arg, val]
         | 
| 43 43 | 
             
                    end
         | 
| 44 44 | 
             
                  }
         | 
| 45 | 
            -
                  @ | 
| 45 | 
            +
                  @args_hash = Hash[@args_array.map{|(arg, val)| [arg.name, val]}]
         | 
| 46 | 
            +
                  
         | 
| 47 | 
            +
                  @args = @args_array.inject([]) {|args, (arg, val)| arg.greedy? ? args.concat(val) : args.push(val); args}
         | 
| 46 48 | 
             
                  unless @tokens.empty?
         | 
| 47 49 | 
             
                    @tokens.select{|t| t.respond_to?(:name)}.each do |named_token|
         | 
| 48 50 | 
             
                      @tokens.delete(named_token)
         | 
| @@ -57,16 +59,6 @@ class Optitron | |
| 57 59 | 
             
                  end
         | 
| 58 60 | 
             
                end
         | 
| 59 61 |  | 
| 60 | 
            -
                def dispatch
         | 
| 61 | 
            -
                  raise unless @parser.target
         | 
| 62 | 
            -
                  if valid?
         | 
| 63 | 
            -
                    dispatch_args = params.empty? ? args : args + [params]
         | 
| 64 | 
            -
                    @parser.target.send(command.to_sym, *dispatch_args)
         | 
| 65 | 
            -
                  else
         | 
| 66 | 
            -
                    puts error_messages.join("\n")
         | 
| 67 | 
            -
                  end
         | 
| 68 | 
            -
                end
         | 
| 69 | 
            -
             | 
| 70 62 | 
             
                def valid?
         | 
| 71 63 | 
             
                  @errors.empty?
         | 
| 72 64 | 
             
                end
         | 
    
        data/lib/optitron/version.rb
    CHANGED
    
    
    
        data/spec/arg_spec.rb
    CHANGED
    
    | @@ -90,11 +90,11 @@ describe "Optitron::Parser arg spec" do | |
| 90 90 | 
             
                end
         | 
| 91 91 | 
             
              end
         | 
| 92 92 |  | 
| 93 | 
            -
              context "one greedy arg" do
         | 
| 93 | 
            +
              context "one required greedy arg" do
         | 
| 94 94 | 
             
                before(:each) {
         | 
| 95 95 | 
             
                  @parser = Optitron.new {
         | 
| 96 96 | 
             
                    cmd "install" do
         | 
| 97 | 
            -
                      arg "files", :type => :greedy
         | 
| 97 | 
            +
                      arg "files", :type => :greedy, :required => true
         | 
| 98 98 | 
             
                    end
         | 
| 99 99 | 
             
                  }
         | 
| 100 100 | 
             
                }
         | 
| @@ -120,6 +120,25 @@ describe "Optitron::Parser arg spec" do | |
| 120 120 | 
             
                end
         | 
| 121 121 | 
             
              end
         | 
| 122 122 |  | 
| 123 | 
            +
              context "one optional + one greedy arg" do
         | 
| 124 | 
            +
                before(:each) {
         | 
| 125 | 
            +
                  @parser = Optitron.new {
         | 
| 126 | 
            +
                    cmd "install" do
         | 
| 127 | 
            +
                      arg "values", :default => [1,2,3]
         | 
| 128 | 
            +
                      arg "files", :type => :greedy
         | 
| 129 | 
            +
                    end
         | 
| 130 | 
            +
                  }
         | 
| 131 | 
            +
                }
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                it "should parse 'install'" do
         | 
| 134 | 
            +
                  response = @parser.parse(%w(install))
         | 
| 135 | 
            +
                  response.command.should == 'install'
         | 
| 136 | 
            +
                  response.args.should == [[1,2,3]]
         | 
| 137 | 
            +
                  response.valid?.should be_true
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
              end
         | 
| 141 | 
            +
             | 
| 123 142 | 
             
              context "invalid parsers" do
         | 
| 124 143 | 
             
                it "shouldn't allow a required arg after an optional arg" do
         | 
| 125 144 | 
             
                  proc {
         | 
    
        data/spec/cli_spec.rb
    CHANGED
    
    | @@ -43,11 +43,24 @@ class AnotherCLIExample < Optitron::CLI | |
| 43 43 | 
             
              end
         | 
| 44 44 | 
             
            end
         | 
| 45 45 |  | 
| 46 | 
            +
            class NoHelpExample < Optitron::CLI
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              dont_use_help
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              class_opt 'verbose'
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              desc "Use this too"
         | 
| 53 | 
            +
              opt 'another_opt'
         | 
| 54 | 
            +
              def use_too(one, two = 'three')
         | 
| 55 | 
            +
                puts "using this too #{one} #{two} #{params['another_opt']} #{@env}"
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| 58 | 
            +
             | 
| 46 59 |  | 
| 47 60 | 
             
            describe "Optitron::Parser defaults" do
         | 
| 48 61 | 
             
              it "should generate the correct help" do
         | 
| 49 62 | 
             
                CLIExample.build
         | 
| 50 | 
            -
                CLIExample.optitron_parser.help.strip.should == "Commands\n\nuse_greedy [one]  | 
| 63 | 
            +
                CLIExample.optitron_parser.help.strip.should == "Commands\n\nuse                                    # Use this\n  -u/--use_opt                         \nuse_too [one] <two=\"three\">            # Use this too\n  -a/--another_opt                     \nuse_greedy [one] <two1 two2 ...>       # Use this three\n  -A/--another_opt_as_well=[NUMERIC]   \nwith_array <ary=[1, 2, 3]>             # something with an array\n\nGlobal options\n\n-v/--verbose                           \n-?/--help                              # Print help message"
         | 
| 51 64 | 
             
              end
         | 
| 52 65 |  | 
| 53 66 | 
             
              it "should dispatch" do
         | 
| @@ -62,4 +75,8 @@ describe "Optitron::Parser defaults" do | |
| 62 75 | 
             
              it "should dispatch with a custom initer" do
         | 
| 63 76 | 
             
                capture(:stdout) { AnotherCLIExample.dispatch(%w(use_too three four --another_opt)) { AnotherCLIExample.new("test") }  }.should == "using this too three four true test\n"
         | 
| 64 77 | 
             
              end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              it "should be able to suppress help" do
         | 
| 80 | 
            +
                capture(:stdout) { NoHelpExample.dispatch(%w(--help)) }.should == "Unknown command\nHelp is unrecognized\n"
         | 
| 81 | 
            +
              end
         | 
| 65 82 | 
             
            end
         | 
    
        data/spec/help_spec.rb
    CHANGED
    
    | @@ -17,10 +17,10 @@ describe "Optitron::Parser help" do | |
| 17 17 | 
             
                    opt "names", "Some sort of hash", :type => :hash
         | 
| 18 18 | 
             
                  end
         | 
| 19 19 | 
             
                  cmd "join", "This joins things" do
         | 
| 20 | 
            -
                    arg "thing", "Stuff to join", :type => :greedy
         | 
| 20 | 
            +
                    arg "thing", "Stuff to join", :type => :greedy, :required => true
         | 
| 21 21 | 
             
                  end
         | 
| 22 22 | 
             
                }
         | 
| 23 | 
            -
                @parser.help.should == "Commands\n\ | 
| 23 | 
            +
                @parser.help.should == "Commands\n\ninstall [file]                 # This installs things\nshow [first] <second>          # This shows things\nkill                           # This kills things\n  -p/--pids=[ARRAY]            # A list of pids to kill\n  -P/--pid=[NUMERIC]           # A pid to kill\n  -n/--names=[HASH]            # Some sort of hash\njoin [thing1 thing2 ...]       # This joins things\n\nGlobal options\n\n-v/--verbose                   # Be very loud"
         | 
| 24 24 | 
             
              end
         | 
| 25 25 |  | 
| 26 26 | 
             
              it "generate help for non-command parsers" do
         | 
    
        data/spec/option_spec.rb
    CHANGED
    
    | @@ -112,4 +112,25 @@ describe "Optitron::Parser options" do | |
| 112 112 | 
             
                  @parser.parse(%w(-123o test)).params.should == {'option' => 'test', '1' => true, '2' => true, '3' => true}
         | 
| 113 113 | 
             
                end
         | 
| 114 114 | 
             
              end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
              context "required options" do
         | 
| 117 | 
            +
                before(:each) do
         | 
| 118 | 
            +
                  @parser = Optitron.new {
         | 
| 119 | 
            +
                    cmd "install" do
         | 
| 120 | 
            +
                      opt "environment", :type => :string, :required => true
         | 
| 121 | 
            +
                    end
         | 
| 122 | 
            +
                  }
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
                
         | 
| 125 | 
            +
                it "shouldn't parse 'install'" do
         | 
| 126 | 
            +
                  @parser.parse(%w(install)).valid?.should be_false
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                it "should parse 'install -esomething'" do
         | 
| 130 | 
            +
                  response = @parser.parse(%w(install -esomething))
         | 
| 131 | 
            +
                  response.valid?.should be_true
         | 
| 132 | 
            +
                  response.params.should == {'environment' => 'something'}
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
              end
         | 
| 135 | 
            +
             | 
| 115 136 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,13 +1,13 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: optitron
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              hash:  | 
| 4 | 
            +
              hash: 27
         | 
| 5 5 | 
             
              prerelease: false
         | 
| 6 6 | 
             
              segments: 
         | 
| 7 7 | 
             
              - 0
         | 
| 8 | 
            +
              - 1
         | 
| 8 9 | 
             
              - 0
         | 
| 9 | 
            -
               | 
| 10 | 
            -
              version: 0.0.11
         | 
| 10 | 
            +
              version: 0.1.0
         | 
| 11 11 | 
             
            platform: ruby
         | 
| 12 12 | 
             
            authors: 
         | 
| 13 13 | 
             
            - Joshua Hull
         | 
| @@ -15,7 +15,7 @@ autorequire: | |
| 15 15 | 
             
            bindir: bin
         | 
| 16 16 | 
             
            cert_chain: []
         | 
| 17 17 |  | 
| 18 | 
            -
            date: 2010-08- | 
| 18 | 
            +
            date: 2010-08-20 00:00:00 -07:00
         | 
| 19 19 | 
             
            default_executable: 
         | 
| 20 20 | 
             
            dependencies: 
         | 
| 21 21 | 
             
            - !ruby/object:Gem::Dependency 
         | 
| @@ -156,7 +156,6 @@ files: | |
| 156 156 | 
             
            - spec/arg_spec.rb
         | 
| 157 157 | 
             
            - spec/cli_spec.rb
         | 
| 158 158 | 
             
            - spec/default_spec.rb
         | 
| 159 | 
            -
            - spec/dispatch_spec.rb
         | 
| 160 159 | 
             
            - spec/errors_spec.rb
         | 
| 161 160 | 
             
            - spec/help_spec.rb
         | 
| 162 161 | 
             
            - spec/option_spec.rb
         | 
    
        data/spec/dispatch_spec.rb
    DELETED
    
    | @@ -1,32 +0,0 @@ | |
| 1 | 
            -
            require 'spec_helper'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            describe "Optitron::Parser dispatching" do
         | 
| 4 | 
            -
              it "should dispatch 'install'" do
         | 
| 5 | 
            -
                m = mock('mock')
         | 
| 6 | 
            -
                m.should_receive('install').with()
         | 
| 7 | 
            -
                Optitron.dispatch(m, %w(install)) {
         | 
| 8 | 
            -
                  cmd "install"
         | 
| 9 | 
            -
                }
         | 
| 10 | 
            -
              end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
              it "should dispatch 'install file'" do
         | 
| 13 | 
            -
                m = mock('mock')
         | 
| 14 | 
            -
                m.should_receive('install').with('file.rb')
         | 
| 15 | 
            -
                Optitron.dispatch(m, %w(install file.rb)) {
         | 
| 16 | 
            -
                  cmd "install" do
         | 
| 17 | 
            -
                    arg "file"
         | 
| 18 | 
            -
                  end
         | 
| 19 | 
            -
                }
         | 
| 20 | 
            -
              end
         | 
| 21 | 
            -
             | 
| 22 | 
            -
              it "should dispatch 'install file --noop'" do
         | 
| 23 | 
            -
                m = mock('mock')
         | 
| 24 | 
            -
                m.should_receive('install').with('file.rb', {'noop' => true})
         | 
| 25 | 
            -
                Optitron.dispatch(m, %w(install file.rb --noop)) {
         | 
| 26 | 
            -
                  cmd "install" do
         | 
| 27 | 
            -
                    opt 'noop'
         | 
| 28 | 
            -
                    arg "file"
         | 
| 29 | 
            -
                  end
         | 
| 30 | 
            -
                }
         | 
| 31 | 
            -
              end
         | 
| 32 | 
            -
            end
         |