boson 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT LICENSE
2
2
 
3
- Copyright (c) 2009 Gabriel Horner
3
+ Copyright (c) 2010 Gabriel Horner
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -18,8 +18,8 @@ Note: To read a linkable version of this README, {see here}[http://tagaholic.me/
18
18
  * can have any number of local and global options (Boson::OptionCommand). Options are defined with Boson::OptionParser.
19
19
  * can have any view associated to it (via Hirb) without adding view code to the command's method.
20
20
  These views can be toggled on and manipulated via global render options (Boson::View and Boson::OptionCommand).
21
- * can pipe their return value into multiple commands with pipe options. Default pipe options give the ability
22
- to search and sort an array of any objects (Boson::Pipe).
21
+ * can pipe their return value into custom pipe options (Boson::Pipe).
22
+ * has default pipe options to search and sort an array of any objects (Boson::Pipes).
23
23
  * Option parser (Boson::OptionParser)
24
24
  * provides option types that map to objects i.e. :array type creates Array objects.
25
25
  * come with 5 default option types: boolean, array, string, hash and numeric.
data/Rakefile CHANGED
@@ -25,14 +25,14 @@ begin
25
25
  s.authors = ["Gabriel Horner"]
26
26
  s.has_rdoc = true
27
27
  s.rubyforge_project = 'tagaholic'
28
- s.add_dependency 'hirb', '>= 0.2.8'
28
+ s.add_dependency 'hirb', '>= 0.2.10'
29
29
  s.add_dependency 'alias', '>= 0.2.1'
30
30
  s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
31
31
  s.files = FileList["Rakefile", "VERSION.yml", "README.rdoc", "LICENSE.txt", "{bin,lib,test}/**/*"]
32
32
  end
33
33
 
34
34
  rescue LoadError
35
- puts "Jeweler not available. Install it for jeweler-related tasks with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
35
+ puts "Jeweler not available. Install it for jeweler-related tasks with: sudo gem install jeweler"
36
36
  end
37
37
 
38
38
  Rake::TestTask.new do |t|
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
2
  :minor: 2
3
- :patch: 2
3
+ :patch: 3
4
4
  :build:
5
5
  :major: 0
data/lib/boson.rb CHANGED
@@ -5,7 +5,7 @@ $:.unshift File.dirname(__FILE__) unless $:.include? File.expand_path(File.dirna
5
5
  # order of library subclasses matters
6
6
  %w{module file gem require local_file}.each {|e| require "boson/libraries/#{e}_library" }
7
7
  (%w{namespace view command util commands option_parser options} +
8
- %w{index repo_index scientist option_command pipe}).each {|e| require "boson/#{e}" }
8
+ %w{index repo_index scientist option_command pipe pipes}).each {|e| require "boson/#{e}" }
9
9
 
10
10
  # This module stores the libraries, commands, repos and main object used throughout Boson.
11
11
  #
data/lib/boson/command.rb CHANGED
@@ -72,7 +72,7 @@ module Boson
72
72
  @args = [['*args']]
73
73
  end
74
74
  end
75
- @config = (hash.delete(:config) || {}).merge(hash)
75
+ @config = Util.recursive_hash_merge hash, hash.delete(:config) || {}
76
76
  end
77
77
 
78
78
  # Library object a command belongs to.
@@ -96,6 +96,11 @@ module Boson
96
96
  @option_parser ||= OptionParser.new(@options || {})
97
97
  end
98
98
 
99
+ # Option parser for command as defined by @render_options.
100
+ def render_option_parser
101
+ option_command? ? Boson::Scientist.option_command(self).option_parser : nil
102
+ end
103
+
99
104
  # Help string for options if a command has it.
100
105
  def option_help
101
106
  @options ? option_parser.to_s : ''
@@ -120,6 +125,11 @@ module Boson
120
125
  end
121
126
 
122
127
  #:stopdoc:
128
+ # until @config is consistent in index + actual loading
129
+ def config
130
+ @config ||= {}
131
+ end
132
+
123
133
  def file_string_and_method_for_args(lib)
124
134
  if !lib.is_a?(ModuleLibrary) && (klass_method = (lib.class_commands || {})[@name])
125
135
  if RUBY_VERSION >= '1.9'
@@ -135,7 +145,7 @@ module Boson
135
145
  end
136
146
 
137
147
  def has_splat_args?
138
- !!(@args && @args[-1] && @args[-1][0][/^\*/])
148
+ !!(args && @args[-1] && @args[-1][0][/^\*/])
139
149
  end
140
150
 
141
151
  def make_option_command(lib=library)
@@ -8,13 +8,16 @@ module Boson::Commands::Core #:nodoc:
8
8
  commands = {
9
9
  'render'=>{:desc=>"Render any object using Hirb"},
10
10
  'menu'=>{:desc=>"Provide a menu to multi-select elements from a given array"},
11
- 'usage'=>{:desc=>"Print a command's usage", :options=>{[:verbose, :V]=>:boolean}},
11
+ 'usage'=>{:desc=>"Print a command's usage", :options=>{
12
+ :verbose=>{:desc=>"Display global options", :type=>:boolean},
13
+ :render_options=>{:desc=>"Render options for option tables", :default=>{},
14
+ :keys=>[:vertical, :fields, :hide_empty]} } },
12
15
  'commands'=>{
13
16
  :desc=>"List or search commands. Query must come before any options.", :default_option=>'query',
14
17
  :options=>{ :index=>{:type=>:boolean, :desc=>"Searches index"},
15
18
  :local=>{:type=>:boolean, :desc=>"Local commands only" } },
16
19
  :render_options=>{
17
- :headers=>{:default=>{:desc=>'description'}},
20
+ [:headers,:H]=>{:default=>{:desc=>'description'}},
18
21
  :query=>{:keys=>command_attributes, :default_keys=>'full_name'},
19
22
  :fields=>{:default=>[:full_name, :lib, :alias, :usage, :desc], :values=>command_attributes, :enum=>false},
20
23
  :filters=>{:default=>{:render_options=>:inspect, :options=>:inspect, :args=>:inspect, :config=>:inspect}}
@@ -53,20 +56,22 @@ module Boson::Commands::Core #:nodoc:
53
56
  Boson::View.render(object, options)
54
57
  end
55
58
 
56
- def menu(output, options={}, &block)
57
- Hirb::Console.format_output(output, options.merge(:class=>"Hirb::Menu"), &block)
59
+ def menu(arr, options={}, &block)
60
+ Hirb::Console.format_output(arr, options.merge(:class=>"Hirb::Menu"), &block)
58
61
  end
59
62
 
60
63
  def usage(command, options={})
61
- msg = (cmd = Boson::Command.find(command)) ? "#{command} #{cmd.usage}" : "Command '#{cmd}' not found"
64
+ msg = (cmd = Boson::Command.find(command)) ? "#{command} #{cmd.usage}" : "Command '#{command}' not found"
62
65
  puts msg
63
- if cmd && options[:verbose]
64
- if cmd.options && !cmd.options.empty?
65
- puts "\nLOCAL OPTIONS"
66
- cmd.option_parser.print_usage_table
67
- end
66
+ return if options[:one_line] || !cmd
67
+
68
+ if cmd.options && !cmd.options.empty?
69
+ puts "\nLOCAL OPTIONS"
70
+ cmd.option_parser.print_usage_table options[:render_options].dup.merge(:local=>true)
71
+ end
72
+ if options[:verbose]
68
73
  puts "\nGLOBAL OPTIONS"
69
- Boson::Scientist.option_command(cmd).option_parser.print_usage_table
74
+ cmd.render_option_parser.print_usage_table options[:render_options].dup
70
75
  end
71
76
  end
72
77
  end
@@ -1,52 +1,59 @@
1
- module Boson::Commands::WebCore #:nodoc:
1
+ module Boson::Commands::WebCore
2
2
  extend self
3
3
 
4
- def config
4
+ def config #:nodoc:
5
5
  descriptions = {
6
6
  :install=>"Installs a library by url. Library should then be loaded with load_library.",
7
- :browser=>"Opens urls in a browser on a Mac", :get=>"Gets the body of a url", :post=>'Posts to url' }
7
+ :browser=>"Opens urls in a browser on a Mac", :get=>"Gets the body of a url", :post=>'Posts to url',
8
+ :build_url=>"Builds a url, escaping the given params"
9
+ }
8
10
  commands = descriptions.inject({}) {|h,(k,v)| h[k.to_s] = {:desc=>v}; h}
9
11
  commands['install'][:options] = {:name=>{:type=>:string, :desc=>"Library name to save to"},
10
12
  :force=>{:type=>:boolean, :desc=>'Overwrites an existing library'},
13
+ :default=>{:type=>:boolean, :desc=>'Adds library as a default library to main config file'},
11
14
  :module_wrap=>{:type=>:boolean, :desc=>"Wraps a module around install using library name"},
12
15
  :method_wrap=>{:type=>:boolean, :desc=>"Wraps a method and module around installed library using library name"}}
16
+ commands['install'][:args] = [['url'],['options', {}]]
13
17
  {:library_file=>File.expand_path(__FILE__), :commands=>commands, :namespace=>false}
14
18
  end
15
19
 
16
- def get(url, options={})
17
- %w{uri net/http}.each {|e| require e }
18
- if options[:success_only]
19
- url = URI.parse(url)
20
- res = Net::HTTP.start(url.host, url.port) {|http| http.get(url.request_uri) }
21
- res.code == '200' ? res.body : nil
22
- else
23
- Net::HTTP.get(URI.parse(url))
20
+ # Requires libraries only once before defining method with given block
21
+ def self.def_which_requires(meth, *libs, &block)
22
+ define_method(meth) do |*args|
23
+ libs.each {|e| require e }
24
+ define_method(meth, block).call(*args)
24
25
  end
25
- rescue
26
- raise "Error opening #{url}"
27
26
  end
28
27
 
29
- def post(url, options={})
30
- %w{uri net/http}.each {|e| require e }
28
+ def_which_requires(:get, 'uri', 'net/http') do |url, options|
29
+ options ||= {}
30
+ url = build_url(url, options[:params]) if options[:params]
31
+ Get.new(url).request(options)
32
+ end
33
+
34
+ def_which_requires(:build_url, 'cgi') do |url, params|
35
+ url + (url[/\?/] ? '&' : "?") + params.map {|k,v|
36
+ v = v.is_a?(Array) ? v.join(' ') : v.to_s
37
+ "#{k}=#{CGI.escape(v)}"
38
+ }.join("&")
39
+ end
40
+
41
+ def_which_requires(:post, 'uri', 'net/http') do |url, options|
31
42
  Net::HTTP.post_form(URI.parse(url), options)
32
43
  end
33
44
 
34
- def install(url, options={})
45
+ def install(url, options={}) #:nodoc:
35
46
  options[:name] ||= strip_name_from_url(url)
36
47
  return puts("Please give a library name for this url.") if options[:name].empty?
37
- filename = File.join Boson.repo.commands_dir, "#{options[:name]}.rb"
48
+ filename = File.join ::Boson.repo.commands_dir, "#{options[:name]}.rb"
38
49
  return puts("Library name #{options[:name]} already exists. Try a different name.") if File.exists?(filename) && !options[:force]
39
- File.open(filename, 'w') {|f| f.write get(url) }
40
50
 
41
- if options[:method_wrap] || options[:module_wrap]
42
- file_string = File.read(filename)
43
- file_string = "def #{options[:name]}\n#{file_string}\nend" if options[:method_wrap]
44
- unless (mod_name = Boson::Util.camelize(options[:name]))
45
- return puts("Can't wrap install with name #{options[:name]}")
46
- end
47
- file_string = "module #{mod_name}\n#{file_string}\nend"
48
- File.open(filename, 'w') {|f| f.write file_string }
49
- end
51
+ file_string = get(url)
52
+ file_string = "# Originally from #{url}\n"+file_string
53
+ file_string = wrap_install(file_string, options) if options[:method_wrap] || options[:module_wrap]
54
+
55
+ File.open(filename, 'w') {|f| f.write file_string }
56
+ Boson.repo.update_config {|c| (c[:defaults] ||= []) << options[:name] } if options[:default]
50
57
  puts "Saved to #{filename}."
51
58
  end
52
59
 
@@ -56,7 +63,80 @@ module Boson::Commands::WebCore #:nodoc:
56
63
  end
57
64
 
58
65
  private
66
+ def wrap_install(file_string, options)
67
+ indent = " "
68
+ unless (mod_name = ::Boson::Util.camelize(options[:name]))
69
+ return puts("Can't wrap install with name #{options[:name]}")
70
+ end
71
+
72
+ file_string.gsub!(/(^)/,'\1'+indent)
73
+ file_string = "def #{options[:name]}\n#{file_string}\nend".gsub(/(^)/,'\1'+indent) if options[:method_wrap]
74
+ "module #{mod_name}\n#{file_string}\nend"
75
+ end
76
+
59
77
  def strip_name_from_url(url)
60
78
  url[/\/([^\/.]+)(\.[a-z]+)?$/, 1].to_s.gsub('-', '_').gsub(/[^a-zA-Z_]/, '')
61
79
  end
80
+
81
+ # Used by the get command to make get requests and optionally parse json and yaml.
82
+ # Ruby 1.8.x is dependent on json gem for parsing json.
83
+ # See Get.request for options a request can take.
84
+ class Get
85
+ FORMAT_HEADERS = {
86
+ :json=>%w{application/json text/json application/javascript text/javascript},
87
+ :yaml=>%w{application/x-yaml text/yaml}
88
+ } #:nodoc:
89
+
90
+ def initialize(url, options={})
91
+ @url, @options = url, options
92
+ end
93
+
94
+ # Returns the response body string or a parsed data structure. Returns nil if request fails. By default expects response
95
+ # to be 200.
96
+ # ==== Options:
97
+ # [:any_response] Returns body string for any response code. Default is false.
98
+ # [:parse] Parse the body into either json or yaml. Expects a valid format or if true autodetects one.
99
+ # Default is false.
100
+ # [:raise_error] Raises any original errors when parsing or fetching url instead of handling errors silently.
101
+ def request(options={})
102
+ @options.merge! options
103
+ body = get_body
104
+ body && @options[:parse] ? parse_body(body) : body
105
+ end
106
+
107
+ private
108
+ # Returns body string if successful or nil if not.
109
+ def get_body
110
+ uri = URI.parse(@url)
111
+ @response = Net::HTTP.get_response uri
112
+ (@options[:any_response] || @response.code == '200') ? @response.body : nil
113
+ rescue
114
+ @options[:raise_error] ? raise : puts("Error: GET '#{@url}' -> #{$!.class}: #{$!.message}")
115
+ end
116
+
117
+ # Returns nil if dependencies or parsing fails
118
+ def parse_body(body)
119
+ format = determine_format(@options[:parse])
120
+ case format
121
+ when :json
122
+ unless ::Boson::Util.safe_require 'json'
123
+ return puts("Install the json gem to parse json: sudo gem install json")
124
+ end
125
+ JSON.parse body
126
+ when :yaml
127
+ YAML::load body
128
+ else
129
+ puts "Can't parse this format."
130
+ end
131
+ rescue
132
+ @options[:raise_error] ? raise : puts("Error while parsing #{format} response of '#{@url}': #{$!.class}")
133
+ end
134
+
135
+ def determine_format(format)
136
+ return format.to_sym if %w{json yaml}.include?(format.to_s)
137
+ return :json if FORMAT_HEADERS[:json].include?(@response.content_type)
138
+ return :yaml if FORMAT_HEADERS[:yaml].include?(@response.content_type)
139
+ nil
140
+ end
141
+ end
62
142
  end
@@ -68,9 +68,8 @@ module Boson
68
68
 
69
69
  #:stopdoc:
70
70
  def add_method_scraped_data
71
- (MethodInspector::METHODS + [:method_args]).each do |e|
72
- key = command_key(e)
73
- (@store[e] || []).each do |cmd, val|
71
+ (MethodInspector::METHODS + [:args]).each do |key|
72
+ (@store[key] || []).each do |cmd, val|
74
73
  @commands_hash[cmd] ||= {}
75
74
  add_scraped_data_to_config(key, val, cmd)
76
75
  end
@@ -94,15 +93,10 @@ module Boson
94
93
  scraped = CommentInspector.scrape(FileLibrary.read_library_file(file), lineno, MethodInspector.current_module)
95
94
  @commands_hash[cmd] ||= {}
96
95
  MethodInspector::METHODS.each do |e|
97
- add_scraped_data_to_config(command_key(e), scraped[e], cmd)
96
+ add_scraped_data_to_config(e, scraped[e], cmd)
98
97
  end
99
98
  end
100
99
  end
101
-
102
- # translates from inspector attribute name to command attribute name
103
- def command_key(key)
104
- {:method_args=>:args}[key] || key
105
- end
106
100
  #:startdoc:
107
101
  end
108
102
  end
@@ -37,13 +37,13 @@ module Boson
37
37
 
38
38
  # Scrapes a method's arguments using ArgumentInspector.
39
39
  def scrape_arguments(meth)
40
- store[:method_args] ||= {}
40
+ store[:args] ||= {}
41
41
 
42
42
  o = Object.new
43
43
  o.extend(@current_module)
44
44
  # private methods return nil
45
45
  if (val = ArgumentInspector.scrape_with_eval(meth, @current_module, o))
46
- store[:method_args][meth.to_s] = val
46
+ store[:args][meth.to_s] = val
47
47
  end
48
48
  end
49
49
 
@@ -18,8 +18,8 @@ module Boson
18
18
  # end
19
19
  #
20
20
  # Once loaded, this library can be run from the commandline or irb:
21
- # bash> boson take_over world
22
- # irb>> take_over 'world'
21
+ # $ boson take_over world
22
+ # >> take_over 'world'
23
23
  #
24
24
  # If the library is namespaced, the command would be run as brain.take_over.
25
25
  #
@@ -33,8 +33,8 @@ module Boson
33
33
  # end
34
34
  #
35
35
  # From the commandline and irb this runs as:
36
- # bash> boson take_over world -e initiate_brainiac
37
- # irb>> take_over 'world -e initiate_brainiac'
36
+ # $ boson take_over world -e initiate_brainiac
37
+ # >> take_over 'world -e initiate_brainiac'
38
38
  #
39
39
  # Since Boson aims to make your libraries just standard ruby, we can achieve the above
40
40
  # by making options a commented method attribute:
@@ -53,11 +53,11 @@ module Boson
53
53
  # * See Inspector for other method attributes, like config and render_options, that can be placed above a method.
54
54
  #
55
55
  # Once a command has a defined option, a command can also recognize a slew of global options:
56
- # irb>> take_over '-h'
56
+ # >> take_over '-h'
57
57
  # take_over [destination] [--execute=STRING]
58
58
  #
59
59
  # # prints much more verbose help
60
- # irb>> take_over '-hv'
60
+ # >> take_over '-hv'
61
61
  #
62
62
  # For more about these global options see OptionCommand and View.
63
63
  class FileLibrary < Library
data/lib/boson/manager.rb CHANGED
@@ -44,7 +44,7 @@ module Boson
44
44
  FileLibrary.reset_file_cache(library.to_s)
45
45
  failed_libraries << library
46
46
  $stderr.puts "Unable to #{load_method} library #{library}. Reason: #{e.message}"
47
- rescue Exception=>e
47
+ rescue StandardError=>e
48
48
  FileLibrary.reset_file_cache(library.to_s)
49
49
  failed_libraries << library
50
50
  message = "Unable to #{load_method} library #{library}. Reason: #{$!}"
@@ -1,9 +1,10 @@
1
1
  require 'shellwords'
2
2
  module Boson
3
3
  # A class used by Scientist to wrap around Command objects. It's main purpose is to parse
4
- # a command's global options (basic options, render options, pipe options) and local options.
4
+ # a command's global options (basic, render and pipe types) and local options.
5
5
  # As the names imply, global options are available to all commands while local options are specific to a command.
6
6
  # When passing options to commands, global ones _must_ be passed first, then local ones.
7
+ # Also, options _must_ all be passed either before or after arguments.
7
8
  # For more about pipe and render options see Pipe and View respectively.
8
9
  #
9
10
  # === Basic Global Options
@@ -61,6 +62,7 @@ module Boson
61
62
  :help=>{:type=>:boolean, :desc=>"Display a command's help"},
62
63
  :render=>{:type=>:boolean, :desc=>"Toggle a command's default rendering behavior"},
63
64
  :verbose=>{:type=>:boolean, :desc=>"Increase verbosity for help, errors, etc."},
65
+ :usage_options=>{:type=>:string, :desc=>"Render options to pass to usage/help"},
64
66
  :pretend=>{:type=>:boolean, :desc=>"Display what a command would execute without executing it"},
65
67
  :delete_options=>{:type=>:array, :desc=>'Deletes global options starting with given strings' }
66
68
  } #:nodoc:
@@ -76,6 +78,7 @@ module Boson
76
78
  :sort=>{:type=>:string, :desc=>"Sort by given field"},
77
79
  :reverse_sort=>{:type=>:boolean, :desc=>"Reverse a given sort"},
78
80
  :query=>{:type=>:hash, :desc=>"Queries fields given field:search pairs"},
81
+ :pipes=>{:alias=>'P', :type=>:array, :desc=>"Pipe to commands sequentially"}
79
82
  } #:nodoc:
80
83
 
81
84
  class <<self
@@ -77,6 +77,7 @@ module Boson
77
77
  EQ_RE = /^(--\w+[-\w+]*|-[a-zA-Z])=(.*)$/i
78
78
  SHORT_SQ_RE = /^-([a-zA-Z]{2,})$/i # Allow either -x -v or -xv style for single char args
79
79
  SHORT_NUM = /^(-[a-zA-Z])#{NUMERIC}$/i
80
+ STOP_STRINGS = %w{-- -}
80
81
 
81
82
  attr_reader :leading_non_opts, :trailing_non_opts, :opt_aliases
82
83
 
@@ -148,9 +149,9 @@ module Boson
148
149
  # Default is false.
149
150
  # [*:alias*] Alternative way to define option aliases with an option name or an array of them. Useful in yaml files.
150
151
  # Setting to false will prevent creating an automatic alias.
151
- # [*:values*] An array of values an option can have. Available for :array and :string options. Values here
152
- # can be aliased by typing a unique string it starts with. For example, for values foo, odd, optional,
153
- # f refers to foo, o to odd and op to optional.
152
+ # [*:values*] An array of values an option can have. Available for :array and :string options. Values here
153
+ # can be aliased by typing a unique string it starts with or underscore aliasing (see Util.underscore_search).
154
+ # For example, for values foo, odd and obnoxiously_long, f refers to foo, od to odd and o_l to obnoxiously_long.
154
155
  # [*:enum*] Boolean indicating if an option enforces values in :values or :keys. Default is true. For
155
156
  # :array, :hash and :string options.
156
157
  # [*:split*] For :array and :hash options. A string or regular expression on which an array value splits
@@ -196,8 +197,7 @@ module Boson
196
197
  case type
197
198
  when TrueClass then @defaults[nice_name] = true
198
199
  when FalseClass then @defaults[nice_name] = false
199
- else
200
- @defaults[nice_name] = type unless type.is_a?(Symbol)
200
+ else @defaults[nice_name] = type unless type.is_a?(Symbol)
201
201
  end
202
202
  mem[name] = !type.nil? ? determine_option_type(type) : type
203
203
  mem
@@ -207,7 +207,7 @@ module Boson
207
207
  @opt_aliases = @opt_aliases.sort.inject({}) {|h, (nice_name, aliases)|
208
208
  name = dasherize nice_name
209
209
  # allow for aliases as symbols
210
- aliases.map! {|e| e.to_s.index('-') != 0 ? dasherize(e.to_s) : e }
210
+ aliases.map! {|e| e.to_s.index('-') == 0 || e == false ? e : dasherize(e.to_s) }
211
211
  if aliases.empty? and nice_name.length > 1
212
212
  opt_alias = nice_name[0,1]
213
213
  opt_alias = h.key?("-"+opt_alias) ? "-"+opt_alias.capitalize : "-"+opt_alias
@@ -232,7 +232,7 @@ module Boson
232
232
 
233
233
  @leading_non_opts = []
234
234
  unless flags[:opts_before_args]
235
- @leading_non_opts << shift until current_is_option? || @args.empty? || peek == '--'
235
+ @leading_non_opts << shift until current_is_option? || @args.empty? || STOP_STRINGS.include?(peek)
236
236
  end
237
237
 
238
238
  while current_is_option?
@@ -281,20 +281,41 @@ module Boson
281
281
 
282
282
  # More verbose option help in the form of a table.
283
283
  def print_usage_table(render_options={})
284
+ user_fields = render_options.delete(:fields)
285
+ fields = get_usage_fields user_fields
286
+ (fields << :default).uniq! if render_options.delete(:local) || user_fields == '*'
287
+ opts = all_options_with_fields fields
288
+ fields.delete(:default) if fields.include?(:default) && opts.all? {|e| e[:default].nil? }
289
+ render_options = default_render_options.merge(:fields=>fields).merge(render_options)
290
+ View.render opts, render_options
291
+ end
292
+
293
+ def all_options_with_fields(fields) #:nodoc:
284
294
  aliases = @opt_aliases.invert
285
- additional = [:desc, :values].select {|e| (@option_attributes || {}).values.any? {|f| f.key?(e) } }
286
- additional_opts = {:desc=>[:desc], :values=>[:values, :keys]}
287
- opts = @opt_types.keys.sort.inject([]) {|t,e|
288
- h = {:name=>e, :aliases=>aliases[e], :type=>@opt_types[e]}
289
- additional.each {|f|
290
- h[f] = additional_opts[f].map {|g| (@option_attributes[undasherize(e)] || {})[g]}.flatten.compact
295
+ @opt_types.keys.sort.inject([]) {|t,e|
296
+ nice_name = undasherize(e)
297
+ h = {:name=>e, :type=>@opt_types[e], :alias=>aliases[e] || '' }
298
+ h[:default] = @defaults[nice_name] if fields.include?(:default)
299
+ (fields - h.keys).each {|f|
300
+ h[f] = (option_attributes[nice_name] || {})[f]
291
301
  }
292
302
  t << h
293
303
  }
294
- render_options = {:headers=>{:name=>"Option", :aliases=>"Alias", :desc=>'Description', :values=>'Values/Keys', :type=>'Type'},
295
- :fields=>[:name, :aliases, :type] + additional, :description=>false, :filters=>{:values=>lambda {|e| (e || []).join(',')} }
296
- }.merge(render_options)
297
- View.render opts, render_options
304
+ end
305
+
306
+ def default_render_options #:nodoc:
307
+ {:header_filter=>:capitalize, :description=>false, :filter_any=>true,
308
+ :filter_classes=>{Array=>[:join, ',']}, :hide_empty=>true}
309
+ end
310
+
311
+ # Hash of option names mapped to hash of its external attributes
312
+ def option_attributes
313
+ @option_attributes || {}
314
+ end
315
+
316
+ def get_usage_fields(fields) #:nodoc:
317
+ fields || ([:name, :alias, :type] + [:desc, :values, :keys].select {|e|
318
+ option_attributes.values.any? {|f| f.key?(e) } }).uniq
298
319
  end
299
320
 
300
321
  # Hash of option attributes for the currently parsed option. _Any_ hash keys
@@ -343,8 +364,7 @@ module Boson
343
364
  case value
344
365
  when TrueClass, FalseClass then :boolean
345
366
  when Numeric then :numeric
346
- else
347
- Util.underscore(value.class.to_s).to_sym
367
+ else Util.underscore(value.class.to_s).to_sym
348
368
  end
349
369
  end
350
370
 
@@ -365,7 +385,15 @@ module Boson
365
385
  end
366
386
 
367
387
  def auto_alias_value(values, possible_value)
368
- values.find {|v| v.to_s =~ /^#{possible_value}/ } || possible_value
388
+ if Boson.repo.config[:option_underscore_search]
389
+ self.class.send(:define_method, :auto_alias_value) {|values, possible_value|
390
+ Util.underscore_search(possible_value, values, true) || possible_value
391
+ }.call(values, possible_value)
392
+ else
393
+ self.class.send(:define_method, :auto_alias_value) {|values, possible_value|
394
+ values.find {|v| v.to_s =~ /^#{possible_value}/ } || possible_value
395
+ }.call(values, possible_value)
396
+ end
369
397
  end
370
398
 
371
399
  def validate_enum_values(values, possible_values)
@@ -386,7 +414,7 @@ module Boson
386
414
 
387
415
  def delete_invalid_opts
388
416
  @trailing_non_opts.delete_if {|e|
389
- break if %w{- --}.include? e
417
+ break if STOP_STRINGS.include? e
390
418
  invalid = e.to_s[/^-/]
391
419
  $stderr.puts "Deleted invalid option '#{e}'" if invalid
392
420
  invalid