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 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
@@ -1,16 +1,23 @@
1
1
  class Optitron
2
2
  class Parser
3
- attr_reader :commands, :options
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 = nil
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
@@ -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(obj)
59
+ def dispatch
60
+ raise unless @parser.target
60
61
  if valid?
61
62
  dispatch_args = params.empty? ? args : args + [params]
62
- obj.send(command.to_sym, *dispatch_args)
63
+ @parser.target.send(command.to_sym, *dispatch_args)
63
64
  else
64
- raise
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
@@ -1,3 +1,3 @@
1
1
  class Optitron
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
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
@@ -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
- @parser = Optitron.new {
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
- @parser = Optitron.new {
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
- @parser = Optitron.new {
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: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 4
10
- version: 0.0.4
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-15 00:00:00 -07:00
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