bosonson 0.304.1

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.
Files changed (61) hide show
  1. data/CHANGELOG.rdoc +108 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.rdoc +181 -0
  4. data/bin/bss +6 -0
  5. data/bosonson.gemspec +24 -0
  6. data/deps.rip +2 -0
  7. data/lib/boson.rb +96 -0
  8. data/lib/boson/command.rb +196 -0
  9. data/lib/boson/commands.rb +7 -0
  10. data/lib/boson/commands/core.rb +77 -0
  11. data/lib/boson/commands/web_core.rb +153 -0
  12. data/lib/boson/index.rb +48 -0
  13. data/lib/boson/inspector.rb +120 -0
  14. data/lib/boson/inspectors/argument_inspector.rb +97 -0
  15. data/lib/boson/inspectors/comment_inspector.rb +100 -0
  16. data/lib/boson/inspectors/method_inspector.rb +98 -0
  17. data/lib/boson/libraries/file_library.rb +144 -0
  18. data/lib/boson/libraries/gem_library.rb +30 -0
  19. data/lib/boson/libraries/local_file_library.rb +30 -0
  20. data/lib/boson/libraries/module_library.rb +37 -0
  21. data/lib/boson/libraries/require_library.rb +23 -0
  22. data/lib/boson/library.rb +179 -0
  23. data/lib/boson/loader.rb +118 -0
  24. data/lib/boson/manager.rb +169 -0
  25. data/lib/boson/namespace.rb +31 -0
  26. data/lib/boson/option_command.rb +222 -0
  27. data/lib/boson/option_parser.rb +475 -0
  28. data/lib/boson/options.rb +146 -0
  29. data/lib/boson/pipe.rb +147 -0
  30. data/lib/boson/pipes.rb +75 -0
  31. data/lib/boson/repo.rb +107 -0
  32. data/lib/boson/repo_index.rb +124 -0
  33. data/lib/boson/runner.rb +81 -0
  34. data/lib/boson/runners/bin_runner.rb +208 -0
  35. data/lib/boson/runners/console_runner.rb +58 -0
  36. data/lib/boson/scientist.rb +182 -0
  37. data/lib/boson/util.rb +129 -0
  38. data/lib/boson/version.rb +3 -0
  39. data/lib/boson/view.rb +95 -0
  40. data/test/argument_inspector_test.rb +62 -0
  41. data/test/bin_runner_test.rb +223 -0
  42. data/test/command_test.rb +22 -0
  43. data/test/commands_test.rb +22 -0
  44. data/test/comment_inspector_test.rb +126 -0
  45. data/test/deps.rip +4 -0
  46. data/test/file_library_test.rb +42 -0
  47. data/test/loader_test.rb +235 -0
  48. data/test/manager_test.rb +114 -0
  49. data/test/method_inspector_test.rb +90 -0
  50. data/test/option_parser_test.rb +367 -0
  51. data/test/options_test.rb +189 -0
  52. data/test/pipes_test.rb +65 -0
  53. data/test/repo_index_test.rb +122 -0
  54. data/test/repo_test.rb +23 -0
  55. data/test/runner_test.rb +40 -0
  56. data/test/scientist_test.rb +341 -0
  57. data/test/test_helper.rb +130 -0
  58. data/test/util_test.rb +56 -0
  59. data/vendor/bundle/gems/bacon-bits-0.1.0/deps.rip +1 -0
  60. data/vendor/bundle/gems/hirb-0.6.0/test/deps.rip +4 -0
  61. metadata +217 -0
@@ -0,0 +1,189 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe "Options" do
4
+ def create(opts)
5
+ @opt = OptionParser.new(opts)
6
+ end
7
+
8
+ def parse(*args)
9
+ @non_opts = []
10
+ @opt.parse(args.flatten)
11
+ end
12
+
13
+ describe ":string type" do
14
+ before {
15
+ create "--foo" => :string, "--bar" => :string, :blah=>{:type=>:string, :default=>:huh}
16
+ }
17
+
18
+ it "doesn't set nonexistant options" do
19
+ parse("--bling")[:bar].should == nil
20
+ end
21
+
22
+ it "sets values correctly" do
23
+ parse("--foo", "12")[:foo].should == "12"
24
+ parse("--bar", "12")[:bar].should == "12"
25
+ end
26
+
27
+ it "raises error if passed another valid option" do
28
+ assert_error(OptionParser::Error, "cannot pass.*'foo'") { parse("--foo", "--bar") }
29
+ end
30
+
31
+ it "raises error if not passed a value" do
32
+ assert_error(OptionParser::Error, "no value.*'foo'") { parse("--foo") }
33
+ end
34
+
35
+ it "overwrites earlier values with later values" do
36
+ parse("--foo", "12", "--foo", "13")[:foo].should == "13"
37
+ end
38
+
39
+ it "can have symbolic default value" do
40
+ parse('--blah','ok')[:blah].should == 'ok'
41
+ end
42
+ end
43
+
44
+ describe ":string type with :values attribute" do
45
+ before_all { create :foo=>{:type=>:string, :values=>%w{angola abu abib}} }
46
+ it "auto aliases if a match exists" do
47
+ parse("-f", "an")[:foo].should == 'angola'
48
+ end
49
+
50
+ it "auto aliases first sorted match" do
51
+ parse("-f", "a")[:foo].should == 'abib'
52
+ end
53
+
54
+ it "raises error if option doesn't auto alias or match given values" do
55
+ assert_error(OptionParser::Error, "invalid.*'z'") { parse("-f", "z") }
56
+ end
57
+
58
+ it "doesn't raise error for a nonmatch if enum is false" do
59
+ create :foo=>{:type=>:string, :values=>%w{angola abu abib}, :enum=>false}
60
+ parse("-f", "z")[:foo].should == 'z'
61
+ end
62
+ end
63
+
64
+ describe ":string type with default value" do
65
+ before { create "--branch" => "master" }
66
+
67
+ it "should get the specified value" do
68
+ parse("--branch", "bugfix").should == { :branch => "bugfix" }
69
+ end
70
+
71
+ it "should get the default value when not specified" do
72
+ parse.should == { :branch => "master" }
73
+ end
74
+ end
75
+
76
+ describe ":numeric type" do
77
+ before { create "n" => :numeric, "m" => 5 }
78
+
79
+ it "supports numeric defaults" do
80
+ parse["m"].should == 5
81
+ end
82
+
83
+ it "converts values to numeric types" do
84
+ parse("-n", "3", "-m", ".5").should == {:n => 3, :m => 0.5}
85
+ end
86
+
87
+ it "raises error when value isn't numeric" do
88
+ assert_error(OptionParser::Error, "expected numeric value for.*'n'") { parse("-n", "foo") }
89
+ end
90
+
91
+ it "raises error when opt is present without value" do
92
+ assert_error(OptionParser::Error, "no value.*'n'") { parse("-n") }
93
+ end
94
+ end
95
+
96
+ describe ":array type" do
97
+ before_all {
98
+ create :a=>:array, :b=>[1,2,3], :c=>{:type=>:array, :values=>%w{foo fa bar zebra}, :enum=>false},
99
+ :d=>{:type=>:array, :split=>" ", :values=>[:ab, :bc, :cd], :enum=>false},
100
+ :e=>{:type=>:array, :values=>%w{some so silly}, :regexp=>true}
101
+ }
102
+
103
+ it "supports array defaults" do
104
+ parse[:b].should == [1,2,3]
105
+ end
106
+
107
+ it "converts comma delimited values to an array" do
108
+ parse("-a","1,2,5")[:a].should == %w{1 2 5}
109
+ end
110
+
111
+ it "raises error when option has no value" do
112
+ assert_error(OptionParser::Error, "no value.*'a'") { parse("-a") }
113
+ end
114
+
115
+ it "auto aliases :values attribute" do
116
+ parse("-c","f,b")[:c].should == %w{fa bar}
117
+ end
118
+
119
+ it "auto aliases symbolic :values" do
120
+ parse("-d","a c")[:d].should == [:ab,:cd]
121
+ end
122
+
123
+ it "supports a configurable splitter" do
124
+ parse("-d", "yogi berra")[:d].should == %w{yogi berra}
125
+ end
126
+
127
+ it "aliases * to all values" do
128
+ parse("-c", '*')[:c].sort.should == %w{bar fa foo zebra}
129
+ parse("-c", '*,ok')[:c].sort.should == %w{bar fa foo ok zebra}
130
+ end
131
+
132
+ it "aliases correctly with :regexp on" do
133
+ parse("-e", 'so')[:e].sort.should == %w{so some}
134
+ end
135
+ end
136
+
137
+ describe ":hash type" do
138
+ before_all {
139
+ create :a=>:hash, :b=>{:default=>{:a=>'b'}}, :c=>{:type=>:hash, :keys=>%w{one two three}},
140
+ :e=>{:type=>:hash, :keys=>[:one, :two, :three], :default_keys=>:three},
141
+ :d=>{:type=>:hash, :split=>" "}
142
+ }
143
+
144
+ it "converts comma delimited pairs to hash" do
145
+ parse("-a", "f:3,g:4")[:a].should == {'f'=>'3', 'g'=>'4'}
146
+ end
147
+
148
+ it "supports hash defaults" do
149
+ parse[:b].should == {:a=>'b'}
150
+ end
151
+
152
+ it "raises error when option has no value" do
153
+ assert_error(OptionParser::Error, "no value.*'a'") { parse("-a") }
154
+ end
155
+
156
+ it "raises error if invalid key-value pair given for unknown keys" do
157
+ assert_error(OptionParser::Error, "invalid.*pair.*'a'") { parse("-a", 'b') }
158
+ end
159
+
160
+ it "auto aliases :keys attribute" do
161
+ parse("-c","t:3,o:1")[:c].should == {'three'=>'3', 'one'=>'1'}
162
+ end
163
+
164
+ it "adds in explicit default keys with value only argument" do
165
+ parse('-e', 'whoop')[:e].should == {:three=>'whoop'}
166
+ end
167
+
168
+ it "adds in default keys from known :keys with value only argument" do
169
+ parse("-c","okay")[:c].should == {'one'=>'okay'}
170
+ end
171
+
172
+ it "auto aliases symbolic :keys" do
173
+ parse("-e","t:3,o:1")[:e].should == {:three=>'3', :one=>'1'}
174
+ end
175
+
176
+ it "supports a configurable splitter" do
177
+ parse("-d","a:ab b:bc")[:d].should == {'a'=>'ab', 'b'=>'bc'}
178
+ end
179
+
180
+ it "supports grouping keys" do
181
+ parse("-c", "t,tw:foo,o:bar")[:c].should == {'three'=>'foo','two'=>'foo', 'one'=>'bar'}
182
+ end
183
+
184
+ it "aliases * to all keys" do
185
+ parse("-c", "*:foo")[:c].should == {'three'=>'foo', 'two'=>'foo', 'one'=>'foo'}
186
+ parse('-a', '*:foo')[:a].should == {'*'=>'foo'}
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,65 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe "Pipes" do
4
+ before_all { ::Ab = Struct.new(:a, :b) }
5
+ describe "query_pipe" do
6
+ before_all {
7
+ @hashes = [{:a=>'some', :b=>'thing'}, {:a=>:more, :b=>:yep}]
8
+ @objects = [Ab.new('some', 'thing'), Ab.new(:more, :yep)]
9
+ }
10
+
11
+ it "searches one query" do
12
+ [@hashes, @objects].each {|e|
13
+ Pipes.query_pipe(e, :a=>'some').should == e[0,1]
14
+ }
15
+ end
16
+
17
+ it "searches non-string values" do
18
+ [@hashes, @objects].each {|e|
19
+ Pipes.query_pipe(e, :a=>'more').should == e[1,1]
20
+ }
21
+ end
22
+
23
+ it "searches multiple search terms" do
24
+ [@hashes, @objects].each {|e|
25
+ Pipes.query_pipe(e, :a=>'some', :b=>'yep').size.should == 2
26
+ }
27
+ end
28
+
29
+ it "prints error for invalid search field" do
30
+ capture_stderr { Pipes.query_pipe(@objects, :blah=>'blah') }.should =~ /failed.*'blah'/
31
+ end
32
+ end
33
+
34
+ describe "sort_pipe" do
35
+ before_all {
36
+ @hashes = [{:a=>'some', :b=>'thing'}, {:a=>:more, :b=>:yep}]
37
+ @objects = [Ab.new('some', 'thing'), Ab.new(:more, :yep)]
38
+ }
39
+ it "sorts objects with values of different types" do
40
+ Pipes.sort_pipe(@objects, :a).should == @objects.reverse
41
+ end
42
+
43
+ it "sorts hashes with values of different types" do
44
+ Pipes.sort_pipe(@hashes, :a).should == @hashes.reverse
45
+ end
46
+
47
+ it "sorts numeric values" do
48
+ hashes = [{:a=>10, :b=>4}, {:a=>5, :b=>3}]
49
+ Pipes.sort_pipe(hashes, :a).should == hashes.reverse
50
+ end
51
+
52
+ it "sorts numeric hash keys by string" do
53
+ hashes = [{2=>'thing'}, {2=>'some'}]
54
+ Pipes.sort_pipe(hashes, '2').should == hashes.reverse
55
+ end
56
+
57
+ it "sorts numeric hash keys by string" do
58
+ Pipes.sort_pipe(@hashes, 'a').should == @hashes.reverse
59
+ end
60
+
61
+ it "prints error for invalid sort field" do
62
+ capture_stderr { Pipes.sort_pipe(@objects, :blah)}.should =~ /failed.*'blah'/
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,122 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe "RepoIndex" do
4
+ # since we're defining our own @commands, @libraries, @lib_hashes
5
+ def index
6
+ @index ||= begin
7
+ ind = RepoIndex.new(Boson.repo)
8
+ ind.instance_variable_set "@read", true
9
+ ind
10
+ end
11
+ end
12
+
13
+ describe "read_and_transfer" do
14
+ before { reset_boson; index.instance_eval "@libraries = @commands = nil" }
15
+
16
+ def transfers(options={})
17
+ index.instance_variable_set "@libraries", [Library.new(:name=>'blah', :commands=>['blurb']),
18
+ Library.new(:name=>'bling')]
19
+ index.instance_variable_set "@commands", [Command.new(:name=>'blurb', :lib=>'blah')]
20
+ index.read_and_transfer options[:ignored] || []
21
+ Boson.libraries.map {|e| e.name}.should == options[:libraries]
22
+ Boson.commands.map {|e| e.name}.should == options[:commands]
23
+ end
24
+
25
+ it "transfers libraries with no libraries to ignore" do
26
+ transfers :libraries=>%w{blah bling}, :commands=>%w{blurb}, :ignored=>[]
27
+ end
28
+
29
+ it "transfers libraries and commands except for ignored libraries and its commands" do
30
+ transfers :libraries=>%w{bling}, :commands=>[], :ignored=>%w{blah}
31
+ end
32
+
33
+ it "doesn't replace existing libraries" do
34
+ lib = Library.new(:name=>'blah')
35
+ cmd = Command.new(:name=>'blurb', :lib=>'blah')
36
+ Boson.libraries << lib
37
+ Boson.commands << cmd
38
+ transfers :libraries=>%w{blah bling}, :commands=>%w{blurb}
39
+ Boson.libraries.include?(lib).should == true
40
+ Boson.commands.include?(cmd).should == true
41
+ end
42
+ end
43
+
44
+ describe "find_library" do
45
+ before_all {
46
+ reset_boson
47
+ commands = [Command.new(:name=>'blurb', :lib=>'blah', :alias=>'bb'),
48
+ Command.new(:name=>'sub', :lib=>'bling', :alias=>'s')
49
+ ]
50
+ index.instance_variable_set "@commands", commands
51
+ index.instance_variable_set "@libraries", [Library.new(:name=>'blah'), Library.new(:name=>'bling', :namespace=>'bling')]
52
+ }
53
+
54
+ it "finds command aliased or not" do
55
+ index.find_library('blurb').should == 'blah'
56
+ index.find_library('bb').should == 'blah'
57
+ end
58
+
59
+ it "doesn't find command" do
60
+ index.find_library('blah').should == nil
61
+ end
62
+
63
+ it "finds a subcommand aliased or not" do
64
+ index.find_library('bling.sub').should == 'bling'
65
+ # @index.find_library('bl.s').should == 'bling'
66
+ end
67
+
68
+ it "finds namespace command aliased or not without a subcommand" do
69
+ index.find_library('bling').should == 'bling'
70
+ # @index.find_library('bl').should == 'bling'
71
+ end
72
+
73
+ it "doesn't find a subcommand" do
74
+ index.find_library('d.d').should == nil
75
+ end
76
+ end
77
+
78
+ describe "changed_libraries" do
79
+ before_all { index.instance_eval "@lib_hashes = nil" }
80
+
81
+ def changed(string, all_libs=['file1'])
82
+ index.repo.expects(:all_libraries).returns(all_libs)
83
+ index.instance_variable_set "@lib_hashes", {"file1"=>Digest::MD5.hexdigest("state1")}
84
+ File.stubs(:exists?).returns(true)
85
+ File.expects(:read).returns(string)
86
+ index.changed_libraries
87
+ end
88
+
89
+ it "detects changed libraries" do
90
+ changed("state2").should == %w{file1}
91
+ end
92
+
93
+ it "detects new libraries" do
94
+ changed("state1", ['file2']).should == %w{file2}
95
+ end
96
+
97
+ it "detects no changed libraries" do
98
+ changed("state1").should == []
99
+ end
100
+ end
101
+
102
+ describe "write" do
103
+ before_all {
104
+ reset_boson
105
+ Boson.commands << Command.new(:name=>'blah', :lib=>'blah', :args=>[['arg1', {}], ['arg2', self.class]])
106
+ Boson.libraries << Library.new(:name=>'blah', :module=>self.class)
107
+ index.expects(:latest_hashes)
108
+ libraries = commands = []
109
+ index.expects(:save_marshal_index).with {|str| libraries, commands, hashes = Marshal.load(str) ; true}
110
+ index.write
111
+ @index_hash = {:libraries=>libraries, :commands=>commands}
112
+ }
113
+
114
+ it "saves library module constants as strings" do
115
+ @index_hash[:libraries][0].module.class.should == String
116
+ end
117
+
118
+ it "save commands with arg values as strings" do
119
+ @index_hash[:commands][0].args.each {|e| e[1].class.should == String}
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,23 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe "config" do
4
+ before_all { reset }
5
+ before { @repo = Repo.new(File.dirname(__FILE__)) }
6
+
7
+ it "reloads config when passed true" do
8
+ @repo.config.object_id.should.not == @repo.config(true).object_id
9
+ end
10
+
11
+ it "reads existing config correctly" do
12
+ expected_hash = {:commands=>{'c1'=>{}}, :libraries=>{}}
13
+ File.expects(:exists?).returns(true)
14
+ YAML.expects(:load_file).returns(expected_hash)
15
+ @repo.config[:commands]['c1'].should == {}
16
+ end
17
+
18
+ it "ignores nonexistent file and sets config defaults" do
19
+ @repo.config[:command_aliases].class.should == Hash
20
+ @repo.config[:libraries].class.should == Hash
21
+ end
22
+ after_all { FileUtils.rm_r File.dirname(__FILE__)+'/config', :force=>true }
23
+ end
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe "repl_runner" do
4
+ def start(hash={})
5
+ Hirb.stubs(:enable)
6
+ Boson.start(hash.merge(:verbose=>false))
7
+ end
8
+
9
+ before_all { reset }
10
+ before { ConsoleRunner.instance_eval("@initialized = false") }
11
+
12
+ it "loads default libraries and libraries in :console_defaults config" do
13
+ defaults = Runner.default_libraries + ['yo']
14
+ with_config(:console_defaults=>['yo']) do
15
+ Manager.expects(:load).with {|*args| args[0] == defaults }
16
+ start
17
+ end
18
+ end
19
+
20
+ it "doesn't call init twice" do
21
+ capture_stderr { start }
22
+ ConsoleRunner.expects(:init).never
23
+ start
24
+ end
25
+
26
+ it "loads multiple libraries with :libraries option" do
27
+ ConsoleRunner.expects(:init)
28
+ Manager.expects(:load).with([:lib1,:lib2], anything)
29
+ start(:libraries=>[:lib1, :lib2])
30
+ end
31
+
32
+ it "autoloader autoloads libraries" do
33
+ start(:autoload_libraries=>true)
34
+ Index.expects(:read)
35
+ Index.expects(:find_library).with('blah').returns('blah')
36
+ Manager.expects(:load).with('blah', anything)
37
+ Boson.main_object.blah
38
+ end
39
+ after_all { FileUtils.rm_r File.dirname(__FILE__)+'/config', :force=>true }
40
+ end
@@ -0,0 +1,341 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe "Scientist" do
4
+ before_all {
5
+ Runner.in_shell = nil
6
+ eval <<-EOF
7
+ module Blah
8
+ def blah(arg1, options={})
9
+ [arg1, options]
10
+ end
11
+ def splat_blah(*args)
12
+ args
13
+ end
14
+ def default_blah(arg1, arg2=default, options={})
15
+ [arg1, arg2, options]
16
+ end
17
+ def default; 'some default'; end
18
+ def default_option(options={})
19
+ options
20
+ end
21
+ end
22
+ EOF
23
+ @opt_cmd = Object.new.extend Blah
24
+ }
25
+
26
+ def command(hash, args)
27
+ hash = {:name=>'blah', :lib=>'bling', :options=>{:force=>:boolean, :level=>2}}.merge(hash)
28
+ @cmd = Command.new hash
29
+ @cmd.instance_variable_set("@file_parsed_args", true) if hash[:file_parsed_args]
30
+ Scientist.redefine_command(@opt_cmd, @cmd)
31
+ @opt_cmd.send(hash[:name], *args)
32
+ end
33
+
34
+ def command_with_arg_size(*args)
35
+ command({:args=>2}, args)
36
+ end
37
+
38
+ def command_with_args(*args)
39
+ command({:args=>[['arg1'],['options', {}]]}, args)
40
+ end
41
+
42
+ def basic_command(hash, args)
43
+ command({:name=>'splat_blah', :args=>'*'}.merge(hash), args)
44
+ end
45
+
46
+ def command_with_splat_args(*args)
47
+ command({:name=>'splat_blah', :args=>'*'}, args)
48
+ end
49
+
50
+ def command_with_arg_defaults(*args)
51
+ arg_defaults = [%w{arg1}, %w{arg2 default}, %w{options {}}]
52
+ command({:name=>'default_blah', :file_parsed_args=>true, :args=>arg_defaults}, args)
53
+ end
54
+
55
+ def args_are_equal(args, array)
56
+ command_with_args(*args).should == array
57
+ command_with_arg_size(*args).should == array
58
+ command_with_splat_args(*args).should == array
59
+ end
60
+
61
+ def all_commands
62
+ [:command_with_args, :command_with_arg_size, :command_with_splat_args]
63
+ end
64
+
65
+ describe "all commands" do
66
+ it "translate arg and options as one string" do
67
+ args_are_equal ['a1 -f'], ['a1', {:force=>true, :level=>2}]
68
+ end
69
+
70
+ it "translate arg and stringified options" do
71
+ args_are_equal [:cool, '-l3'], [:cool, {:level=>3}]
72
+ end
73
+
74
+ it "translate arg and normal hash options" do
75
+ args_are_equal [:cool, {:ok=>true}], [:cool, {:ok=>true, :level=>2}]
76
+ end
77
+
78
+ it "translate stringified arg without options sets default options" do
79
+ args_are_equal ['cool'], ['cool', {:level=>2}]
80
+ end
81
+
82
+ it "translate arg without options sets default options" do
83
+ args_are_equal [:cool], [:cool, {:level=>2}]
84
+ end
85
+
86
+ it "with invalid options print errors and delete them" do
87
+ all_commands.each do |cmd|
88
+ capture_stderr {
89
+ send(cmd, 'cool -f -z').should == ['cool', {:force=>true, :level=>2}]
90
+ }.should =~/invalid.*z/
91
+ end
92
+ end
93
+
94
+ it "print help with help option" do
95
+ all_commands.each do |cmd|
96
+ Boson.expects(:invoke).with(:usage, anything, anything)
97
+ send(cmd, '-h')
98
+ end
99
+ end
100
+ end
101
+
102
+ describe "command" do
103
+ describe "with arg defaults" do
104
+ it "sets defaults with stringified args" do
105
+ command_with_arg_defaults('1').should == ["1", "some default", {:level=>2}]
106
+ end
107
+
108
+ it "sets defaults with normal args" do
109
+ command_with_arg_defaults(1).should == [1, "some default", {:level=>2}]
110
+ end
111
+
112
+ it "sets default if optional arg is a valid option" do
113
+ command_with_arg_defaults("cool -f").should == ["cool", "some default", {:level=>2, :force=>true}]
114
+ end
115
+
116
+ it "doesn't set defaults if not needed" do
117
+ command_with_arg_defaults(1, 'nada').should == [1, "nada", {:level=>2}]
118
+ end
119
+
120
+ it "prints error for invalid defaults" do
121
+ arg_defaults = [%w{arg1}, %w{arg2 invalidzzz}, %w{options {}}]
122
+ capture_stderr {
123
+ command({:name=>'default_blah', :file_parsed_args=>true, :args=>arg_defaults}, [1])
124
+ }.should =~ /Error.*position 2/
125
+ end
126
+ end
127
+
128
+ describe "prints error" do
129
+ it "with option error" do
130
+ capture_stderr { command_with_args('a1 -l') }.should =~ /Error.*level/
131
+ end
132
+
133
+ it "with unexpected error in render" do
134
+ Scientist.expects(:can_render?).raises("unexpected")
135
+ capture_stderr { command_with_args('a1') }.should =~ /Error.*unexpected/
136
+ end
137
+
138
+ it "with no argument defined for options" do
139
+ assert_error(OptionCommand::CommandArgumentError, '2 for 1') { command({:args=>1}, 'ok') }
140
+ end
141
+ end
142
+
143
+ it "translates stringfied args + options starting at second arg" do
144
+ command_with_arg_defaults(1, 'nada -l3').should == [1, "nada", {:level=>3}]
145
+ end
146
+
147
+ it "with leading option-like args are translated as arguments" do
148
+ command_with_args('-z -f').should == ["-z", {:force=>true, :level=>2}]
149
+ command_with_args('--blah -f').should == ['--blah', {:force=>true, :level=>2}]
150
+ end
151
+
152
+ it "with splat args does not raise error for too few or many args" do
153
+ [[], [''], [1,2,3], ['1 2 3']].each do |args|
154
+ should.not.raise { command_with_splat_args *args }
155
+ end
156
+ end
157
+
158
+ it "with debug option prints debug" do
159
+ capture_stdout { command_with_args("-v ok") }.should =~ /Arguments.*ok/
160
+ end
161
+
162
+ it "with pretend option prints arguments and returns early" do
163
+ Scientist.expects(:render_or_raw).never
164
+ capture_stdout { command_with_args("-p ok") }.should =~ /Arguments.*ok/
165
+ end
166
+
167
+ it "with not enough args raises CommandArgumentError" do
168
+ args = [OptionCommand::CommandArgumentError, '0 for 1']
169
+ assert_error(*args) { command_with_args }
170
+ assert_error(*args) { command_with_args '' }
171
+ assert_error(*args) { command_with_arg_size }
172
+ assert_error(*args) { command_with_arg_size '' }
173
+ end
174
+
175
+ it "with too many args raises CommandArgumentError" do
176
+ args3 = [ArgumentError, '3 for 2']
177
+ args4 = [OptionCommand::CommandArgumentError, '4 for 2']
178
+ assert_error(*args3) { command_with_args 1,2,3 }
179
+ assert_error(*args4) { command_with_args '1 2 3' }
180
+ assert_error(*args3) { command_with_arg_size 1,2,3 }
181
+ assert_error(*args4) { command_with_arg_size '1 2 3' }
182
+ end
183
+ end
184
+
185
+ def command_with_render(*args)
186
+ basic_command({:render_options=>{:fields=>{:values=>['f1', 'f2']}} }, args)
187
+ end
188
+
189
+ def render_expected(options=nil)
190
+ View.expects(:render).with(anything, options || anything, false)
191
+ end
192
+
193
+ describe "render" do
194
+ it "called for command with render_options" do
195
+ render_expected
196
+ command_with_render('1')
197
+ end
198
+
199
+ it "called for command without render_options and --render" do
200
+ render_expected
201
+ command_with_args('--render 1')
202
+ end
203
+
204
+ it "not called for command with render_options and --render" do
205
+ Boson.expects(:invoke).never
206
+ command_with_render('--render 1')
207
+ end
208
+
209
+ it "not called for command without render_options" do
210
+ Boson.expects(:invoke).never
211
+ command_with_args('1')
212
+ end
213
+ end
214
+
215
+ describe "command renders" do
216
+ it "with basic render options" do
217
+ render_expected :fields => ['f1', 'f2']
218
+ command_with_render("--fields f1,f2 ab")
219
+ end
220
+
221
+ it "without non-render options" do
222
+ render_expected :fields=>['f1']
223
+ Scientist.expects(:can_render?).returns(true)
224
+ args = ["--render --fields f1 ab"]
225
+ basic_command({:render_options=>{:fields=>{:values=>['f1', 'f2']}} }, args)
226
+ end
227
+
228
+ it "with user-defined render options" do
229
+ render_expected :fields=>['f1'], :foo=>true
230
+ args = ["--foo --fields f1 ab"]
231
+ basic_command({:render_options=>{:foo=>:boolean, :fields=>{:values=>['f1', 'f2']}} }, args)
232
+ end
233
+
234
+ it "with non-hash user-defined render options" do
235
+ render_expected :fields=>['f1'], :foo=>true
236
+ args = ["--foo --fields f1 ab"]
237
+ basic_command({:render_options=>{:foo=>:boolean, :fields=>%w{f1 f2 f3}} }, args)
238
+ end
239
+ end
240
+
241
+ describe "command with default option" do
242
+ before_all { @cmd_attributes = {:name=>'default_option', :default_option=>'level', :args=>1} }
243
+
244
+ it "parses normally from irb" do
245
+ command(@cmd_attributes, '-f --level=3').should == {:level=>3, :force=>true}
246
+ end
247
+
248
+ it "parses normally from cmdline" do
249
+ Runner.expects(:in_shell?).times(2).returns true
250
+ command(@cmd_attributes, ['--force', '--level=3']).should == {:level=>3, :force=>true}
251
+ end
252
+
253
+ it "parses no arguments normally" do
254
+ command(@cmd_attributes, '').should == {:level=>2}
255
+ end
256
+
257
+ it "parses ruby arguments normally" do
258
+ command(@cmd_attributes, [{:force=>true, :level=>10}]).should == {:level=>10, :force=>true}
259
+ end
260
+
261
+ it "prepends correctly from irb" do
262
+ command(@cmd_attributes, '3 -f').should == {:level=>3, :force=>true}
263
+ end
264
+
265
+ it "prepends correctly from cmdline" do
266
+ Runner.expects(:in_shell?).times(2).returns true
267
+ command(@cmd_attributes, ['3','-f']).should == {:level=>3, :force=>true}
268
+ end
269
+ end
270
+
271
+ it "optionless command renders" do
272
+ render_expected :fields=>['f1']
273
+ command({:args=>2, :options=>nil, :render_options=>{:fields=>:array}}, ["--fields f1 ab ok"])
274
+ end
275
+
276
+ it "redefine_command prints error for command with nonexistant method" do
277
+ capture_stderr {
278
+ Scientist.redefine_command Object.new, Command.new(:name=>'blah', :lib=>'blah')
279
+ }.should =~ /Error: No method.*'blah'/
280
+ end
281
+
282
+ describe "global options:" do
283
+ def local_and_global(*args)
284
+ Scientist.stubs(:can_render?).returns(false) # turn off rendering caused by :render_options
285
+ @non_opts = basic_command(@command_options, args)
286
+ @non_opts.slice!(-1,1) << Scientist.global_options
287
+ end
288
+
289
+ before_all {
290
+ @command_options = {:options=>{:do=>:boolean, :foo=>:boolean},
291
+ :render_options=>{:dude=>:boolean}}
292
+ @expected_non_opts = [[], ['doh'], ['doh'], [:doh]]
293
+ }
294
+
295
+ it "local option overrides global one" do
296
+ ['-d', 'doh -d','-d doh', [:doh, '-d']].each_with_index do |args, i|
297
+ local_and_global(*args).should == [{:do=>true}, {}]
298
+ @non_opts.should == @expected_non_opts[i]
299
+ end
300
+ end
301
+
302
+ it "global option before local one is valid" do
303
+ args_arr = ['--dude -f', '--dude doh -f', '--dude -f doh', [:doh, '--dude -f']]
304
+ args_arr.each_with_index do |args, i|
305
+ local_and_global(*args).should == [{:foo=>true}, {:dude=>true}]
306
+ @non_opts.should == @expected_non_opts[i]
307
+ end
308
+ end
309
+
310
+ it "delete_options deletes global options" do
311
+ local_and_global('--delete_options=r,p -rp -f').should ==
312
+ [{:foo=>true}, {:delete_options=>["r", "p"]}]
313
+ end
314
+
315
+ it "global option after local one is invalid" do
316
+ args_arr = ['-f --dude', '-f doh --dude', '-f --dude doh', [:doh, '-f --dude'] ]
317
+ args_arr.each_with_index do |args, i|
318
+ capture_stderr {
319
+ local_and_global(*args).should == [{:foo=>true}, {}]
320
+ @non_opts.should == @expected_non_opts[i]
321
+ }.should =~ /invalid.*dude/
322
+ end
323
+ end
324
+
325
+ it "global option after local one and -" do
326
+ local_and_global("doh -r -f - --dude").should == [{:foo=>true}, {:dude=>true, :render=>true}]
327
+ end
328
+
329
+ it "conflicting global option after -" do
330
+ local_and_global('doh - -f=1,2').should == [{}, {:fields=>["1", "2"]}]
331
+ end
332
+
333
+ it "no options parsed after --" do
334
+ local_and_global('doh -f -- -r').should == [{:foo=>true}, {}]
335
+ local_and_global('doh -- -r -f').should == [{}, {}]
336
+ local_and_global('-- -r -f').should == [{}, {}]
337
+ local_and_global('doh -r -- -f').should == [{}, {:render=>true}]
338
+ end
339
+ end
340
+ after_all { Runner.in_shell = false }
341
+ end