optitron 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +73 -0
- data/lib/optitron.rb +9 -0
- data/lib/optitron/help.rb +82 -0
- data/lib/optitron/parser.rb +10 -58
- data/lib/optitron/response.rb +9 -6
- data/lib/optitron/version.rb +1 -1
- data/spec/arg_spec.rb +15 -0
- data/spec/dispatch_spec.rb +3 -6
- data/spec/{other_spec.rb → help_spec.rb} +10 -1
- metadata +6 -5
data/README.rdoc
CHANGED
@@ -63,3 +63,76 @@ If you try parsing invalid parameters, get back friendly error messages
|
|
63
63
|
=> ["File is required"]
|
64
64
|
@parser.parse(%w(kill --pid=something)).error_messages
|
65
65
|
=> ["Pid is invalid"]
|
66
|
+
|
67
|
+
== Usage in a binary
|
68
|
+
|
69
|
+
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>
|
70
|
+
|
71
|
+
class Runner
|
72
|
+
def install(file, opts)
|
73
|
+
puts "installing #{file} with #{opts}!"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
Optitron.dispatch(Runner.new) {
|
78
|
+
opt 'verbose', "Be very loud"
|
79
|
+
cmd "install", "This installs things" do
|
80
|
+
arg "file", "The file to install"
|
81
|
+
end
|
82
|
+
cmd "show", "This shows things" do
|
83
|
+
arg "first", "The first thing to show"
|
84
|
+
arg "second", "The second optional thing to show", :required => false
|
85
|
+
end
|
86
|
+
cmd "kill", "This kills things" do
|
87
|
+
opt "pids", "A list of pids to kill", :type => :array
|
88
|
+
opt "pid", "A pid to kill", :type => :numeric
|
89
|
+
opt "names", "Some sort of hash", :type => :hash
|
90
|
+
end
|
91
|
+
cmd "join", "This joins things" do
|
92
|
+
arg "thing", "Stuff to join", :type => :greedy
|
93
|
+
end
|
94
|
+
}
|
95
|
+
|
96
|
+
Now, try running it.
|
97
|
+
|
98
|
+
crapbook-pro:optitron joshua$ ruby test.rb
|
99
|
+
Commands
|
100
|
+
|
101
|
+
show [first] <second> # This shows things
|
102
|
+
install [file] # This installs things
|
103
|
+
kill # This kills things
|
104
|
+
-p/--pids=[ARRAY] # A list of pids to kill
|
105
|
+
-P/--pid=[NUMERIC] # A pid to kill
|
106
|
+
-n/--names=[HASH] # Some sort of hash
|
107
|
+
join [thing1 thing2 ...] # This joins things
|
108
|
+
|
109
|
+
Global options
|
110
|
+
|
111
|
+
-v/--verbose # Be very loud
|
112
|
+
|
113
|
+
Errors:
|
114
|
+
Unknown command
|
115
|
+
|
116
|
+
crapbook-pro:optitron joshua$ ruby test.rb install
|
117
|
+
Commands
|
118
|
+
|
119
|
+
show [first] <second> # This shows things
|
120
|
+
install [file] # This installs things
|
121
|
+
kill # This kills things
|
122
|
+
-p/--pids=[ARRAY] # A list of pids to kill
|
123
|
+
-P/--pid=[NUMERIC] # A pid to kill
|
124
|
+
-n/--names=[HASH] # Some sort of hash
|
125
|
+
join [thing1 thing2 ...] # This joins things
|
126
|
+
|
127
|
+
Global options
|
128
|
+
|
129
|
+
-v/--verbose # Be very loud
|
130
|
+
|
131
|
+
Errors:
|
132
|
+
File is required
|
133
|
+
|
134
|
+
crapbook-pro:optitron joshua$ ruby test.rb install file
|
135
|
+
installing file with {"verbose"=>false}!
|
136
|
+
|
137
|
+
crapbook-pro:optitron joshua$ ruby test.rb install file --verbose true
|
138
|
+
installing file with {"verbose"=>true}!
|
data/lib/optitron.rb
CHANGED
@@ -4,9 +4,12 @@ class Optitron
|
|
4
4
|
autoload :Parser, 'optitron/parser'
|
5
5
|
autoload :Response, 'optitron/response'
|
6
6
|
autoload :Option, 'optitron/option'
|
7
|
+
autoload :Help, 'optitron/help'
|
7
8
|
|
8
9
|
InvalidParser = Class.new(RuntimeError)
|
9
10
|
|
11
|
+
attr_reader :parser
|
12
|
+
|
10
13
|
def initialize(&blk)
|
11
14
|
@parser = Parser.new
|
12
15
|
Dsl.new(@parser, &blk) if blk
|
@@ -20,6 +23,12 @@ class Optitron
|
|
20
23
|
Optitron.new(&blk).parse(args)
|
21
24
|
end
|
22
25
|
|
26
|
+
def self.dispatch(target = nil, args = ARGV, &blk)
|
27
|
+
optitron = Optitron.new(&blk)
|
28
|
+
optitron.parser.target = target
|
29
|
+
optitron.parser.parse(args).dispatch
|
30
|
+
end
|
31
|
+
|
23
32
|
def help
|
24
33
|
@parser.help
|
25
34
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
class Optitron
|
2
|
+
class Help
|
3
|
+
def initialize(parser)
|
4
|
+
@parser = parser
|
5
|
+
end
|
6
|
+
|
7
|
+
def help_line_for_opt(opt)
|
8
|
+
opt_line = ''
|
9
|
+
opt_line << [opt.short_name ? "-#{opt.short_name}" : nil, "--#{opt.name}"].compact.join('/')
|
10
|
+
opt_line << "=[#{opt.type.to_s.upcase}]" if opt.type != :boolean
|
11
|
+
[opt_line, opt.desc]
|
12
|
+
end
|
13
|
+
|
14
|
+
def help_line_for_arg(arg)
|
15
|
+
arg_line = ''
|
16
|
+
arg_line << (arg.required? ? '[' : '<')
|
17
|
+
if arg.type == :greedy
|
18
|
+
arg_line << arg.name << '1 ' << arg.name << '2 ...'
|
19
|
+
else
|
20
|
+
arg_line << arg.name
|
21
|
+
end
|
22
|
+
arg_line << (arg.required? ? ']' : '>')
|
23
|
+
arg_line
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate
|
27
|
+
cmds = {}
|
28
|
+
@parser.commands.each do |cmd_name, cmd|
|
29
|
+
cmd_line = "#{cmd_name}"
|
30
|
+
cmd.args.each do |arg|
|
31
|
+
cmd_line << " " << help_line_for_arg(arg)
|
32
|
+
end
|
33
|
+
cmds[cmd_line] = [cmd.desc]
|
34
|
+
cmd.options.each do |opt|
|
35
|
+
cmds[cmd_line] << help_line_for_opt(opt)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
opts_lines = @parser.options.map do |opt|
|
39
|
+
help_line_for_opt(opt)
|
40
|
+
end
|
41
|
+
|
42
|
+
args_lines = @parser.args.empty? ? nil : [@parser.args.map{|arg| help_line_for_arg(arg)}.join(' '), @parser.args.map{|arg| arg.desc}.join(', ')]
|
43
|
+
|
44
|
+
longest_line = 0
|
45
|
+
longest_line = [longest_line, cmds.keys.map{|k| k.size}.max].max unless cmds.empty?
|
46
|
+
opt_lines = cmds.map{|k,v| k.size + 2}.flatten
|
47
|
+
longest_line = [longest_line, args_lines.first.size].max if args_lines
|
48
|
+
longest_line = [longest_line, opt_lines.max].max unless opt_lines.empty?
|
49
|
+
longest_line = [opts_lines.map{|o| o.first.size}.max, longest_line].max unless opts_lines.empty?
|
50
|
+
help_output = []
|
51
|
+
|
52
|
+
unless cmds.empty?
|
53
|
+
help_output << "Commands\n\n" + cmds.map do |cmd, opts|
|
54
|
+
cmd_text = ""
|
55
|
+
cmd_text << "%-#{longest_line}s " % cmd
|
56
|
+
cmd_desc = opts.shift
|
57
|
+
cmd_text << "# #{cmd_desc}" if cmd_desc
|
58
|
+
opts.each do |opt|
|
59
|
+
cmd_text << "\n %-#{longest_line}s " % opt.first
|
60
|
+
cmd_text << "# #{opt.last}" if opt.last
|
61
|
+
end
|
62
|
+
cmd_text
|
63
|
+
end.join("\n")
|
64
|
+
end
|
65
|
+
if args_lines
|
66
|
+
arg_help = "Arguments\n\n"
|
67
|
+
arg_help << "%-#{longest_line}s " % args_lines.first
|
68
|
+
arg_help << "# #{args_lines.last}" if args_lines.first
|
69
|
+
help_output << arg_help
|
70
|
+
end
|
71
|
+
unless opts_lines.empty?
|
72
|
+
help_output << "Global options\n\n" + opts_lines.map do |opt|
|
73
|
+
opt_text = ''
|
74
|
+
opt_text << "%-#{longest_line}s " % opt.first
|
75
|
+
opt_text << "# #{opt.last}" if opt.last
|
76
|
+
opt_text
|
77
|
+
end.join("\n")
|
78
|
+
end
|
79
|
+
help_output.join("\n\n")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/optitron/parser.rb
CHANGED
@@ -1,16 +1,23 @@
|
|
1
1
|
class Optitron
|
2
2
|
class Parser
|
3
|
-
|
3
|
+
attr_accessor :target
|
4
|
+
attr_reader :commands, :options, :args
|
4
5
|
def initialize
|
5
6
|
@options = []
|
6
7
|
@commands = {}
|
8
|
+
@args = []
|
9
|
+
@help = Help.new(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def help
|
13
|
+
@help.generate
|
7
14
|
end
|
8
15
|
|
9
16
|
def parse(args = ARGV)
|
10
17
|
tokens = Tokenizer.new(args).tokens
|
11
|
-
response = Response.new(tokens)
|
18
|
+
response = Response.new(self, tokens)
|
12
19
|
options = @options
|
13
|
-
args =
|
20
|
+
args = @args
|
14
21
|
if !@commands.empty?
|
15
22
|
potential_cmd_toks = tokens.select { |t| t.respond_to?(:val) }
|
16
23
|
if cmd_tok = potential_cmd_toks.find { |t| @commands[t.val] }
|
@@ -43,60 +50,5 @@ class Optitron
|
|
43
50
|
def parse_args(tokens, args, response)
|
44
51
|
args.each { |arg| arg.consume(response, tokens) } if args
|
45
52
|
end
|
46
|
-
|
47
|
-
def help
|
48
|
-
cmds = {}
|
49
|
-
@commands.each do |cmd_name, cmd|
|
50
|
-
cmd_line = "#{cmd_name}"
|
51
|
-
cmd.args.each do |arg|
|
52
|
-
cmd_line << " "
|
53
|
-
cmd_line << (arg.required? ? '[' : '<')
|
54
|
-
if arg.type == :greedy
|
55
|
-
cmd_line << arg.name << '1 ' << arg.name << '2 ...'
|
56
|
-
else
|
57
|
-
cmd_line << arg.name
|
58
|
-
end
|
59
|
-
cmd_line << (arg.required? ? ']' : '>')
|
60
|
-
end
|
61
|
-
cmds[cmd_line] = [cmd.desc]
|
62
|
-
cmd.options.each do |opt|
|
63
|
-
opt_line = ''
|
64
|
-
opt_line << [opt.short_name ? "-#{opt.short_name}" : nil, "--#{opt.name}"].compact.join('/')
|
65
|
-
opt_line << "=[#{opt.type.to_s.upcase}]" if opt.type != :boolean
|
66
|
-
cmds[cmd_line] << [opt_line, opt.desc]
|
67
|
-
end
|
68
|
-
end
|
69
|
-
opts_lines = @options.map do |opt|
|
70
|
-
opt_line = ''
|
71
|
-
opt_line << [opt.short_name ? "-#{opt.short_name}" : nil, "--#{opt.name}"].compact.join('/')
|
72
|
-
opt_line << "=[#{opt.type.to_s.upcase}]" if opt.type != :boolean
|
73
|
-
[opt_line, opt.desc]
|
74
|
-
end
|
75
|
-
|
76
|
-
longest_line = cmds.keys.map{|k| k.size}.max
|
77
|
-
opt_lines = cmds.map{|k,v| k.size + 2}.flatten
|
78
|
-
longest_line = [longest_line, opt_lines.max].max unless opt_lines.empty?
|
79
|
-
longest_line = [opts_lines.map{|o| o.first.size}.max, longest_line].max unless opts_lines.empty?
|
80
|
-
help_output = "Commands\n\n" + cmds.map do |cmd, opts|
|
81
|
-
cmd_text = ""
|
82
|
-
cmd_text << "%-#{longest_line}s " % cmd
|
83
|
-
cmd_desc = opts.shift
|
84
|
-
cmd_text << "# #{cmd_desc}" if cmd_desc
|
85
|
-
opts.each do |opt|
|
86
|
-
cmd_text << "\n %-#{longest_line}s " % opt.first
|
87
|
-
cmd_text << "# #{opt.last}" if opt.last
|
88
|
-
end
|
89
|
-
cmd_text
|
90
|
-
end.join("\n")
|
91
|
-
unless opts_lines.empty?
|
92
|
-
help_output << "\n\nGlobal options\n\n"
|
93
|
-
help_output << opts_lines.map do |opt|
|
94
|
-
opt_text = ''
|
95
|
-
opt_text << "%-#{longest_line}s " % opt.first
|
96
|
-
opt_text << "# #{opt.last}" if opt.last
|
97
|
-
opt_text
|
98
|
-
end.join("\n")
|
99
|
-
end
|
100
|
-
end
|
101
53
|
end
|
102
54
|
end
|
data/lib/optitron/response.rb
CHANGED
@@ -2,8 +2,8 @@ class Optitron
|
|
2
2
|
class Response
|
3
3
|
attr_reader :params_array, :args, :params, :args_with_tokens, :errors
|
4
4
|
attr_accessor :command
|
5
|
-
def initialize(tokens)
|
6
|
-
@tokens = tokens
|
5
|
+
def initialize(parser, tokens)
|
6
|
+
@parser, @tokens = parser, tokens
|
7
7
|
@params_array = []
|
8
8
|
@args_with_tokens = []
|
9
9
|
@args = []
|
@@ -56,15 +56,18 @@ class Optitron
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
def dispatch
|
59
|
+
def dispatch
|
60
|
+
raise unless @parser.target
|
60
61
|
if valid?
|
61
62
|
dispatch_args = params.empty? ? args : args + [params]
|
62
|
-
|
63
|
+
@parser.target.send(command.to_sym, *dispatch_args)
|
63
64
|
else
|
64
|
-
|
65
|
+
puts @parser.help
|
66
|
+
puts "\nErrors:"
|
67
|
+
puts error_messages.join("\n")
|
65
68
|
end
|
66
69
|
end
|
67
|
-
|
70
|
+
|
68
71
|
def valid?
|
69
72
|
@errors.empty?
|
70
73
|
end
|
data/lib/optitron/version.rb
CHANGED
data/spec/arg_spec.rb
CHANGED
@@ -143,4 +143,19 @@ describe "Optitron::Parser arg spec" do
|
|
143
143
|
}.should raise_error(Optitron::InvalidParser)
|
144
144
|
end
|
145
145
|
end
|
146
|
+
|
147
|
+
context "root level args" do
|
148
|
+
before(:each) {
|
149
|
+
@parser = Optitron.new {
|
150
|
+
arg "file"
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
it "should parse" do
|
155
|
+
response = @parser.parse(%w(life.rb))
|
156
|
+
response.command.should be_nil
|
157
|
+
response.args.should == ['life.rb']
|
158
|
+
response.valid?.should be_true
|
159
|
+
end
|
160
|
+
end
|
146
161
|
end
|
data/spec/dispatch_spec.rb
CHANGED
@@ -4,32 +4,29 @@ describe "Optitron::Parser dispatching" do
|
|
4
4
|
it "should dispatch 'install'" do
|
5
5
|
m = mock('mock')
|
6
6
|
m.should_receive('install').with()
|
7
|
-
|
7
|
+
Optitron.dispatch(m, %w(install)) {
|
8
8
|
cmd "install"
|
9
9
|
}
|
10
|
-
@parser.parse(%w(install)).dispatch(m)
|
11
10
|
end
|
12
11
|
|
13
12
|
it "should dispatch 'install file'" do
|
14
13
|
m = mock('mock')
|
15
14
|
m.should_receive('install').with('file.rb')
|
16
|
-
|
15
|
+
Optitron.dispatch(m, %w(install file.rb)) {
|
17
16
|
cmd "install" do
|
18
17
|
arg "file"
|
19
18
|
end
|
20
19
|
}
|
21
|
-
@parser.parse(%w(install file.rb)).dispatch(m)
|
22
20
|
end
|
23
21
|
|
24
22
|
it "should dispatch 'install file --noop'" do
|
25
23
|
m = mock('mock')
|
26
24
|
m.should_receive('install').with('file.rb', {'noop' => true})
|
27
|
-
|
25
|
+
Optitron.dispatch(m, %w(install file.rb --noop)) {
|
28
26
|
cmd "install" do
|
29
27
|
opt 'noop'
|
30
28
|
arg "file"
|
31
29
|
end
|
32
30
|
}
|
33
|
-
@parser.parse(%w(install file.rb --noop)).dispatch(m)
|
34
31
|
end
|
35
32
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "Optitron::Parser help" do
|
4
|
-
it "generate help" do
|
4
|
+
it "generate help for command parsers" do
|
5
5
|
@parser = Optitron.new {
|
6
6
|
opt 'verbose', "Be very loud"
|
7
7
|
cmd "install", "This installs things" do
|
@@ -22,4 +22,13 @@ describe "Optitron::Parser help" do
|
|
22
22
|
}
|
23
23
|
@parser.help.should == "Commands\n\nshow [first] <second> # This shows things\ninstall [file] # This installs 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
|
+
|
26
|
+
it "generate help for non-command parsers" do
|
27
|
+
@parser = Optitron.new {
|
28
|
+
opt 'verbose', "Be very loud"
|
29
|
+
arg "src", "Source"
|
30
|
+
arg "dest", "Destination"
|
31
|
+
}
|
32
|
+
@parser.help.should == "Arguments\n\n[src] [dest] # Source, Destination\n\nGlobal options\n\n-v/--verbose # Be very loud"
|
33
|
+
end
|
25
34
|
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: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 5
|
10
|
+
version: 0.0.5
|
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-16 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -52,6 +52,7 @@ files:
|
|
52
52
|
- Rakefile
|
53
53
|
- lib/optitron.rb
|
54
54
|
- lib/optitron/dsl.rb
|
55
|
+
- lib/optitron/help.rb
|
55
56
|
- lib/optitron/option.rb
|
56
57
|
- lib/optitron/parser.rb
|
57
58
|
- lib/optitron/response.rb
|
@@ -62,8 +63,8 @@ files:
|
|
62
63
|
- spec/default_spec.rb
|
63
64
|
- spec/dispatch_spec.rb
|
64
65
|
- spec/errors_spec.rb
|
66
|
+
- spec/help_spec.rb
|
65
67
|
- spec/option_spec.rb
|
66
|
-
- spec/other_spec.rb
|
67
68
|
- spec/short_name_spec.rb
|
68
69
|
- spec/simple_spec.rb
|
69
70
|
- spec/spec.opts
|