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
|