bosonson 0.304.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. data/CHANGELOG.rdoc +108 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.rdoc +181 -0
  4. data/bin/bss +6 -0
  5. data/bosonson.gemspec +24 -0
  6. data/deps.rip +2 -0
  7. data/lib/boson.rb +96 -0
  8. data/lib/boson/command.rb +196 -0
  9. data/lib/boson/commands.rb +7 -0
  10. data/lib/boson/commands/core.rb +77 -0
  11. data/lib/boson/commands/web_core.rb +153 -0
  12. data/lib/boson/index.rb +48 -0
  13. data/lib/boson/inspector.rb +120 -0
  14. data/lib/boson/inspectors/argument_inspector.rb +97 -0
  15. data/lib/boson/inspectors/comment_inspector.rb +100 -0
  16. data/lib/boson/inspectors/method_inspector.rb +98 -0
  17. data/lib/boson/libraries/file_library.rb +144 -0
  18. data/lib/boson/libraries/gem_library.rb +30 -0
  19. data/lib/boson/libraries/local_file_library.rb +30 -0
  20. data/lib/boson/libraries/module_library.rb +37 -0
  21. data/lib/boson/libraries/require_library.rb +23 -0
  22. data/lib/boson/library.rb +179 -0
  23. data/lib/boson/loader.rb +118 -0
  24. data/lib/boson/manager.rb +169 -0
  25. data/lib/boson/namespace.rb +31 -0
  26. data/lib/boson/option_command.rb +222 -0
  27. data/lib/boson/option_parser.rb +475 -0
  28. data/lib/boson/options.rb +146 -0
  29. data/lib/boson/pipe.rb +147 -0
  30. data/lib/boson/pipes.rb +75 -0
  31. data/lib/boson/repo.rb +107 -0
  32. data/lib/boson/repo_index.rb +124 -0
  33. data/lib/boson/runner.rb +81 -0
  34. data/lib/boson/runners/bin_runner.rb +208 -0
  35. data/lib/boson/runners/console_runner.rb +58 -0
  36. data/lib/boson/scientist.rb +182 -0
  37. data/lib/boson/util.rb +129 -0
  38. data/lib/boson/version.rb +3 -0
  39. data/lib/boson/view.rb +95 -0
  40. data/test/argument_inspector_test.rb +62 -0
  41. data/test/bin_runner_test.rb +223 -0
  42. data/test/command_test.rb +22 -0
  43. data/test/commands_test.rb +22 -0
  44. data/test/comment_inspector_test.rb +126 -0
  45. data/test/deps.rip +4 -0
  46. data/test/file_library_test.rb +42 -0
  47. data/test/loader_test.rb +235 -0
  48. data/test/manager_test.rb +114 -0
  49. data/test/method_inspector_test.rb +90 -0
  50. data/test/option_parser_test.rb +367 -0
  51. data/test/options_test.rb +189 -0
  52. data/test/pipes_test.rb +65 -0
  53. data/test/repo_index_test.rb +122 -0
  54. data/test/repo_test.rb +23 -0
  55. data/test/runner_test.rb +40 -0
  56. data/test/scientist_test.rb +341 -0
  57. data/test/test_helper.rb +130 -0
  58. data/test/util_test.rb +56 -0
  59. data/vendor/bundle/gems/bacon-bits-0.1.0/deps.rip +1 -0
  60. data/vendor/bundle/gems/hirb-0.6.0/test/deps.rip +4 -0
  61. metadata +217 -0
@@ -0,0 +1,182 @@
1
+ module Boson
2
+ # Scientist wraps around and redefines an object's method to give it the following features:
3
+ # * Methods can take shell command input with options or receive its normal arguments. See the Commandification
4
+ # section.
5
+ # * Methods have a slew of global options available. See OptionCommand for an explanation of basic global options.
6
+ # * Before a method returns its value, it pipes its return value through pipe commands if pipe options are specified. See Pipe.
7
+ # * Methods can have any number of optional views associated with them via global render options (see View). Views can be toggled
8
+ # on/off with the global option --render (see OptionCommand).
9
+ #
10
+ # The main methods Scientist provides are redefine_command() for redefining an object's method with a Command object
11
+ # and commandify() for redefining with a hash of method attributes. Note that for an object's method to be redefined correctly,
12
+ # its last argument _must_ expect a hash.
13
+ #
14
+ # === Commandification
15
+ # Take for example this basic method/command with an options definition:
16
+ # options :level=>:numeric, :verbose=>:boolean
17
+ # def foo(*args)
18
+ # args
19
+ # end
20
+ #
21
+ # When Scientist wraps around foo(), it can take arguments normally or as a shell command:
22
+ # foo 'one', 'two', :verbose=>true # normal call
23
+ # foo 'one two -v' # commandline call
24
+ #
25
+ # Both calls return: ['one', 'two', {:verbose=>true}]
26
+ #
27
+ # Non-string arguments can be passed as well:
28
+ # foo Object, 'two', :level=>1
29
+ # foo Object, 'two -l1'
30
+ #
31
+ # Both calls return: [Object, 'two', {:level=>1}]
32
+ module Scientist
33
+ extend self
34
+ # Handles all Scientist errors.
35
+ class Error < StandardError; end
36
+
37
+ attr_accessor :global_options, :rendered, :render
38
+ @no_option_commands ||= []
39
+ @option_commands ||= {}
40
+ @object_methods = {}
41
+
42
+ # Redefines an object's method with a Command of the same name.
43
+ def redefine_command(obj, command)
44
+ cmd_block = redefine_command_block(obj, command)
45
+ @no_option_commands << command if command.options.nil?
46
+ [command.name, command.alias].compact.each {|e|
47
+ obj.instance_eval("class<<self;self;end").send(:define_method, e, cmd_block)
48
+ }
49
+ rescue Error
50
+ $stderr.puts "Error: #{$!.message}"
51
+ end
52
+
53
+ # A wrapper around redefine_command that doesn't depend on a Command object. Rather you
54
+ # simply pass a hash of command attributes (see Command.new) or command methods and let OpenStruct mock a command.
55
+ # The only required attribute is :name, though to get any real use you should define :options and
56
+ # :arg_size (default is '*'). Example:
57
+ # >> def checkit(*args); args; end
58
+ # => nil
59
+ # >> Boson::Scientist.commandify(self, :name=>'checkit', :options=>{:verbose=>:boolean, :num=>:numeric})
60
+ # => ['checkit']
61
+ # # regular ruby method
62
+ # >> checkit 'one', 'two', :num=>13, :verbose=>true
63
+ # => ["one", "two", {:num=>13, :verbose=>true}]
64
+ # # commandline ruby method
65
+ # >> checkit 'one two -v -n=13'
66
+ # => ["one", "two", {:num=>13, :verbose=>true}]
67
+ def commandify(obj, hash)
68
+ raise ArgumentError, ":name required" unless hash[:name]
69
+ hash[:arg_size] ||= '*'
70
+ hash[:has_splat_args?] = true if hash[:arg_size] == '*'
71
+ fake_cmd = OpenStruct.new(hash)
72
+ fake_cmd.option_parser ||= OptionParser.new(fake_cmd.options || {})
73
+ redefine_command(obj, fake_cmd)
74
+ end
75
+
76
+ # The actual method which redefines a command's original method
77
+ def redefine_command_block(obj, command)
78
+ object_methods(obj)[command.name] ||= begin
79
+ obj.method(command.name)
80
+ rescue NameError
81
+ raise Error, "No method exists to redefine command '#{command.name}'."
82
+ end
83
+ lambda {|*args|
84
+ Scientist.translate_and_render(obj, command, args) {|args|
85
+ Scientist.object_methods(obj)[command.name].call(*args)
86
+ }
87
+ }
88
+ end
89
+
90
+ #:stopdoc:
91
+ def object_methods(obj)
92
+ @object_methods[obj] ||= {}
93
+ end
94
+
95
+ def option_command(cmd=@command)
96
+ @option_commands[cmd] ||= OptionCommand.new(cmd)
97
+ end
98
+
99
+ def call_original_command(args, &block)
100
+ block.call(args)
101
+ end
102
+
103
+ def translate_and_render(obj, command, args, &block)
104
+ @global_options, @command, original_args = {}, command, args.dup
105
+ @args = translate_args(obj, args)
106
+ return run_help_option if @global_options[:help]
107
+ run_pretend_option(@args)
108
+ render_or_raw call_original_command(@args, &block) unless @global_options[:pretend]
109
+ rescue OptionCommand::CommandArgumentError
110
+ run_pretend_option(@args ||= [])
111
+ return if !@global_options[:pretend] && run_verbose_help(option_command, original_args)
112
+ raise unless @global_options[:pretend]
113
+ rescue OptionParser::Error, Error
114
+ raise if Runner.in_shell?
115
+ message = @global_options[:verbose] ? "#{$!}\n#{$!.backtrace.inspect}" : $!.message
116
+ $stderr.puts "Error: " + message
117
+ end
118
+
119
+ def translate_args(obj, args)
120
+ option_command.modify_args(args)
121
+ @global_options, @current_options, args = option_command.parse(args)
122
+ return if @global_options[:help]
123
+
124
+ (@global_options[:delete_options] || []).map {|e|
125
+ @global_options.keys.map {|k| k.to_s }.grep(/^#{e}/)
126
+ }.flatten.each {|e| @global_options.delete(e.to_sym) }
127
+
128
+ if @current_options
129
+ option_command.add_default_args(args, obj)
130
+ return args if @no_option_commands.include?(@command)
131
+ args << @current_options
132
+ option_command.check_argument_size(args)
133
+ end
134
+ args
135
+ end
136
+
137
+ def run_verbose_help(option_command, original_args)
138
+ global_opts = option_command.parse_global_options(original_args)
139
+ if global_opts[:help] && global_opts[:verbose]
140
+ @global_options = global_opts
141
+ run_help_option
142
+ return true
143
+ end
144
+ false
145
+ end
146
+
147
+ def run_help_option
148
+ opts = @global_options[:verbose] ? ['--verbose'] : []
149
+ opts << "--render_options=#{@global_options[:usage_options]}" if @global_options[:usage_options]
150
+ Boson.invoke :usage, @command.full_name + " " + opts.join(' ')
151
+ end
152
+
153
+ def run_pretend_option(args)
154
+ if @global_options[:verbose] || @global_options[:pretend]
155
+ puts "Arguments: #{args.inspect}", "Global options: #{@global_options.inspect}"
156
+ end
157
+ @rendered = true if @global_options[:pretend]
158
+ end
159
+
160
+ def render_or_raw(result)
161
+ if (@rendered = can_render?)
162
+ if @global_options.key?(:class) || @global_options.key?(:method)
163
+ result = Pipe.scientist_process(result, @global_options, :config=>@command.config, :args=>@args, :options=>@current_options)
164
+ end
165
+ View.render(result, OptionCommand.delete_non_render_options(@global_options.dup), false)
166
+ else
167
+ Pipe.scientist_process(result, @global_options, :config=>@command.config, :args=>@args, :options=>@current_options)
168
+ end
169
+ rescue StandardError
170
+ raise Error, $!.message, $!.backtrace
171
+ end
172
+
173
+ def can_render?
174
+ render.nil? ? command_renders? : render
175
+ end
176
+
177
+ def command_renders?
178
+ (!!@command.render_options ^ @global_options[:render]) && !Pipe.any_no_render_pipes?(@global_options)
179
+ end
180
+ #:startdoc:
181
+ end
182
+ end
@@ -0,0 +1,129 @@
1
+ module Boson
2
+ # Collection of utility methods used throughout Boson.
3
+ module Util
4
+ extend self
5
+ # From Rails ActiveSupport, converts a camelcased string to an underscored string:
6
+ # 'Boson::MethodInspector' -> 'boson/method_inspector'
7
+ def underscore(camel_cased_word)
8
+ camel_cased_word.to_s.gsub(/::/, '/').
9
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
10
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
11
+ tr("-", "_").
12
+ downcase
13
+ end
14
+
15
+ # From Rails ActiveSupport, does the reverse of underscore:
16
+ # 'boson/method_inspector' -> 'Boson::MethodInspector'
17
+ def camelize(string)
18
+ Hirb::Util.camelize(string)
19
+ end
20
+
21
+ # Converts a module/class string to the actual constant.
22
+ # Returns nil if not found.
23
+ def constantize(string)
24
+ any_const_get(camelize(string))
25
+ end
26
+
27
+ # Returns a constant like const_get() no matter what namespace it's nested in.
28
+ # Returns nil if the constant is not found.
29
+ def any_const_get(name)
30
+ Hirb::Util.any_const_get(name)
31
+ end
32
+
33
+ # Detects new object/kernel methods, gems and modules created within a block.
34
+ # Returns a hash of what's detected.
35
+ # Valid options and possible returned keys are :methods, :object_methods, :modules, :gems.
36
+ def detect(options={}, &block)
37
+ options = {:methods=>true, :object_methods=>true}.merge!(options)
38
+ original_gems = Object.const_defined?(:Gem) ? Gem.loaded_specs.keys : []
39
+ original_object_methods = Object.instance_methods
40
+ original_instance_methods = class << Boson.main_object; instance_methods end
41
+ original_modules = modules if options[:modules]
42
+ block.call
43
+ detected = {}
44
+ detected[:methods] = options[:methods] ? (class << Boson.main_object; instance_methods end -
45
+ original_instance_methods) : []
46
+ detected[:methods] -= (Object.instance_methods - original_object_methods) unless options[:object_methods]
47
+ detected[:gems] = Gem.loaded_specs.keys - original_gems if Object.const_defined? :Gem
48
+ detected[:modules] = modules - original_modules if options[:modules]
49
+ detected
50
+ end
51
+
52
+ # Safely calls require, returning false if LoadError occurs.
53
+ def safe_require(lib)
54
+ begin
55
+ require lib
56
+ true
57
+ rescue LoadError
58
+ false
59
+ end
60
+ end
61
+
62
+ # Returns all modules that currently exist.
63
+ def modules
64
+ all_modules = []
65
+ ObjectSpace.each_object(Module) {|e| all_modules << e}
66
+ all_modules
67
+ end
68
+
69
+ # Creates a module under a given base module and possible name. If the module already exists or conflicts
70
+ # per top_level_class_conflict, it attempts to create one with a number appended to the name.
71
+ def create_module(base_module, name)
72
+ desired_class = camelize(name)
73
+ possible_suffixes = [''] + %w{1 2 3 4 5 6 7 8 9 10}
74
+ if (suffix = possible_suffixes.find {|e| !base_module.const_defined?(desired_class+e) &&
75
+ !top_level_class_conflict(base_module, "#{base_module}::#{desired_class}#{e}") })
76
+ base_module.const_set(desired_class+suffix, Module.new)
77
+ end
78
+ end
79
+
80
+ # Behaves just like the unix which command, returning the full path to an executable based on ENV['PATH'].
81
+ def which(command)
82
+ ENV['PATH'].split(File::PATH_SEPARATOR).map {|e| File.join(e, command) }.find {|e| File.exists?(e) }
83
+ end
84
+
85
+ # Deep copies any object if it can be marshaled. Useful for deep hashes.
86
+ def deep_copy(obj)
87
+ Marshal::load(Marshal::dump(obj))
88
+ end
89
+
90
+ # Recursively merge hash1 with hash2.
91
+ def recursive_hash_merge(hash1, hash2)
92
+ hash1.merge(hash2) {|k,o,n| (o.is_a?(Hash)) ? recursive_hash_merge(o,n) : n}
93
+ end
94
+
95
+ # From Rubygems, determine a user's home.
96
+ def find_home
97
+ Hirb::Util.find_home
98
+ end
99
+
100
+ # Returns name of top level class that conflicts if it exists. For example, for base module Boson::Commands,
101
+ # Boson::Commands::Alias conflicts with Alias if Alias exists.
102
+ def top_level_class_conflict(base_module, conflicting_module)
103
+ (conflicting_module =~ /^#{base_module}.*::([^:]+)/) && Object.const_defined?($1) && $1
104
+ end
105
+
106
+ # Splits array into array of arrays with given element
107
+ def split_array_by(arr, divider)
108
+ arr.inject([[]]) {|results, element|
109
+ (divider == element) ? (results << []) : (results.last << element)
110
+ results
111
+ }
112
+ end
113
+
114
+ # Regular expression search of a list with underscore anchoring of words.
115
+ # For example 'some_dang_long_word' can be specified as 's_d_l_w'.
116
+ def underscore_search(input, list, first_match=false)
117
+ meth = first_match ? :find : :select
118
+ return (first_match ? input : [input]) if list.include?(input)
119
+ input = input.to_s
120
+ if input.include?("_")
121
+ underscore_regex = input.split('_').map {|e| Regexp.escape(e) }.join("([^_]+)?_")
122
+ list.send(meth) {|e| e.to_s =~ /^#{underscore_regex}/ }
123
+ else
124
+ escaped_input = Regexp.escape(input)
125
+ list.send(meth) {|e| e.to_s =~ /^#{escaped_input}/ }
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,3 @@
1
+ module Boson
2
+ VERSION = '0.304.1'
3
+ end
@@ -0,0 +1,95 @@
1
+ module Boson
2
+ # This module generates views for a command by handing it to {Hirb}[http://tagaholic.me/hirb/]. Since Hirb can be customized
3
+ # to generate any view, commands can have any views associated with them!
4
+ #
5
+ # === Views with Render Options
6
+ # To pass rendering options to a Hirb helper as command options, a command has to define the options with
7
+ # the render_options method attribute:
8
+ #
9
+ # # @render_options :fields=>[:a,:b]
10
+ # def list(options={})
11
+ # [{:a=>1, :b=>2}, {:a=>10,:b=>11}]
12
+ # end
13
+ #
14
+ # # To see that the render_options method attribute actually passes the :fields option by default:
15
+ # >> list '-p' # or list '--pretend'
16
+ # Arguments: []
17
+ # Global options: {:pretend=>true, :fields=>[:a, :b]}
18
+ #
19
+ # >> list
20
+ # +----+----+
21
+ # | a | b |
22
+ # +----+----+
23
+ # | 1 | 2 |
24
+ # | 10 | 11 |
25
+ # +----+----+
26
+ # 2 rows in set
27
+ #
28
+ # # To create a vertical table, we can pass --vertical, one of the default global render options.
29
+ # >> list '-V' # or list '--vertical'
30
+ # *** 1. row ***
31
+ # a: 1
32
+ # b: 2
33
+ # ...
34
+ #
35
+ # # To get the original return value use the global option --render
36
+ # >> list '-r' # or list '--render'
37
+ # => [{:a=>1, :b=>2}, {:a=>10,:b=>11}]
38
+ #
39
+ # === Boson and Hirb
40
+ # Since Boson uses {Hirb's auto table helper}[http://tagaholic.me/hirb/doc/classes/Hirb/Helpers/AutoTable.html]
41
+ # by default, you may want to read up on its many options. To use any of them in commands, define them locally
42
+ # with render_options or globally by adding them under the :render_options key of the main config.
43
+ # What if you want to use your own helper class? No problem. Simply pass it with the global :class option.
44
+ #
45
+ # When using the default helper, one of the most important options to define is :fields. Aside from controlling what fields
46
+ # are displayed, it's used to set :values option attributes for related options i.e. :sort and :query. This provides handy option
47
+ # value aliasing via OptionParser. If you don't set :fields, Boson will try to set its :values with field-related options i.e.
48
+ # :change_fields, :filters and :headers.
49
+ module View
50
+ extend self
51
+
52
+ # Enables hirb and reads a config file from the main repo's config/hirb.yml.
53
+ def enable
54
+ unless @enabled
55
+ Hirb::View.enable(:config_file=>File.join(Boson.repo.config_dir, 'hirb.yml'))
56
+ Hirb::Helpers::Table.filter_any = true
57
+ end
58
+ @enabled = true
59
+ end
60
+
61
+ # Renders any object via Hirb. Options are passed directly to
62
+ # {Hirb::Console.render_output}[http://tagaholic.me/hirb/doc/classes/Hirb/Console.html#M000011].
63
+ def render(object, options={}, return_obj=false)
64
+ if options[:inspect]
65
+ puts(object.inspect)
66
+ else
67
+ render_object(object, options, return_obj) unless silent_object?(object)
68
+ end
69
+ end
70
+
71
+ #:stopdoc:
72
+ def class_config(klass)
73
+ opts = (Hirb::View.formatter_config[klass] || {}).dup
74
+ opts.delete(:ancestor)
75
+ opts.merge!((opts.delete(:options) || {}).dup)
76
+ OptionParser.make_mergeable!(opts)
77
+ opts
78
+ end
79
+
80
+ def toggle_pager
81
+ Hirb::View.toggle_pager
82
+ end
83
+
84
+ def silent_object?(obj)
85
+ [nil,false,true].include?(obj)
86
+ end
87
+
88
+ def render_object(object, options={}, return_obj=false)
89
+ options[:class] ||= :auto_table
90
+ render_result = Hirb::Console.render_output(object, options)
91
+ return_obj ? object : render_result
92
+ end
93
+ #:startdoc:
94
+ end
95
+ end
@@ -0,0 +1,62 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe "scrape_with_text" do
4
+ def args_from(file_string)
5
+ ArgumentInspector.scrape_with_text(file_string, "blah")
6
+ end
7
+
8
+ it "parses arguments of class method" do
9
+ args_from(" def YAML.blah( filepath )\n").should == [['filepath']]
10
+ end
11
+
12
+ it "parses arguments with no spacing" do
13
+ args_from("def bong; end\ndef blah(arg1,arg2='val2')\nend").should == [["arg1"], ['arg2', "'val2'"]]
14
+ end
15
+
16
+ it "parses arguments with spacing" do
17
+ args_from("\t def blah( arg1=val1, arg2 = val2)").should == [["arg1","val1"], ["arg2", "val2"]]
18
+ end
19
+
20
+ it "parses arguments without parenthesis" do
21
+ args_from(" def blah arg1, arg2, arg3={}").should == [['arg1'], ['arg2'], ['arg3','{}']]
22
+ end
23
+ end
24
+
25
+ describe "scrape_with_eval" do
26
+ def args_from(string)
27
+ # methods need options to have their args parsed with ArgumentInspector
28
+ string.gsub!(/(def blah)/, 'options :a=>1; \1')
29
+ Inspector.enable
30
+ ::Boson::Commands::Aaa.module_eval(string)
31
+ Inspector.disable
32
+ MethodInspector.store[:args]['blah']
33
+ end
34
+
35
+ before_all { eval "module ::Boson::Commands::Aaa; end"; }
36
+ before { MethodInspector.mod_store[::Boson::Commands::Aaa] = {} }
37
+
38
+ it "determines arguments with literal defaults" do
39
+ args_from("def blah(arg1,arg2='val2'); end").should == [['arg1'], ['arg2','val2']]
40
+ end
41
+
42
+ it "determines splat arguments" do
43
+ args_from("def blah(arg1, *args); end").should == [['arg1'], ["*args"]]
44
+ end
45
+
46
+ it "determines arguments with local values before a method" do
47
+ body = "AWESOME='awesome'; def sweet; 'ok'; end; def blah(arg1=AWESOME, arg2=sweet); end"
48
+ args_from(body).should == [['arg1', 'awesome'], ['arg2', 'ok']]
49
+ end
50
+
51
+ it "doesn't get arguments with local values after a method" do
52
+ args_from("def blah(arg1=nope) end; def nope; 'nope'; end").should == nil
53
+ end
54
+
55
+ it "doesn't determine arguments of a private method" do
56
+ args_from("private; def blah(arg1,arg2); end").should == nil
57
+ end
58
+
59
+ it "doesn't determine arguments if an error occurs" do
60
+ args_from("def blah(arg1,arg2=raise); end").should == nil
61
+ end
62
+ end