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.
@@ -0,0 +1,127 @@
1
+ module Boson
2
+ # This module contains the methods used to define the default option types.
3
+ #
4
+ # === Creating Your Own Option Type
5
+ # Defining your own option type simply requires one method (create_@type) to parse the option value and create
6
+ # the desired object. To create an option type :date, you could create the following create_date method:
7
+ # # Drop this in your ~/.irbrc after require 'boson'
8
+ # module Boson::Options::Date
9
+ # def create_date(value)
10
+ # # value_shift should be mm/dd
11
+ # Date.parse(value + "/#{Date.today.year}")
12
+ # end
13
+ # end
14
+ # Boson::OptionParser.send :include, Boson::Options::Date
15
+ #
16
+ # In a FileLibrary, we could then use this new option:
17
+ # module Calendar
18
+ # #@options :day=>:date
19
+ # def appointments(options={})
20
+ # # ...
21
+ # end
22
+ # end
23
+ # # >> appointments '-d 10/10' -> {:day=>#<Date: 4910229/2,0,2299161> }
24
+ # As you can see, a date object is created from the :date option's value and passed into appointments().
25
+ #
26
+ # Some additional tips on the create_* method:
27
+ # * The argument passed to the method is the option value from the user.
28
+ # * To access the current option name use @current_option.
29
+ # * To access the hash of attributes the current option has use OptionParser.current_attributes. See
30
+ # OptionParser.new for more about option attributes.
31
+ #
32
+ # There are two optional methods per option type: validate_@type and usage_for_@type i.e. validate_date and usage_for_date.
33
+ # Like create_@type, validate_@type takes the option's value. If the value validation fails, raise an
34
+ # OptionParser::Error with a proper message. All user-defined option types automatically validate for an option value's existence.
35
+ # The usage_for_* method takes an option's name (i.e. --day) and returns a usage string to be wrapped in '[ ]'. If no usage is defined
36
+ # the default would look like '[--day=:date]'. Consider using the OptionParser.default_usage helper method for your usage.
37
+ module Options
38
+ #:stopdoc:
39
+ # Parse/create methods
40
+ def create_string(value)
41
+ if (values = current_attributes[:values]) && (values = values.sort_by {|e| e.to_s})
42
+ (val = auto_alias_value(values, value)) && value = val
43
+ end
44
+ value
45
+ end
46
+
47
+ def create_boolean(value)
48
+ if (!@opt_types.key?(dasherize(@current_option)) && @current_option =~ /^no-(\w+)$/)
49
+ opt = (opt = original_no_opt($1)) ? undasherize(opt) : $1
50
+ (@current_option.replace(opt) && false)
51
+ else
52
+ true
53
+ end
54
+ end
55
+
56
+ def create_numeric(value)
57
+ value.index('.') ? value.to_f : value.to_i
58
+ end
59
+
60
+ def create_array(value)
61
+ splitter = current_attributes[:split] || ','
62
+ array = value.split(splitter)
63
+ if (values = current_attributes[:values]) && (values = values.sort_by {|e| e.to_s })
64
+ array.each {|e| array.delete(e) && array += values if e == '*'}
65
+ array.each_with_index {|e,i|
66
+ (value = auto_alias_value(values, e)) && array[i] = value
67
+ }
68
+ end
69
+ array
70
+ end
71
+
72
+ def create_hash(value)
73
+ splitter = current_attributes[:split] || ','
74
+ (keys = current_attributes[:keys]) && keys = keys.sort_by {|e| e.to_s }
75
+ if !value.include?(':') && current_attributes[:default_keys]
76
+ value = current_attributes[:default_keys].to_s + ":#{value}"
77
+ end
78
+ # Creates array pairs, grouping array of keys with a value
79
+ aoa = Hash[*value.split(/(?::)([^#{Regexp.quote(splitter)}]+)#{Regexp.quote(splitter)}?/)].to_a
80
+ aoa.each_with_index {|(k,v),i| aoa[i][0] = keys.join(splitter) if k == '*' } if keys
81
+ hash = aoa.inject({}) {|t,(k,v)| k.split(splitter).each {|e| t[e] = v }; t }
82
+ keys ? hash.each {|k,v|
83
+ (new_key = auto_alias_value(keys, k)) && hash[new_key] = hash.delete(k)
84
+ } : hash
85
+ end
86
+
87
+ # Validation methods
88
+ def validate_string(value)
89
+ raise OptionParser::Error, "cannot pass '#{value}' as an argument to option '#{@current_option}'" if valid?(value)
90
+ end
91
+
92
+ def validate_numeric(value)
93
+ unless value =~ OptionParser::NUMERIC and $& == value
94
+ raise OptionParser::Error, "expected numeric value for option '#{@current_option}'; got #{value.inspect}"
95
+ end
96
+ end
97
+
98
+ def validate_hash(value)
99
+ if !value.include?(':') && !current_attributes[:default_keys]
100
+ raise(OptionParser::Error, "invalid key:value pair for option '#{@current_option}'")
101
+ end
102
+ end
103
+
104
+ # Usage methods
105
+ def usage_for_boolean(opt)
106
+ opt
107
+ end
108
+
109
+ def usage_for_string(opt)
110
+ default_usage(opt, undasherize(opt).upcase)
111
+ end
112
+
113
+ def usage_for_numeric(opt)
114
+ default_usage opt, "N"
115
+ end
116
+
117
+ def usage_for_array(opt)
118
+ default_usage opt, "A,B,C"
119
+ end
120
+
121
+ def usage_for_hash(opt)
122
+ default_usage opt, "A:B,C:D"
123
+ end
124
+ #:startdoc:
125
+ end
126
+ end
127
+ Boson::OptionParser.send :include, Boson::Options
data/lib/boson/repo.rb CHANGED
@@ -15,7 +15,16 @@ module Boson
15
15
 
16
16
  # Points to the config/ subdirectory and is automatically created when called. Used for config files.
17
17
  def config_dir
18
- @config_dir ||= FileUtils.mkdir_p("#{dir}/config") && "#{dir}/config"
18
+ @config_dir ||= FileUtils.mkdir_p(config_dir_path) && config_dir_path
19
+ end
20
+
21
+ def config_dir_path
22
+ "#{dir}/config"
23
+ end
24
+
25
+ # Path name of main config file. If passed true, parent directory of file is created.
26
+ def config_file(create_dir=false)
27
+ File.join((create_dir ? config_dir : config_dir_path), 'boson.yml')
19
28
  end
20
29
 
21
30
  # Points to the commands/ subdirectory and is automatically created when called. Used for command libraries.
@@ -25,6 +34,7 @@ module Boson
25
34
 
26
35
  # A hash read from the YAML config file at config/boson.yml.
27
36
  # {See here}[http://github.com/cldwalker/irbfiles/blob/master/boson/config/boson.yml] for an example config file.
37
+ # Top level config keys, library attributes and config attributes need to be symbols.
28
38
  # ==== Valid config keys:
29
39
  # [:libraries] Hash of libraries mapping their name to attribute hashes. See Library.new for configurable attributes.
30
40
  # Example:
@@ -35,8 +45,10 @@ module Boson
35
45
  # key of a library entry in :libraries.
36
46
  # Example:
37
47
  # :command_aliases=>{'libraries'=>'lib', 'commands'=>'com'}
38
- # [:console_defaults] Array of libraries to load at start up when used in irb. Default is to load all library files and libraries defined
39
- # in the config.
48
+ # [:defaults] Array of libraries to load at start up for commandline and irb. This is useful for extending boson i.e. adding your
49
+ # own option types. Default is no libraries.
50
+ # [:console_defaults] Array of libraries to load at start up when used in irb. Default is to load all library files and libraries
51
+ # defined in the config.
40
52
  # [:bin_defaults] Array of libraries to load at start up when used from the commandline. Default is no libraries.
41
53
  # [:add_load_path] Boolean specifying whether to add a load path pointing to the lib subdirectory/. This is useful in sharing
42
54
  # classes between libraries without resorting to packaging them as gems. Defaults to false if the lib
@@ -45,11 +57,18 @@ module Boson
45
57
  # the global namespace. When set to false, Boson automatically puts the library in its own namespace.
46
58
  # When set to true, the library fails to load explicitly. Default is false.
47
59
  # [:console] Console to load when using --console from commandline. Default is irb.
48
- # [:auto_namespace] Boolean which automatically namespaces all user-defined libraries. Default is false.
60
+ # [:auto_namespace] Boolean which automatically namespaces all user-defined libraries. Be aware this can break libraries which
61
+ # depend on commands from other libraries. Default is false.
49
62
  def config(reload=false)
50
63
  if reload || @config.nil?
51
- default = {:libraries=>{}, :command_aliases=>{}, :console_defaults=>[]}
52
- @config = default.merge(YAML::load_file(config_dir + '/boson.yml')) rescue default
64
+ begin
65
+ @config = {:libraries=>{}, :command_aliases=>{}, :console_defaults=>[]}
66
+ @config.merge!(YAML::load_file(config_file(true))) if File.exists?(config_file)
67
+ rescue ArgumentError
68
+ message = $!.message !~ /syntax error on line (\d+)/ ? "Error"+$!.message :
69
+ "Error: Syntax error in line #{$1} of config file '#{config_file}'"
70
+ Kernel.abort message
71
+ end
53
72
  end
54
73
  @config
55
74
  end
data/lib/boson/runner.rb CHANGED
@@ -11,13 +11,13 @@ module Boson
11
11
 
12
12
  # Libraries that come with Boson
13
13
  def default_libraries
14
- [Boson::Commands::Core, Boson::Commands::WebCore]
14
+ [Boson::Commands::Core, Boson::Commands::WebCore] + (Boson.repo.config[:defaults] || [])
15
15
  end
16
16
 
17
17
  # Libraries detected in repositories
18
18
  def detected_libraries
19
19
  Boson.repos.map {|repo| Dir[File.join(repo.commands_dir, '**/*.rb')].
20
- map {|e| e.gsub(/.*commands\//,'').gsub('.rb','') } }.flatten
20
+ map {|e| e.gsub(/^#{repo.commands_dir}\/|\.rb$/, '')} }.flatten
21
21
  end
22
22
 
23
23
  # Libraries specified in config files and detected_libraries
@@ -1,29 +1,38 @@
1
1
  module Boson
2
- # Runs Boson from the commandline. Usage for the boson shell command looks like this:
2
+ # This class handles the boson executable (boson command execution from the commandline). Any changes
3
+ # to your commands are immediately available from the commandline except for changes to the main config file.
4
+ # For those changes to take effect you need to explicitly load and index the libraries with --index.
5
+ # See Index to understand how Boson can immediately detect the latest commands.
6
+ #
7
+ # Usage for the boson shell command looks like this:
3
8
  # boson [GLOBAL OPTIONS] [COMMAND] [ARGS] [COMMAND OPTIONS]
4
9
  #
5
10
  # The boson executable comes with these global options:
6
11
  # [:help] Gives a basic help of global options. When a command is given the help shifts to a command's help.
7
12
  # [:verbose] Using this along with :help option shows more help. Also gives verbosity to other actions i.e. loading.
8
- # [:index] Updates index. This should be called in the unusual case that Boson doesn't detect new commands
9
- # and libraries.
10
13
  # [:execute] Like ruby -e, this executes a string of ruby code. However, this has the advantage that all
11
14
  # commands are available as normal methods, automatically loading as needed. This is a good
12
15
  # way to call commands that take non-string arguments.
13
16
  # [:console] This drops Boson into irb after having loaded default commands and any explict libraries with
14
- # :load option. This is a good way to start irb with only certain libraries loaded.
17
+ # :load option. This is a good way to start irb with only certain libraries loaded.
15
18
  # [:load] Explicitly loads a list of libraries separated by commas. Most useful when used with :console option.
16
19
  # Can also be used to explicitly load libraries that aren't being detected automatically.
20
+ # [:index] Updates index for given libraries allowing you to use them. This is useful if Boson's autodetection of
21
+ # changed libraries isn't picking up your changes. Since this option has a :bool_default attribute, arguments
22
+ # passed to this option need to be passed with '=' i.e. '--index=my_lib'.
17
23
  # [:render] Pretty formats the results of commands without options. Handy for commands that return arrays.
24
+ # [:pager_toggle] Toggles Hirb's pager in case you'd like to pipe to another command.
18
25
  class BinRunner < Runner
19
26
  GLOBAL_OPTIONS = {
20
27
  :verbose=>{:type=>:boolean, :desc=>"Verbose description of loading libraries or help"},
21
- :index=>{:type=>:boolean, :desc=>"Updates index for libraries and commands"},
28
+ :index=>{:type=>:array, :desc=>"Libraries to index. Libraries must be passed with '='.",
29
+ :bool_default=>nil, :values=>all_libraries, :enum=>false},
22
30
  :execute=>{:type=>:string, :desc=>"Executes given arguments as a one line script"},
23
31
  :console=>{:type=>:boolean, :desc=>"Drops into irb with default and explicit libraries loaded"},
24
32
  :help=>{:type=>:boolean, :desc=>"Displays this help message or a command's help if given a command"},
25
33
  :load=>{:type=>:array, :values=>all_libraries, :enum=>false, :desc=>"A comma delimited array of libraries to load"},
26
- :render=>{:type=>:boolean, :desc=>"Renders a Hirb view from result of command without options"}
34
+ :render=>{:type=>:boolean, :desc=>"Renders a Hirb view from result of command without options"},
35
+ :pager_toggle=>{:type=>:boolean, :desc=>"Toggles Hirb's pager"}
27
36
  } #:nodoc:
28
37
 
29
38
  class <<self
@@ -34,6 +43,7 @@ module Boson
34
43
  return print_usage if args.empty? || (@command.nil? && !@options[:console] && !@options[:execute])
35
44
  return ConsoleRunner.bin_start(@options[:console], @options[:load]) if @options[:console]
36
45
  init
46
+ View.toggle_pager if @options[:pager_toggle]
37
47
 
38
48
  if @options[:help]
39
49
  Boson.invoke(:usage, @command, :verbose=>@options[:verbose])
@@ -43,14 +53,16 @@ module Boson
43
53
  execute_command
44
54
  end
45
55
  rescue Exception
46
- print_error_message (@command && !Boson.can_invoke?(@command[/\w+/])) ?
56
+ is_invalid_command = lambda {|command| !Boson.can_invoke?(command[/\w+/]) ||
57
+ (Boson.can_invoke?(command[/\w+/]) && command.include?('.') && $!.is_a?(NoMethodError)) }
58
+ print_error_message @command && is_invalid_command.call(@command) ?
47
59
  "Error: Command '#{@command}' not found" : "Error: #{$!.message}"
48
60
  end
49
61
 
50
62
  # Loads the given command.
51
63
  def init
52
64
  super
53
- Index.update(:verbose=>true) if @options[:index]
65
+ Index.update(:verbose=>true, :libraries=>@options[:index]) if @options.key?(:index)
54
66
  if @options[:load]
55
67
  Manager.load @options[:load], load_options
56
68
  elsif @options[:execute]
@@ -62,13 +74,13 @@ module Boson
62
74
 
63
75
  #:stopdoc:
64
76
  def print_error_message(message)
65
- message += "\nActual error: #{$!}\n" + $!.backtrace.inspect if @options && @options[:verbose]
77
+ message += "\nOriginal error: #{$!}\n" + $!.backtrace.slice(0,10).map {|e| " " + e }.join("\n") if @options && @options[:verbose]
66
78
  $stderr.puts message
67
79
  end
68
80
 
69
81
  def load_command_by_index
70
- Index.update(:verbose=>@options[:verbose]) if !@options[:index] && Boson.can_invoke?(@command) && !@options[:help]
71
- if !Boson.can_invoke?(@command) && ((lib = Index.find_library(@command)) ||
82
+ Index.update(:verbose=>@options[:verbose]) if !@options.key?(:index) && Boson.can_invoke?(@command) && !@options[:help]
83
+ if !Boson.can_invoke?(@command, false) && ((lib = Index.find_library(@command)) ||
72
84
  (Index.update(:verbose=>@options[:verbose]) && (lib = Index.find_library(@command))))
73
85
  Manager.load lib, load_options
74
86
  end
@@ -79,9 +91,7 @@ module Boson
79
91
  end
80
92
 
81
93
  def execute_command
82
- command, subcommand = @command.include?('.') ? @command.split('.', 2) : [@command, nil]
83
- dispatcher = subcommand ? Boson.invoke(command) : Boson.main_object
84
- render_output dispatcher.send(subcommand || command, *@args)
94
+ render_output Boson.full_invoke(@command, @args)
85
95
  rescue ArgumentError
86
96
  # for the rare case it's raise outside of boson
87
97
  raise unless $!.backtrace.first.include?('boson/')
@@ -112,7 +122,7 @@ module Boson
112
122
  if @options[:verbose]
113
123
  Manager.load [Boson::Commands::Core]
114
124
  puts "\n\nDEFAULT COMMANDS"
115
- Boson.invoke :commands, "", :fields=>["name", "usage", "description"], :description=>false
125
+ Boson.invoke :commands, :fields=>["name", "usage", "description"], :description=>false
116
126
  end
117
127
  end
118
128
  #:startdoc:
@@ -1,9 +1,12 @@
1
1
  require 'shellwords'
2
2
  module Boson
3
- # Scientist redefines the methods of commands that have options and/or take global options. This redefinition
4
- # allows a command to receive its arguments normally or as a commandline app does. For a command's
5
- # method to be redefined correctly, its last argument _must_ expect a hash.
3
+ # Scientist redefines _any_ object's methods to act like shell commands while still receiving ruby arguments normally.
4
+ # It also let's your method have an optional view generated from a method's return value.
5
+ # Boson::Scientist.create_option_command redefines an object's method with a Boson::Command while
6
+ # Boson::Scientist.commandify redefines with just a hash. For an object's method to be redefined correctly,
7
+ # its last argument _must_ expect a hash.
6
8
  #
9
+ # === Examples
7
10
  # Take for example this basic method/command with an options definition:
8
11
  # options :level=>:numeric, :verbose=>:boolean
9
12
  # def foo(arg='', options={})
@@ -72,25 +75,26 @@ module Boson
72
75
  class Error < StandardError; end
73
76
  class EscapeGlobalOption < StandardError; end
74
77
 
75
- attr_reader :global_options, :rendered
78
+ attr_reader :global_options, :rendered, :option_parsers, :command_options
76
79
  @no_option_commands ||= []
77
80
  GLOBAL_OPTIONS = {
78
81
  :help=>{:type=>:boolean, :desc=>"Display a command's help"},
79
82
  :render=>{:type=>:boolean, :desc=>"Toggle a command's default rendering behavior"},
80
83
  :verbose=>{:type=>:boolean, :desc=>"Increase verbosity for help, errors, etc."},
81
84
  :global=>{:type=>:string, :desc=>"Pass a string of global options without the dashes"},
82
- :pretend=>{:type=>:boolean, :desc=>"Display what a command would execute without executing it"}
85
+ :pretend=>{:type=>:boolean, :desc=>"Display what a command would execute without executing it"},
83
86
  } #:nodoc:
84
87
  RENDER_OPTIONS = {
85
88
  :fields=>{:type=>:array, :desc=>"Displays fields in the order given"},
86
- :sort=>{:type=>:string, :desc=>"Sort by given field"},
87
89
  :class=>{:type=>:string, :desc=>"Hirb helper class which renders"},
88
- :reverse_sort=>{:type=>:boolean, :desc=>"Reverse a given sort"},
89
90
  :max_width=>{:type=>:numeric, :desc=>"Max width of a table"},
90
- :vertical=>{:type=>:boolean, :desc=>"Display a vertical table"}
91
+ :vertical=>{:type=>:boolean, :desc=>"Display a vertical table"},
92
+ :sort=>{:type=>:string, :desc=>"Sort by given field"},
93
+ :reverse_sort=>{:type=>:boolean, :desc=>"Reverse a given sort"},
94
+ :query=>{:type=>:hash, :desc=>"Queries fields given field:search pairs"},
91
95
  } #:nodoc:
92
96
 
93
- # Redefines a command's method for the given object.
97
+ # Redefines an object's method with a Command of the same name.
94
98
  def create_option_command(obj, command)
95
99
  cmd_block = create_option_command_block(obj, command)
96
100
  @no_option_commands << command if command.options.nil?
@@ -99,6 +103,29 @@ module Boson
99
103
  }
100
104
  end
101
105
 
106
+ # A wrapper around create_option_command that doesn't depend on a Command object. Rather you
107
+ # simply pass a hash of command attributes (see Command.new) or command methods and let OpenStruct mock a command.
108
+ # The only required attribute is :name, though to get any real use you should define :options and
109
+ # :arg_size (default is '*'). Example:
110
+ # >> def checkit(*args); args; end
111
+ # => nil
112
+ # >> Boson::Scientist.commandify(self, :name=>'checkit', :options=>{:verbose=>:boolean, :num=>:numeric})
113
+ # => ['checkit']
114
+ # # regular ruby method
115
+ # >> checkit 'one', 'two', :num=>13, :verbose=>true
116
+ # => ["one", "two", {:num=>13, :verbose=>true}]
117
+ # # commandline ruby method
118
+ # >> checkit 'one two -v -n=13'
119
+ # => ["one", "two", {:num=>13, :verbose=>true}]
120
+ def commandify(obj, hash)
121
+ raise ArgumentError, ":name required" unless hash[:name]
122
+ hash[:arg_size] ||= '*'
123
+ hash[:has_splat_args?] = true if hash[:arg_size] == '*'
124
+ fake_cmd = OpenStruct.new(hash)
125
+ fake_cmd.option_parser ||= OptionParser.new(fake_cmd.options || {})
126
+ create_option_command(obj, fake_cmd)
127
+ end
128
+
102
129
  # The actual method which replaces a command's original method
103
130
  def create_option_command_block(obj, command)
104
131
  lambda {|*args|
@@ -123,8 +150,13 @@ module Boson
123
150
 
124
151
  def translate_args(obj, command, args)
125
152
  @obj, @command, @args = obj, command, args
153
+ # prepends default option
154
+ if @command.default_option && @command.arg_size == 1 && !@command.has_splat_args? && @args[0].to_s[/./] != '-'
155
+ @args[0] = "--#{@command.default_option}=#{@args[0]}" unless @args.join.empty? || @args[0].is_a?(Hash)
156
+ end
126
157
  @command.options ||= {}
127
- if parsed_options = command_options
158
+
159
+ if parsed_options = parse_command_options
128
160
  add_default_args(@args)
129
161
  return @args if @no_option_commands.include?(@command)
130
162
  @args << parsed_options
@@ -143,65 +175,103 @@ module Boson
143
175
  end
144
176
 
145
177
  def render_or_raw(result)
146
- (@rendered = render?) ? View.render(result, global_render_options) : result
178
+ if (@rendered = render?)
179
+ result = run_pipe_commands(result)
180
+ render_global_opts = @global_options.dup.delete_if {|k,v| default_global_options.keys.include?(k) }
181
+ View.render(result, render_global_opts, false)
182
+ else
183
+ result = View.search_and_sort(result, @global_options) if !(@global_options.keys & [:sort, :reverse_sort, :query]).empty?
184
+ run_pipe_commands(result)
185
+ end
147
186
  rescue Exception
148
187
  message = @global_options[:verbose] ? "#{$!}\n#{$!.backtrace.inspect}" : $!.message
149
188
  raise Error, message
150
189
  end
151
190
 
191
+ def pipe_options
192
+ @pipe_options ||= Hash[*default_global_options.select {|k,v| v[:pipe] }.flatten]
193
+ end
194
+
195
+ def run_pipe_commands(result)
196
+ (global_options.keys & pipe_options.keys).each {|e|
197
+ command = pipe_options[e][:pipe] != true ? pipe_options[e][:pipe] : e
198
+ pipe_result = pipe_options[e][:type] == :boolean ? Boson.invoke(command, result) :
199
+ Boson.invoke(command, result, global_options[e])
200
+ result = pipe_result if pipe_options[e][:filter]
201
+ }
202
+ result
203
+ end
204
+
205
+ # choose current parser
152
206
  def option_parser
153
207
  @command.render_options ? command_option_parser : default_option_parser
154
208
  end
155
209
 
210
+ # current command parser
156
211
  def command_option_parser
157
- (@option_parsers ||= {})[@command] ||= OptionParser.new render_options.merge(GLOBAL_OPTIONS)
212
+ (@option_parsers ||= {})[@command] ||= OptionParser.new current_command_options
158
213
  end
159
214
 
215
+ # set cmd and use its parser
160
216
  def render_option_parser(cmd)
161
217
  @command = cmd
162
218
  option_parser
163
219
  end
164
220
 
165
221
  def default_option_parser
166
- @default_option_parser ||= OptionParser.new RENDER_OPTIONS.merge(GLOBAL_OPTIONS)
222
+ @default_option_parser ||= OptionParser.new default_render_options.merge(default_global_options)
223
+ end
224
+
225
+ def default_global_options
226
+ @default_global_options ||= GLOBAL_OPTIONS.merge Boson.repo.config[:global_options] || {}
167
227
  end
168
228
 
169
- def render_options
170
- @command.render_options ? command_render_options : RENDER_OPTIONS
229
+ def default_render_options
230
+ @default_render_options ||= RENDER_OPTIONS.merge Boson.repo.config[:render_options] || {}
171
231
  end
172
232
 
173
- def command_render_options
174
- (@command_render_options ||= {})[@command] ||= begin
233
+ def current_command_options
234
+ (@command_options ||= {})[@command] ||= begin
175
235
  @command.render_options.each {|k,v|
176
- if !v.is_a?(Hash) && !v.is_a?(Symbol) && RENDER_OPTIONS.keys.include?(k)
236
+ if !v.is_a?(Hash) && !v.is_a?(Symbol)
177
237
  @command.render_options[k] = {:default=>v}
178
238
  end
179
239
  }
180
- opts = Util.recursive_hash_merge(@command.render_options, Util.deep_copy(RENDER_OPTIONS))
181
- opts[:sort][:values] ||= opts[:fields][:values] if opts[:fields][:values]
240
+ render_opts = Util.recursive_hash_merge(@command.render_options, Util.deep_copy(default_render_options))
241
+ opts = Util.recursive_hash_merge render_opts, Util.deep_copy(default_global_options)
242
+ if !opts[:fields].key?(:values)
243
+ if opts[:fields][:default]
244
+ opts[:fields][:values] = opts[:fields][:default]
245
+ else
246
+ opts[:fields][:values] = opts[:change_fields][:default].values if opts[:change_fields] && opts[:change_fields][:default]
247
+ opts[:fields][:values] ||= opts[:headers][:default].keys if opts[:headers] && opts[:headers][:default]
248
+ end
249
+ opts[:fields][:enum] = false if opts[:fields][:values] && !opts[:fields].key?(:enum)
250
+ end
251
+ if opts[:fields][:values]
252
+ opts[:sort][:values] ||= opts[:fields][:values]
253
+ opts[:query][:keys] ||= opts[:fields][:values]
254
+ opts[:query][:default_keys] ||= "*"
255
+ end
182
256
  opts
183
257
  end
184
258
  end
185
259
 
186
- def global_render_options
187
- @global_options.dup.delete_if {|k,v| !render_options.keys.include?(k) }
188
- end
189
-
190
260
  def render?
191
261
  (@command.render_options && !@global_options[:render]) || (!@command.render_options && @global_options[:render])
192
262
  end
193
263
 
194
- def command_options
264
+ def parse_command_options
195
265
  if @args.size == 1 && @args[0].is_a?(String)
196
266
  parsed_options, @args = parse_options Shellwords.shellwords(@args[0])
197
267
  # last string argument interpreted as args + options
198
268
  elsif @args.size > 1 && @args[-1].is_a?(String)
199
- args = caller.grep(/bin_runner.rb:/).empty? ? Shellwords.shellwords(@args.pop) : @args
269
+ args = Boson.const_defined?(:BinRunner) ? @args : Shellwords.shellwords(@args.pop)
200
270
  parsed_options, new_args = parse_options args
201
271
  @args += new_args
202
272
  # add default options
203
- elsif (!@command.has_splat_args? && @args.size <= @command.arg_size - 1) ||
204
- (@command.has_splat_args? && !@args[-1].is_a?(Hash))
273
+ elsif @command.options.empty? || (!@command.has_splat_args? &&
274
+ @args.size <= (@command.arg_size - 1).abs) || (@command.has_splat_args? && !@args[-1].is_a?(Hash))
205
275
  parsed_options = parse_options([])[0]
206
276
  # merge default options with given hash of options
207
277
  elsif (@command.has_splat_args? || (@args.size == @command.arg_size)) && @args[-1].is_a?(Hash)