optitron 0.0.11 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|