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/README.rdoc +15 -3
- data/Rakefile +2 -2
- data/VERSION.yml +1 -1
- data/lib/boson.rb +22 -4
- data/lib/boson/command.rb +12 -4
- data/lib/boson/commands/core.rb +19 -19
- data/lib/boson/commands/web_core.rb +18 -5
- data/lib/boson/index.rb +31 -22
- data/lib/boson/inspector.rb +12 -10
- data/lib/boson/inspectors/method_inspector.rb +1 -1
- data/lib/boson/libraries/file_library.rb +31 -11
- data/lib/boson/libraries/gem_library.rb +6 -0
- data/lib/boson/libraries/module_library.rb +24 -4
- data/lib/boson/libraries/require_library.rb +8 -0
- data/lib/boson/library.rb +75 -35
- data/lib/boson/loader.rb +41 -12
- data/lib/boson/manager.rb +19 -14
- data/lib/boson/option_parser.rb +144 -107
- data/lib/boson/options.rb +127 -0
- data/lib/boson/repo.rb +25 -6
- data/lib/boson/runner.rb +2 -2
- data/lib/boson/runners/bin_runner.rb +25 -15
- data/lib/boson/scientist.rb +98 -28
- data/lib/boson/util.rb +20 -14
- data/lib/boson/view.rb +70 -15
- data/test/bin_runner_test.rb +27 -11
- data/test/file_library_test.rb +14 -1
- data/test/index_test.rb +2 -1
- data/test/loader_test.rb +84 -21
- data/test/manager_test.rb +1 -9
- data/test/method_inspector_test.rb +2 -2
- data/test/option_parser_test.rb +176 -20
- data/test/repo_test.rb +1 -0
- data/test/scientist_test.rb +38 -2
- data/test/test_helper.rb +6 -3
- data/test/view_test.rb +58 -0
- metadata +7 -6
- data/test/commands_test.rb +0 -51
@@ -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(
|
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
|
-
# [:
|
39
|
-
#
|
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.
|
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
|
-
|
52
|
-
|
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(
|
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
|
-
#
|
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
|
-
#
|
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=>:
|
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
|
-
|
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
|
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 += "\
|
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
|
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
|
-
|
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,
|
125
|
+
Boson.invoke :commands, :fields=>["name", "usage", "description"], :description=>false
|
116
126
|
end
|
117
127
|
end
|
118
128
|
#:startdoc:
|
data/lib/boson/scientist.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'shellwords'
|
2
2
|
module Boson
|
3
|
-
# Scientist redefines
|
4
|
-
#
|
5
|
-
#
|
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
|
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
|
-
|
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?)
|
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
|
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
|
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
|
170
|
-
@
|
229
|
+
def default_render_options
|
230
|
+
@default_render_options ||= RENDER_OPTIONS.merge Boson.repo.config[:render_options] || {}
|
171
231
|
end
|
172
232
|
|
173
|
-
def
|
174
|
-
(@
|
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)
|
236
|
+
if !v.is_a?(Hash) && !v.is_a?(Symbol)
|
177
237
|
@command.render_options[k] = {:default=>v}
|
178
238
|
end
|
179
239
|
}
|
180
|
-
|
181
|
-
opts
|
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
|
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 =
|
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? &&
|
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)
|