boson 0.1.0 → 0.2.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/lib/boson/util.rb CHANGED
@@ -24,12 +24,6 @@ module Boson
24
24
  any_const_get(camelize(string))
25
25
  end
26
26
 
27
- def symbolize_keys(hash)
28
- hash.inject({}) {|options, (key, value)|
29
- options[key.to_sym] = value; options
30
- }
31
- end
32
-
33
27
  # Returns a constant like const_get() no matter what namespace it's nested in.
34
28
  # Returns nil if the constant is not found.
35
29
  def any_const_get(name)
@@ -80,16 +74,13 @@ module Boson
80
74
  all_modules
81
75
  end
82
76
 
83
- # Returns array of _all_ common instance methods between two modules/classes.
84
- def common_instance_methods(module1, module2)
85
- (module1.instance_methods + module1.private_instance_methods) & (module2.instance_methods + module2.private_instance_methods)
86
- end
87
-
88
- # Creates a module under a given base module and possible name. If the module already exists, it attempts
89
- # to create one with a number appended to the name.
77
+ # Creates a module under a given base module and possible name. If the module already exists or conflicts
78
+ # per top_level_class_conflict, it attempts to create one with a number appended to the name.
90
79
  def create_module(base_module, name)
91
80
  desired_class = camelize(name)
92
- if (suffix = ([""] + (1..10).to_a).find {|e| !base_module.const_defined?(desired_class+e)})
81
+ possible_suffixes = [''] + %w{1 2 3 4 5 6 7 8 9 10}
82
+ if (suffix = possible_suffixes.find {|e| !base_module.const_defined?(desired_class+e) &&
83
+ !top_level_class_conflict(base_module, "#{base_module}::#{desired_class}#{e}") })
93
84
  base_module.const_set(desired_class+suffix, Module.new)
94
85
  end
95
86
  end
@@ -108,5 +99,20 @@ module Boson
108
99
  def recursive_hash_merge(hash1, hash2)
109
100
  hash1.merge(hash2) {|k,o,n| (o.is_a?(Hash)) ? recursive_hash_merge(o,n) : n}
110
101
  end
102
+
103
+ # From Rubygems, determine a user's home.
104
+ def find_home
105
+ ['HOME', 'USERPROFILE'].each {|e| return ENV[e] if ENV[e] }
106
+ return "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
107
+ File.expand_path("~")
108
+ rescue
109
+ File::ALT_SEPARATOR ? "C:/" : "/"
110
+ end
111
+
112
+ # Returns name of top level class that conflicts if it exists. For example, for base module Boson::Commands,
113
+ # Boson::Commands::Alias conflicts with Alias if Alias exists.
114
+ def top_level_class_conflict(base_module, conflicting_module)
115
+ (conflicting_module =~ /^#{base_module}.*::([^:]+)/) && Object.const_defined?($1) && $1
116
+ end
111
117
  end
112
118
  end
data/lib/boson/view.rb CHANGED
@@ -11,33 +11,88 @@ module Boson
11
11
 
12
12
  # Renders any object via Hirb. Options are passed directly to
13
13
  # {Hirb::Console.render_output}[http://tagaholic.me/hirb/doc/classes/Hirb/Console.html#M000011].
14
- def render(object, options={})
14
+ def render(object, options={}, return_obj=false)
15
15
  if silent_object?(object)
16
16
  puts(object.inspect) unless options[:silence_booleans]
17
17
  else
18
- render_object(object, options)
18
+ render_object(object, options, return_obj)
19
19
  end
20
20
  end
21
21
 
22
+ # Searches and sorts an array of objects or hashes using options :query, :sort and :reverse_sort.
23
+ # The :query option is a hash of fields mapped to their search terms. Searches are OR-ed.
24
+ def search_and_sort(object, options)
25
+ if object.is_a?(Array)
26
+ object = search_object(object, options[:query]) if options[:query]
27
+ object = sort_object(object, options[:sort], options[:reverse_sort]) if object.size > 0 && options[:sort]
28
+ end
29
+ object
30
+ end
31
+
32
+ #:stopdoc:
33
+ def toggle_pager
34
+ Hirb::View.toggle_pager
35
+ end
36
+
22
37
  def silent_object?(obj)
23
38
  [nil,false,true].include?(obj)
24
39
  end
25
40
 
26
- def render_object(object, options={}) #:nodoc:
41
+ def render_object(object, options={}, return_obj=false)
27
42
  options[:class] ||= :auto_table
28
- if object.is_a?(Array) && object.size > 0 && (sort = options.delete(:sort))
29
- begin
30
- sort_lambda = object[0].is_a?(Hash) ? (object[0][sort].respond_to?(:<=>) ?
31
- lambda {|e| e[sort] } : lambda {|e| e[sort].to_s }) :
32
- (object[0].send(sort).respond_to?(:<=>) ? lambda {|e| e.send(sort)} :
33
- lambda {|e| e.send(sort).to_s })
34
- object = object.sort_by &sort_lambda
35
- object = object.reverse if options[:reverse_sort]
36
- rescue NoMethodError, ArgumentError
37
- $stderr.puts "Sort failed with nonexistant method '#{sort}'"
43
+ render_result = Hirb::Console.render_output(object, options)
44
+ return_obj ? object : render_result
45
+ end
46
+
47
+ def search_object(object, query_hash)
48
+ if object[0].is_a?(Hash)
49
+ TableCallbacks.search_callback(object, :query=>query_hash)
50
+ else
51
+ query_hash.map {|field,query| object.select {|e| e.send(field).to_s =~ /#{query}/i } }.flatten.uniq
52
+ end
53
+ rescue NoMethodError
54
+ $stderr.puts "Query failed with nonexistant method '#{$!.message[/`(.*)'/,1]}'"
55
+ end
56
+
57
+ def sort_object(object, sort, reverse_sort=false)
58
+ if object[0].is_a?(Hash)
59
+ TableCallbacks.sort_callback(object, :sort=>sort, :reverse_sort=>reverse_sort)
60
+ else
61
+ sort_lambda = object.all? {|e| e.send(sort).respond_to?(:<=>) } ? lambda {|e| e.send(sort) || ''} :
62
+ lambda {|e| e.send(sort).to_s }
63
+ object = object.sort_by &sort_lambda
64
+ object = object.reverse if reverse_sort
65
+ object
66
+ end
67
+ rescue NoMethodError, ArgumentError
68
+ $stderr.puts "Sort failed with nonexistant method '#{sort}'"
69
+ end
70
+ #:startdoc:
71
+
72
+ # Callbacks used by Hirb::Helpers::Table to search and sort arrays of hashes.
73
+ module TableCallbacks
74
+ extend self
75
+ # Case-insensitive searches an array of hashes using the option :query. Numerical string keys
76
+ # in :query are converted to actual numbers to interface with Hirb. See View.search_and_sort for more
77
+ # about :query.
78
+ def search_callback(obj, options)
79
+ !options[:query] ? obj : begin
80
+ options[:query].map {|field,query|
81
+ field = field.to_i if field.to_s[/^\d+$/]
82
+ obj.select {|e| e[field].to_s =~ /#{query}/i }
83
+ }.flatten.uniq
38
84
  end
39
85
  end
40
- Hirb::Console.render_output(object, options)
86
+
87
+ # Sorts an array of hashes using :sort option and reverses the sort with :reverse_sort option.
88
+ def sort_callback(obj, options)
89
+ sort = options[:sort].to_s[/^\d+$/] ? options[:sort].to_i : options[:sort]
90
+ sort_lambda = (obj.all? {|e| e[sort].respond_to?(:<=>) } ? lambda {|e| e[sort] } : lambda {|e| e[sort].to_s })
91
+ obj = obj.sort_by &sort_lambda
92
+ obj = obj.reverse if options[:reverse_sort]
93
+ obj
94
+ end
41
95
  end
42
96
  end
43
- end
97
+ end
98
+ Hirb::Helpers::Table.send :include, Boson::View::TableCallbacks
@@ -55,8 +55,8 @@ module Boson
55
55
  end
56
56
 
57
57
  test "global option takes value with whitespace" do
58
- View.expects(:render).with(anything, {:sort => :lib, :fields => [:name, :lib]})
59
- start('commands', '-g', 'f=name,lib s=lib')
58
+ View.expects(:render).with(anything, {:vertical=>true, :fields => [:name, :lib]}, anything)
59
+ start('commands', '-g', 'f=name,lib vertical')
60
60
  end
61
61
 
62
62
  test "execute option errors are caught" do
@@ -69,7 +69,11 @@ module Boson
69
69
 
70
70
  test "failed subcommand prints error and not command not found" do
71
71
  BinRunner.expects(:execute_command).raises("bling")
72
- capture_stderr { start("commands.blah") }.should =~ /Error: bling/
72
+ capture_stderr { start("commands.to_s") }.should =~ /Error: bling/
73
+ end
74
+
75
+ test "nonexistant subcommand prints command not found" do
76
+ capture_stderr { start("to_s.bling") }.should =~ /'to_s.bling' not found/
73
77
  end
74
78
 
75
79
  test "undiscovered command prints error" do
@@ -102,19 +106,31 @@ module Boson
102
106
  end
103
107
 
104
108
  context "load_command_by_index" do
109
+ def index(options={})
110
+ Manager.expects(:load).with {|*args| args[0][0].is_a?(Module) ? true : args[0] == options[:load]
111
+ }.at_least(1).returns(!options[:fails])
112
+ Index.expects(:write)
113
+ end
114
+
105
115
  test "with index option, no existing index and core command updates index and prints index message" do
106
- Manager.expects(:load).with {|*args| args[0][0].is_a?(Module) ? true : args[0] == Runner.all_libraries }.at_least(1)
107
- Index.expects(:exists?).returns(false)
108
- Index.expects(:write)
116
+ index :load=>Runner.all_libraries
117
+ Index.stubs(:exists?).returns(false)
109
118
  capture_stdout { start("--index", "libraries") }.should =~ /Generating index/
110
119
  end
111
120
 
112
121
  test "with index option, existing index and core command updates incremental index" do
113
- Index.expects(:changed_libraries).returns(['changed'])
114
- Manager.expects(:load).with {|*args| args[0][0].is_a?(Module) ? true : args[0] == ['changed'] }.at_least(1)
115
- Index.expects(:exists?).returns(true)
116
- Index.expects(:write)
117
- capture_stdout { start("--index", "libraries")}.should =~ /Indexing.*changed/
122
+ index :load=>['changed']
123
+ Index.stubs(:exists?).returns(true)
124
+ capture_stdout { start("--index=changed", "libraries")}.should =~ /Indexing.*changed/
125
+ end
126
+
127
+ test "with index option, failed indexing prints error" do
128
+ index :load=>['changed'], :fails=>true
129
+ Index.stubs(:exists?).returns(true)
130
+ Manager.stubs(:failed_libraries).returns(['changed'])
131
+ capture_stderr {
132
+ capture_stdout { start("--index=changed", "libraries")}.should =~ /Indexing.*changed/
133
+ }.should =~ /Error:.*failed.*changed/
118
134
  end
119
135
 
120
136
  test "with core command updates index and doesn't print index message" do
@@ -13,7 +13,20 @@ module Boson
13
13
 
14
14
  test "in a subdirectory loads" do
15
15
  load 'site/delicious', :file_string=>"module Delicious; def blah; end; end"
16
- library_has_module('site/delicious', "Boson::Commands::Delicious")
16
+ library_has_module('site/delicious', "Boson::Commands::Site::Delicious")
17
+ command_exists?('blah')
18
+ end
19
+
20
+ test "in a sub subdirectory loads" do
21
+ load 'web/site/delicious', :file_string=>"module Delicious; def blah; end; end"
22
+ library_has_module('web/site/delicious', "Boson::Commands::Web::Site::Delicious")
23
+ command_exists?('blah')
24
+ end
25
+
26
+ test "loads by basename" do
27
+ Dir.stubs(:[]).returns(['./test/commands/site/github.rb'])
28
+ load 'github', :file_string=>"module Github; def blah; end; end", :exists=>false
29
+ library_has_module('site/github', "Boson::Commands::Site::Github")
17
30
  command_exists?('blah')
18
31
  end
19
32
 
data/test/index_test.rb CHANGED
@@ -9,7 +9,8 @@ module Boson
9
9
  before(:each) { reset_boson; Index.instance_eval "@libraries = @commands = nil" }
10
10
 
11
11
  def transfers(options={})
12
- Index.instance_variable_set "@libraries", [Library.new(:name=>'blah'), Library.new(:name=>'bling')]
12
+ Index.instance_variable_set "@libraries", [Library.new(:name=>'blah', :commands=>['blurb']),
13
+ Library.new(:name=>'bling')]
13
14
  Index.instance_variable_set "@commands", [Command.new(:name=>'blurb', :lib=>'blah')]
14
15
  Index.read_and_transfer options[:ignored] || []
15
16
  Boson.libraries.map {|e| e.name}.should == options[:libraries]
data/test/loader_test.rb CHANGED
@@ -7,20 +7,62 @@ module Boson
7
7
  Manager.load([Boson::Commands::Namespace])
8
8
  end
9
9
 
10
+ context "config" do
11
+ before(:each) { reset }
12
+ test "from callback overridden by user's config" do
13
+ with_config(:libraries=>{'blih'=>{:namespace=>false}}) do
14
+ load :blih, :file_string=>"module Blah; def self.config; {:namespace=>'bling'}; end; end"
15
+ library('blih').namespace.should == false
16
+ end
17
+ end
18
+
19
+ # if this test fails, other exists? using methods fail
20
+ test "from callback recursively merges with user's config" do
21
+ with_config(:libraries=>{'blah'=>{:commands=>{'bling'=>{:description=>'bling', :options=>{:num=>3}}}}}) do
22
+ File.stubs(:exists?).returns(true)
23
+ load :blah, :file_string=> "module Blah; def self.config; {:commands=>{'blang'=>{:alias=>'ba'}, " +
24
+ "'bling'=>{:options=>{:verbose=>:boolean}}}}; end; end"
25
+ library('blah').command_object('bling').options.should == {:verbose=>:boolean, :num=>3}
26
+ library('blah').command_object('bling').description.should == 'bling'
27
+ library('blah').command_object('blang').alias.should == 'ba'
28
+ end
29
+ end
30
+
31
+ test "non-hash from inspector overridden by user's config" do
32
+ with_config(:libraries=>{'blah'=>{:commands=>{'bling'=>{:description=>'already'}}}}) do
33
+ load :blah, :file_string=>"module Blah; #from file\ndef bling; end; end"
34
+ library('blah').command_object('bling').description.should == 'already'
35
+ end
36
+ end
37
+
38
+ test "hash from inspector recursively merged with user's config" do
39
+ with_config(:libraries=>{'blah'=>{:commands=>{'blung'=>{:args=>[], :render_options=>{:sort=>'this'}}}}}) do
40
+ CommentInspector.expects(:scrape).returns({:render_options=>{:fields=>['this']}})
41
+ load :blah, :file_string=>"module Blah; def blung; end; end"
42
+ library('blah').command_object('blung').render_options.should == {:fields=>["this"], :sort=>"this"}
43
+ end
44
+ end
45
+ end
46
+
10
47
  context "load" do
11
48
  before(:each) { reset }
12
- test "calls included hook" do
49
+ test "calls included callback" do
13
50
  capture_stdout {
14
51
  load :blah, :file_string=>"module Blah; def self.included(mod); puts 'included blah'; end; def blah; end; end"
15
52
  }.should =~ /included blah/
16
53
  end
17
54
 
18
- test "calls methods in config init_methods" do
19
- with_config(:libraries=>{"blah"=>{:init_methods=>['blah']}}) do
20
- capture_stdout {
21
- load :blah, :file_string=>"module Blah; def blah; puts 'yo'; end; end"
22
- }.should == "yo\n"
23
- end
55
+ test "calls after_included callback" do
56
+ capture_stdout {
57
+ load :blah, :file_string=>"module Blah; def self.after_included; puts 'yo'; end; end"
58
+ }.should == "yo\n"
59
+ end
60
+
61
+ test "prints error if library module conflicts with top level constant/module" do
62
+ capture_stderr {
63
+ load :blah, :file_string=>"module Object; def self.blah; end; end"
64
+ }.should =~ /conflict.*'Object'/
65
+ library_loaded?('blah')
24
66
  end
25
67
 
26
68
  test "prints error and returns false for existing library" do
@@ -37,6 +79,14 @@ module Boson
37
79
  end
38
80
  end
39
81
 
82
+ test "loads a library and creates its class commands" do
83
+ with_config(:libraries=>{"blah"=>{:class_commands=>{"bling"=>"Blah.bling", "Blah"=>['hmm']}}}) do
84
+ load :blah, :file_string=>"module Blah; def self.bling; end; def self.hmm; end; end"
85
+ command_exists? 'bling'
86
+ command_exists? 'hmm'
87
+ end
88
+ end
89
+
40
90
  test "loads a library with dependencies" do
41
91
  File.stubs(:exists?).returns(true)
42
92
  File.stubs(:read).returns("module Water; def water; end; end", "module Oaks; def oaks; end; end")
@@ -60,6 +110,14 @@ module Boson
60
110
  end
61
111
  end
62
112
 
113
+ test "prints error for method conflicts with main_object method" do
114
+ with_config(:error_method_conflicts=>true) do
115
+ capture_stderr {
116
+ load('blah', :file_string=>"module Blah; def require; end; end")
117
+ }.should =~ /Unable to load library blah.*conflict.*require/
118
+ end
119
+ end
120
+
63
121
  test "prints error for method conflicts with config error_method_conflicts" do
64
122
  with_config(:error_method_conflicts=>true) do
65
123
  load('blah', :file_string=>"module Blah; def chwhat; end; end")
@@ -81,11 +139,25 @@ module Boson
81
139
  context "module library" do
82
140
  def mock_library(*args); end
83
141
 
84
- test "loads a module library" do
85
- eval %[module ::Harvey; def bird; end; end]
142
+ test "loads a module library and all its class methods by default" do
143
+ eval %[module ::Harvey; def self.bird; end; def self.eagle; end; end]
86
144
  load ::Harvey, :no_mock=>true
87
- library_has_module('harvey', "Harvey")
88
- command_exists?('bird')
145
+ library_has_command('harvey', 'bird')
146
+ library_has_command('harvey', 'eagle')
147
+ end
148
+
149
+ test "loads a module library with specified commands" do
150
+ eval %[module ::Peanut; def self.bird; end; def self.eagle; end; end]
151
+ load ::Peanut, :no_mock=>true, :commands=>%w{bird}
152
+ library('peanut').commands.size.should == 1
153
+ library_has_command('peanut', 'bird')
154
+ end
155
+
156
+ test "loads a module library as a class" do
157
+ eval %[class ::Mentok; def self.bird; end; def self.eagle; end; end]
158
+ load ::Mentok, :no_mock=>true, :commands=>%w{bird}
159
+ library('mentok').commands.size.should == 1
160
+ library_has_command('mentok', 'bird')
89
161
  end
90
162
  end
91
163
 
@@ -145,17 +217,8 @@ module Boson
145
217
  end
146
218
  end
147
219
 
148
- test "loads with config except" do
149
- with_config(:libraries=>{'blong'=>{:namespace=>true, :except=>['wrong']}}) do
150
- load 'blong', :file_string=>"module Blong; def bling; end; def wrong; end; end"
151
- library_has_command('blong', 'bling')
152
- library_has_command('blong', 'wrong', false)
153
- library('blong').commands.size.should == 1
154
- end
155
- end
156
-
157
220
  test "prints error if namespace conflicts with existing commands" do
158
- eval "module Conflict; def bleng; end; end"
221
+ eval "module Conflict; def self.bleng; end; end"
159
222
  load Conflict, :no_mock=>true
160
223
  with_config(:libraries=>{'bleng'=>{:namespace=>true}}) do
161
224
  capture_stderr {
data/test/manager_test.rb CHANGED
@@ -5,7 +5,7 @@ module Boson
5
5
  context "after_load" do
6
6
  def load_library(hash)
7
7
  new_attributes = {:name=>hash.delete(:name), :commands=>[], :created_dependencies=>[], :loaded=>true}
8
- [:module, :except, :commands].each {|e| new_attributes[e] = hash.delete(e) if hash[e] }
8
+ [:module, :commands].each {|e| new_attributes[e] = hash.delete(e) if hash[e] }
9
9
  Manager.expects(:load_once).returns(Library.new(new_attributes))
10
10
  Manager.load([hash[:name]])
11
11
  end
@@ -24,14 +24,6 @@ module Boson
24
24
  command_exists?('meatwad')
25
25
  end
26
26
 
27
- test "loads library with commands and except option" do
28
- Boson.main_object.instance_eval("class<<self;self;end").expects(:undef_method).with('frylock')
29
- load_library :name=>'blah', :commands=>['frylock','meatwad'], :except=>['frylock']
30
- library_loaded? 'blah'
31
- command_exists?('frylock', false)
32
- command_exists?('meatwad')
33
- end
34
-
35
27
  context "command aliases" do
36
28
  before(:each) { eval %[module ::Aquateen; def frylock; end; end] }
37
29
  after(:each) { Object.send(:remove_const, "Aquateen") }
@@ -1,7 +1,7 @@
1
1
  require File.join(File.dirname(__FILE__), 'test_helper')
2
2
 
3
3
  module Boson
4
- class InspectorTest < Test::Unit::TestCase
4
+ class MethodInspectorTest < Test::Unit::TestCase
5
5
  test "non commands module can't set anything" do
6
6
  eval "module Blah; end"
7
7
  MethodInspector.current_module = Blah
@@ -21,7 +21,7 @@ module Boson
21
21
  end
22
22
 
23
23
  before(:all) { eval "module ::Boson::Commands::Zzz; end" }
24
- before(:each) { MethodInspector.mod_store[::Boson::Commands::Zzz] = {} }
24
+ before(:each) { MethodInspector.mod_store.delete(::Boson::Commands::Zzz) }
25
25
 
26
26
  test "desc sets descriptions" do
27
27
  parsed = parse "desc 'test'; def m1; end; desc 'one'; desc 'more'; def m2; end"
@@ -90,7 +90,7 @@ module Boson
90
90
  end
91
91
 
92
92
  it "allows humanized opt name" do
93
- create 'foo' => :string, :bar => :required
93
+ create 'foo' => :string, :bar => :string
94
94
  parse("-f", "1", "-b", "2").should == {:foo => "1", :bar => "2"}
95
95
  end
96
96
 
@@ -113,6 +113,7 @@ module Boson
113
113
  it "accepts --[no-]opt variant for booleans, setting false for value" do
114
114
  create "--foo" => :boolean
115
115
  parse("--no-foo")["foo"].should == false
116
+ parse("--no-f")["foo"].should == false
116
117
  parse("--foo")["foo"].should == true
117
118
  end
118
119
 
@@ -125,7 +126,7 @@ module Boson
125
126
 
126
127
  context "option values can be set with" do
127
128
  it "a opt=<value> assignment" do
128
- create "--foo" => :required
129
+ create :foo => :string
129
130
  parse("--foo=12")["foo"].should == "12"
130
131
  parse("-f=12")["foo"].should == "12"
131
132
  parse("--foo=bar=baz")["foo"].should == "bar=baz"
@@ -133,8 +134,8 @@ module Boson
133
134
  end
134
135
 
135
136
  it "a -nXY assignment" do
136
- create "--num" => :required
137
- parse("-n12")["num"].should == "12"
137
+ create "--num" => :numeric
138
+ parse("-n12")["num"].should == 12
138
139
  end
139
140
 
140
141
  it "conjoined short options" do
@@ -146,17 +147,17 @@ module Boson
146
147
  end
147
148
 
148
149
  it "conjoined short options with argument" do
149
- create "--foo" => true, "--bar" => true, "--app" => :required
150
+ create "--foo" => true, "--bar" => true, "--app" => :numeric
150
151
  opts = parse "-fba", "12"
151
152
  opts["foo"].should == true
152
153
  opts["bar"].should == true
153
- opts["app"].should == "12"
154
+ opts["app"].should == 12
154
155
  end
155
156
  end
156
157
 
157
158
  context "parse" do
158
159
  it "extracts non-option arguments" do
159
- create "--foo" => :required, "--bar" => true
160
+ create "--foo" => :string, "--bar" => true
160
161
  parse("foo", "bar", "--baz", "--foo", "12", "--bar", "-T", "bang").should == {
161
162
  :foo => "12", :bar => true
162
163
  }
@@ -208,14 +209,55 @@ module Boson
208
209
  end
209
210
  end
210
211
 
211
- it ":required type raises an error if it isn't given" do
212
- create "--foo" => :required, "--bar" => :string
213
- assert_error(OptionParser::Error, 'no value.*required.*foo') { parse("--bar", "str") }
212
+ context ":required option attribute" do
213
+ before(:all) {
214
+ create "--foo" => {:type=>:string, :required=>true}, :bar => {:type=>:hash, :required=>true}
215
+ }
216
+
217
+ it "raises an error if string option isn't given" do
218
+ assert_error(OptionParser::Error, 'no value.*required.*foo') { parse("--bar", "str:ok") }
219
+ end
220
+
221
+ it "raises an error if non-string option isn't given" do
222
+ assert_error(OptionParser::Error, 'no value.*required.*bar') { parse("--foo", "yup") }
223
+ end
224
+
225
+ it "raises no error when given arguments" do
226
+ parse("--foo", "yup", "--bar","ok:dude").should == {:foo=>'yup', :bar=>{'ok'=>'dude'}}
227
+ end
214
228
  end
215
-
229
+
230
+ context ":bool_default option attribute" do
231
+ before(:all) {
232
+ create :foo=>{:type=>:string, :bool_default=>'whoop'}, :bar=>{:type=>:array, :bool_default=>'1'},
233
+ :verbose=>:boolean, :yep=>{:type=>:string, :bool_default=>true}
234
+ }
235
+
236
+ it "sets default boolean" do
237
+ parse('--foo', '--bar', '1')[:foo].should == 'whoop'
238
+ parse('--foo', 'ok', 'dokay')[:foo].should == 'whoop'
239
+ end
240
+
241
+ it "sets options normally" do
242
+ parse('--foo=boo', '--bar=har').should == {:foo=>'boo', :bar=>['har']}
243
+ end
244
+
245
+ it "sets default boolean for array" do
246
+ parse("--bar", '--foo', '2')[:bar].should == ['1']
247
+ end
248
+
249
+ it "sets default boolean for non-string value" do
250
+ parse('--yep', '--foo=2')[:yep].should == true
251
+ end
252
+
253
+ it "default booleans can be joined with boolean options" do
254
+ parse('-fbv').should == {:verbose=>true, :bar=>['1'], :foo=>'whoop'}
255
+ end
256
+ end
257
+
216
258
  context ":string type" do
217
259
  before :each do
218
- create "--foo" => :string, "--bar" => :string
260
+ create "--foo" => :string, "--bar" => :string, :blah=>{:type=>:string, :default=>:huh}
219
261
  end
220
262
 
221
263
  it "doesn't set nonexistant options" do
@@ -238,6 +280,10 @@ module Boson
238
280
  it "overwrites earlier values with later values" do
239
281
  parse("--foo", "12", "--foo", "13")[:foo].should == "13"
240
282
  end
283
+
284
+ it "can have symbolic default value" do
285
+ parse('--blah','ok')[:blah].should == 'ok'
286
+ end
241
287
  end
242
288
 
243
289
  context ":string type with :values attribute" do
@@ -298,8 +344,8 @@ module Boson
298
344
 
299
345
  context ":array type" do
300
346
  before(:all) {
301
- create :a=>:array, :b=>[1,2,3], :c=>{:type=>:array, :values=>%w{foo fa bar zebra}},
302
- :d=>{:type=>:array, :split=>" "}
347
+ create :a=>:array, :b=>[1,2,3], :c=>{:type=>:array, :values=>%w{foo fa bar zebra}, :enum=>false},
348
+ :d=>{:type=>:array, :split=>" ", :values=>[:ab, :bc, :cd], :enum=>false}
303
349
  }
304
350
 
305
351
  it "supports array defaults" do
@@ -318,9 +364,71 @@ module Boson
318
364
  parse("-c","f,b")[:c].should == %w{fa bar}
319
365
  end
320
366
 
321
- it "allows a configurable splitter" do
367
+ it "auto aliases symbolic :values" do
368
+ parse("-d","a c")[:d].should == [:ab,:cd]
369
+ end
370
+
371
+ it "supports a configurable splitter" do
322
372
  parse("-d", "yogi berra")[:d].should == %w{yogi berra}
323
373
  end
374
+
375
+ it "aliases * to all values" do
376
+ parse("-c", '*')[:c].sort.should == %w{bar fa foo zebra}
377
+ parse("-c", '*,ok')[:c].sort.should == %w{bar fa foo ok zebra}
378
+ end
379
+ end
380
+
381
+ context ":hash type" do
382
+ before(:all) {
383
+ create :a=>:hash, :b=>{:default=>{:a=>'b'}}, :c=>{:type=>:hash, :keys=>%w{one two three}},
384
+ :e=>{:type=>:hash, :keys=>[:one, :two, :three], :default_keys=>:three},
385
+ :d=>{:type=>:hash, :split=>" "}
386
+ }
387
+
388
+ it "converts comma delimited pairs to hash" do
389
+ parse("-a", "f:3,g:4")[:a].should == {'f'=>'3', 'g'=>'4'}
390
+ end
391
+
392
+ it "supports hash defaults" do
393
+ parse[:b].should == {:a=>'b'}
394
+ end
395
+
396
+ it "raises error when option has no value" do
397
+ assert_error(OptionParser::Error, "no value.*'a'") { parse("-a") }
398
+ end
399
+
400
+ it "raises error if invalid key-value pair given for unknown keys" do
401
+ assert_error(OptionParser::Error, "invalid.*pair.*'a'") { parse("-a", 'b') }
402
+ end
403
+
404
+ it "auto aliases :keys attribute" do
405
+ parse("-c","t:3,o:1")[:c].should == {'three'=>'3', 'one'=>'1'}
406
+ end
407
+
408
+ it "adds in explicit default keys with value only argument" do
409
+ parse('-e', 'whoop')[:e].should == {:three=>'whoop'}
410
+ end
411
+
412
+ it "adds in default keys from known :keys with value only argument" do
413
+ parse("-c","okay")[:c].should == {'one'=>'okay'}
414
+ end
415
+
416
+ it "auto aliases symbolic :keys" do
417
+ parse("-e","t:3,o:1")[:e].should == {:three=>'3', :one=>'1'}
418
+ end
419
+
420
+ it "supports a configurable splitter" do
421
+ parse("-d","a:ab b:bc")[:d].should == {'a'=>'ab', 'b'=>'bc'}
422
+ end
423
+
424
+ it "supports grouping keys" do
425
+ parse("-c", "t,tw:foo,o:bar")[:c].should == {'three'=>'foo','two'=>'foo', 'one'=>'bar'}
426
+ end
427
+
428
+ it "aliases * to all keys" do
429
+ parse("-c", "*:foo")[:c].should == {'three'=>'foo', 'two'=>'foo', 'one'=>'foo'}
430
+ parse('-a', '*:foo')[:a].should == {'*'=>'foo'}
431
+ end
324
432
  end
325
433
 
326
434
  context "option with attributes" do
@@ -341,11 +449,11 @@ module Boson
341
449
  end
342
450
  end
343
451
 
452
+ def usage
453
+ @opt.formatted_usage.split(" ").sort
454
+ end
455
+
344
456
  context "#formatted_usage" do
345
- def usage
346
- @opt.formatted_usage.split(" ").sort
347
- end
348
-
349
457
  it "outputs string args with sample values" do
350
458
  create "--repo" => :string, "--branch" => "bugfix", "-n" => 6
351
459
  usage.should == %w([--branch=bugfix] [--repo=REPO] [-n=6])
@@ -360,6 +468,54 @@ module Boson
360
468
  create "--libs" => :array
361
469
  usage.should == ["[--libs=A,B,C]"]
362
470
  end
471
+
472
+ it "outputs hash args with sample value" do
473
+ create '--paths' => :hash
474
+ usage.should == ["[--paths=A:B,C:D]"]
475
+ end
476
+ end
477
+
478
+ context "user defined option class" do
479
+ before(:all) {
480
+ ::FooBoo = Struct.new(:name)
481
+ module ::Boson::Options::FooBoo
482
+ def create_foo_boo(value)
483
+ ::FooBoo.new(value)
484
+ end
485
+ def validate_foo_boo(value); end
486
+ end
487
+ ::Boson::OptionParser.send :include, ::Boson::Options::FooBoo
488
+ create :a=>:foo_boo, :b=>::FooBoo.new('blah'), :c=>:blah_blah,
489
+ :d=>{:type=>:foo_boo, :type=>::FooBoo.new('bling')}
490
+ }
491
+
492
+ test "created from symbol" do
493
+ (obj = parse('-a', 'whoop')[:a]).class.should == ::FooBoo
494
+ obj.name.should == 'whoop'
495
+ end
496
+
497
+ test "created from default" do
498
+ (obj = parse[:b]).class.should == ::FooBoo
499
+ obj.name.should == 'blah'
500
+ end
501
+
502
+ test "created from type attribute" do
503
+ (obj = parse('-d', 'whoop')[:d]).class.should == ::FooBoo
504
+ obj.name.should == 'whoop'
505
+ end
506
+
507
+ test "has its validation called" do
508
+ @opt.expects(:validate_foo_boo)
509
+ parse("-a", 'blah')
510
+ end
511
+
512
+ test "has default usage" do
513
+ usage[0].should == "[-a=:foo_boo]"
514
+ end
515
+
516
+ test "when nonexistant raises error" do
517
+ assert_error(OptionParser::Error, "invalid.*:blah_blah") { parse("-c", 'ok') }
518
+ end
363
519
  end
364
520
  end
365
- end
521
+ end