optitron 0.0.4 → 0.0.5
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/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
|