boson 0.4.0 → 1.0.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.
Files changed (64) hide show
  1. data/.gemspec +6 -7
  2. data/.rspec +2 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG.rdoc +1 -1
  5. data/README.md +144 -0
  6. data/README.rdoc +2 -2
  7. data/Upgrading.md +23 -0
  8. data/bin/boson +2 -2
  9. data/lib/boson.rb +44 -52
  10. data/lib/boson/bare_runner.rb +83 -0
  11. data/lib/boson/bin_runner.rb +114 -0
  12. data/lib/boson/command.rb +92 -132
  13. data/lib/boson/inspector.rb +49 -48
  14. data/lib/boson/library.rb +71 -120
  15. data/lib/boson/loader.rb +73 -84
  16. data/lib/boson/manager.rb +131 -135
  17. data/lib/boson/method_inspector.rb +112 -0
  18. data/lib/boson/option_command.rb +71 -154
  19. data/lib/boson/option_parser.rb +178 -173
  20. data/lib/boson/options.rb +46 -32
  21. data/lib/boson/runner.rb +58 -66
  22. data/lib/boson/runner_library.rb +31 -0
  23. data/lib/boson/scientist.rb +48 -81
  24. data/lib/boson/util.rb +46 -61
  25. data/lib/boson/version.rb +1 -1
  26. data/test/bin_runner_test.rb +53 -191
  27. data/test/command_test.rb +5 -9
  28. data/test/deps.rip +2 -2
  29. data/test/loader_test.rb +18 -216
  30. data/test/manager_test.rb +69 -79
  31. data/test/method_inspector_test.rb +12 -36
  32. data/test/option_parser_test.rb +45 -32
  33. data/test/runner_library_test.rb +10 -0
  34. data/test/runner_test.rb +158 -28
  35. data/test/scientist_test.rb +9 -147
  36. data/test/test_helper.rb +87 -52
  37. metadata +30 -72
  38. data/deps.rip +0 -2
  39. data/lib/boson/commands.rb +0 -7
  40. data/lib/boson/commands/core.rb +0 -77
  41. data/lib/boson/commands/web_core.rb +0 -153
  42. data/lib/boson/index.rb +0 -48
  43. data/lib/boson/inspectors/argument_inspector.rb +0 -97
  44. data/lib/boson/inspectors/comment_inspector.rb +0 -100
  45. data/lib/boson/inspectors/method_inspector.rb +0 -98
  46. data/lib/boson/libraries/file_library.rb +0 -144
  47. data/lib/boson/libraries/gem_library.rb +0 -30
  48. data/lib/boson/libraries/local_file_library.rb +0 -30
  49. data/lib/boson/libraries/module_library.rb +0 -37
  50. data/lib/boson/libraries/require_library.rb +0 -23
  51. data/lib/boson/namespace.rb +0 -31
  52. data/lib/boson/pipe.rb +0 -147
  53. data/lib/boson/pipes.rb +0 -75
  54. data/lib/boson/repo.rb +0 -107
  55. data/lib/boson/runners/bin_runner.rb +0 -208
  56. data/lib/boson/runners/console_runner.rb +0 -58
  57. data/lib/boson/view.rb +0 -95
  58. data/test/argument_inspector_test.rb +0 -62
  59. data/test/commands_test.rb +0 -22
  60. data/test/comment_inspector_test.rb +0 -126
  61. data/test/file_library_test.rb +0 -42
  62. data/test/pipes_test.rb +0 -65
  63. data/test/repo_index_test.rb +0 -122
  64. data/test/repo_test.rb +0 -23
data/deps.rip DELETED
@@ -1,2 +0,0 @@
1
- hirb >=0.5.0
2
- alias >=0.2.2
@@ -1,7 +0,0 @@
1
- # Module under which most library modules are evaluated.
2
- module Boson::Commands
3
- # Used for defining namespaces.
4
- module Namespace; end
5
- end
6
- require 'boson/commands/core'
7
- require 'boson/commands/web_core'
@@ -1,77 +0,0 @@
1
- module Boson::Commands::Core #:nodoc:
2
- extend self
3
-
4
- def config
5
- command_attributes = Boson::Command::ATTRIBUTES + [:usage, :full_name, :render_options]
6
- library_attributes = Boson::Library::ATTRIBUTES + [:library_type]
7
-
8
- commands = {
9
- 'render'=>{:desc=>"Render any object using Hirb"},
10
- 'menu'=>{:desc=>"Provide a menu to multi-select elements from a given array"},
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]} } },
15
- 'commands'=>{
16
- :desc=>"List or search commands. Query must come before any options.", :default_option=>'query',
17
- :options=>{ :index=>{:type=>:boolean, :desc=>"Searches index"},
18
- :local=>{:type=>:boolean, :desc=>"Local commands only" } },
19
- :render_options=>{
20
- [:headers,:H]=>{:default=>{:desc=>'description'}},
21
- :query=>{:keys=>command_attributes, :default_keys=>'full_name'},
22
- :fields=>{:default=>[:full_name, :lib, :alias, :usage, :desc], :values=>command_attributes, :enum=>false},
23
- :filters=>{:default=>{:render_options=>:inspect, :options=>:inspect, :args=>:inspect, :config=>:inspect}}
24
- }
25
- },
26
- 'libraries'=>{
27
- :desc=>"List or search libraries. Query must come before any options.", :default_option=>'query',
28
- :options=>{ :index=>{:type=>:boolean, :desc=>"Searches index"},
29
- :local=>{:type=>:boolean, :desc=>"Local libraries only" } },
30
- :render_options=>{
31
- :query=>{:keys=>library_attributes, :default_keys=>'name'},
32
- :fields=>{:default=>[:name, :commands, :gems, :library_type], :values=>library_attributes, :enum=>false},
33
- :filters=>{:default=>{:gems=>[:join, ','],:commands=>:size}, :desc=>"Filters to apply to library fields" }}
34
- },
35
- 'load_library'=>{:desc=>"Load a library", :options=>{[:verbose,:V]=>true}}
36
- }
37
-
38
- {:namespace=>false, :library_file=>File.expand_path(__FILE__), :commands=>commands}
39
- end
40
-
41
- def commands(options={})
42
- cmds = options[:index] ? (Boson::Index.read || true) && Boson::Index.commands : Boson.commands
43
- options[:local] ? cmds.select {|e| e.library && e.library.local? } : cmds
44
- end
45
-
46
- def libraries(options={})
47
- libs = options[:index] ? (Boson::Index.read || true) && Boson::Index.libraries : Boson.libraries
48
- options[:local] ? libs.select {|e| e.local? } : libs
49
- end
50
-
51
- def load_library(library, options={})
52
- Boson::Manager.load(library, options)
53
- end
54
-
55
- def render(object, options={})
56
- Boson::View.render(object, options)
57
- end
58
-
59
- def menu(arr, options={}, &block)
60
- Hirb::Console.format_output(arr, options.merge(:class=>"Hirb::Menu"), &block)
61
- end
62
-
63
- def usage(command, options={})
64
- puts Boson::Command.usage(command)
65
-
66
- if (cmd = Boson::Command.find(command))
67
- if cmd.options && !cmd.options.empty?
68
- puts "\nLOCAL OPTIONS"
69
- cmd.option_parser.print_usage_table options[:render_options].dup.merge(:local=>true)
70
- end
71
- if options[:verbose] && cmd.render_option_parser
72
- puts "\nGLOBAL OPTIONS"
73
- cmd.render_option_parser.print_usage_table options[:render_options].dup
74
- end
75
- end
76
- end
77
- end
@@ -1,153 +0,0 @@
1
- module Boson::Commands::WebCore
2
- extend self
3
-
4
- def config #:nodoc:
5
- commands = {
6
- 'get'=>{ :desc=>"Gets the body of a url", :args=>[['url'],['options', {}]]},
7
- 'post'=>{ :desc=>'Posts to a url', :args=>[['url'],['options', {}]]},
8
- 'build_url'=>{ :desc=>"Builds a url, escaping the given params", :args=>[['url'],['params']]},
9
- 'browser'=>{ :desc=>"Opens urls in a browser on a Mac"},
10
- 'install'=>{ :desc=>"Installs a library by url. Library should then be loaded with load_library.",
11
- :args=>[['url'],['options', {}]],
12
- :options=> { :name=>{:type=>:string, :desc=>"Library name to save to"},
13
- :force=>{:type=>:boolean, :desc=>'Overwrites an existing library'},
14
- :default=>{:type=>:boolean, :desc=>'Adds library as a default library to main config file'},
15
- :module_wrap=>{:type=>:boolean, :desc=>"Wraps a module around install using library name"},
16
- :method_wrap=>{:type=>:boolean, :desc=>"Wraps a method and module around installed library using library name"}}
17
- }
18
- }
19
-
20
- {:library_file=>File.expand_path(__FILE__), :commands=>commands, :namespace=>false}
21
- end
22
-
23
- # Requires libraries only once before defining method with given block
24
- def self.def_which_requires(meth, *libs, &block)
25
- define_method(meth) do |*args|
26
- libs.each {|e| require e }
27
- define_method(meth, block).call(*args)
28
- end
29
- end
30
-
31
- def_which_requires(:get, 'net/https') do |*args|
32
- url, options = args[0], args[1] || {}
33
- url = build_url(url, options[:params]) if options[:params]
34
- Get.new(url).request(options)
35
- end
36
-
37
- def_which_requires(:build_url, 'cgi') do |url, params|
38
- url + (url[/\?/] ? '&' : "?") + params.map {|k,v|
39
- v = v.is_a?(Array) ? v.join(' ') : v.to_s
40
- "#{k}=#{CGI.escape(v)}"
41
- }.join("&")
42
- end
43
-
44
- def_which_requires(:post, 'uri', 'net/http') do |*args|
45
- url, options = args[0], args[1] || {}
46
- (res = Net::HTTP.post_form(URI.parse(url), options)) && res.body
47
- end
48
-
49
- def install(url, options={}) #:nodoc:
50
- options[:name] ||= strip_name_from_url(url)
51
- return puts("Please give a library name for this url.") if options[:name].empty?
52
- filename = File.join ::Boson.repo.commands_dir, "#{options[:name]}.rb"
53
- return puts("Library name #{options[:name]} already exists. Try a different name.") if File.exists?(filename) && !options[:force]
54
-
55
- file_string = get(url) or raise "Unable to fetch url"
56
- file_string = "# Originally from #{url}\n"+file_string
57
- file_string = wrap_install(file_string, options) if options[:method_wrap] || options[:module_wrap]
58
-
59
- File.open(filename, 'w') {|f| f.write file_string }
60
- Boson.repo.update_config {|c| (c[:defaults] ||= []) << options[:name] } if options[:default]
61
- puts "Saved to #{filename}."
62
- end
63
-
64
- # non-mac users should override this with the launchy gem
65
- def browser(*urls)
66
- system('open', *urls)
67
- end
68
-
69
- private
70
- def wrap_install(file_string, options)
71
- indent = " "
72
- unless (mod_name = ::Boson::Util.camelize(options[:name]))
73
- return puts("Can't wrap install with name #{options[:name]}")
74
- end
75
-
76
- file_string.gsub!(/(^)/,'\1'+indent)
77
- file_string = "def #{options[:name]}\n#{file_string}\nend".gsub(/(^)/,'\1'+indent) if options[:method_wrap]
78
- "module #{mod_name}\n#{file_string}\nend"
79
- end
80
-
81
- def strip_name_from_url(url)
82
- url[/\/([^\/.]+)(\.[a-z]+)?$/, 1].to_s.gsub('-', '_').gsub(/[^a-zA-Z_]/, '')
83
- end
84
-
85
- # Used by the get command to make get requests and optionally parse json and yaml.
86
- # Ruby 1.8.x is dependent on json gem for parsing json.
87
- # See Get.request for options a request can take.
88
- class Get
89
- FORMAT_HEADERS = {
90
- :json=>%w{application/json text/json application/javascript text/javascript},
91
- :yaml=>%w{application/x-yaml text/yaml}
92
- } #:nodoc:
93
-
94
- def initialize(url, options={})
95
- @url, @options = url, options
96
- end
97
-
98
- # Returns the response body string or a parsed data structure. Returns nil if request fails. By default expects response
99
- # to be 200.
100
- # ==== Options:
101
- # [:any_response] Returns body string for any response code. Default is false.
102
- # [:parse] Parse the body into either json or yaml. Expects a valid format or if true autodetects one.
103
- # Default is false.
104
- # [:raise_error] Raises any original errors when parsing or fetching url instead of handling errors silently.
105
- def request(options={})
106
- @options.merge! options
107
- body = get_body
108
- body && @options[:parse] ? parse_body(body) : body
109
- end
110
-
111
- private
112
- # Returns body string if successful or nil if not.
113
- def get_body
114
- uri = URI.parse(@url)
115
- @response = get_response(uri)
116
- (@options[:any_response] || @response.code == '200') ? @response.body : nil
117
- rescue
118
- @options[:raise_error] ? raise : puts("Error: GET '#{@url}' -> #{$!.class}: #{$!.message}")
119
- end
120
-
121
- def get_response(uri)
122
- net = Net::HTTP.new(uri.host, uri.port)
123
- net.verify_mode = OpenSSL::SSL::VERIFY_NONE if uri.scheme == 'https'
124
- net.use_ssl = true if uri.scheme == 'https'
125
- net.start {|http| http.request_get(uri.request_uri) }
126
- end
127
-
128
- # Returns nil if dependencies or parsing fails
129
- def parse_body(body)
130
- format = determine_format(@options[:parse])
131
- case format
132
- when :json
133
- unless ::Boson::Util.safe_require 'json'
134
- return puts("Install the json gem to parse json: sudo gem install json")
135
- end
136
- JSON.parse body
137
- when :yaml
138
- YAML::load body
139
- else
140
- puts "Can't parse this format."
141
- end
142
- rescue
143
- @options[:raise_error] ? raise : puts("Error while parsing #{format} response of '#{@url}': #{$!.class}")
144
- end
145
-
146
- def determine_format(format)
147
- return format.to_sym if %w{json yaml}.include?(format.to_s)
148
- return :json if FORMAT_HEADERS[:json].include?(@response.content_type)
149
- return :yaml if FORMAT_HEADERS[:yaml].include?(@response.content_type)
150
- nil
151
- end
152
- end
153
- end
data/lib/boson/index.rb DELETED
@@ -1,48 +0,0 @@
1
- module Boson
2
- # This class manages indexing/storing all commands and libraries. See RepoIndex for details
3
- # about the index created for each Repo.
4
- module Index
5
- extend self
6
- # Array of indexes, one per repo in Boson.repos.
7
- def indexes
8
- @indexes ||= Boson.repos.map {|e| RepoIndex.new(e) }
9
- end
10
-
11
- # Updates all repo indexes.
12
- def update(options={})
13
- indexes.each {|e| e.update(options) }
14
- end
15
-
16
- #:stopdoc:
17
- def read
18
- indexes.each {|e| e.read }
19
- end
20
-
21
- def find_library(command, object=false)
22
- indexes.each {|e|
23
- (lib = e.find_library(command, object)) and return lib
24
- }
25
- nil
26
- end
27
-
28
- def find_command(command)
29
- indexes.each {|e|
30
- (cmd = Command.find(command, e.commands)) and return(cmd)
31
- }
32
- nil
33
- end
34
-
35
- def commands
36
- indexes.map {|e| e.commands}.flatten
37
- end
38
-
39
- def libraries
40
- indexes.map {|e| e.libraries}.flatten
41
- end
42
-
43
- def all_main_methods
44
- indexes.map {|e| e.all_main_methods}.flatten
45
- end
46
- #:startdoc:
47
- end
48
- end
@@ -1,97 +0,0 @@
1
- # Extracts arguments and their default values from methods either by
2
- # by scraping a method's text or with method_added and brute force eval (thanks to
3
- # {eigenclass}[http://eigenclass.org/hiki/method+arguments+via+introspection]).
4
- module Boson::ArgumentInspector
5
- extend self
6
- # Returns same argument arrays as scrape_with_eval but argument defaults haven't been evaluated.
7
- def scrape_with_text(file_string, meth)
8
- tabspace = "[ \t]"
9
- if match = /^#{tabspace}*def#{tabspace}+(?:\w+\.)?#{Regexp.quote(meth)}#{tabspace}*($|(?:\(|\s+)([^\n\)]+)\s*\)?\s*$)/.match(file_string)
10
- (match.to_a[2] || '').strip.split(/\s*,\s*/).map {|e| e.split(/\s*=\s*/)}
11
- end
12
- end
13
-
14
- # Max number of arguments extracted per method with scrape_with_eval
15
- MAX_ARGS = 10
16
- # Scrapes non-private methods for argument names and default values.
17
- # Returns arguments as array of argument arrays with optional default value as a second element.
18
- # ====Examples:
19
- # def meth1(arg1, arg2='val', options={}) -> [['arg1'], ['arg2', 'val'], ['options', {}]]
20
- # def meth2(*args) -> [['*args']]
21
- def scrape_with_eval(meth, klass, object)
22
- unless %w[initialize].include?(meth.to_s)
23
- return if class << object; private_instance_methods(true).map {|e| e.to_s } end.include?(meth.to_s)
24
- end
25
- params, values, arity, num_args = trace_method_args(meth, klass, object)
26
- return if local_variables == params # nothing new found
27
- format_arguments(params, values, arity, num_args)
28
- rescue Exception
29
- print_debug_message(klass, meth) if Boson::Runner.debug
30
- ensure
31
- set_trace_func(nil)
32
- end
33
-
34
- def print_debug_message(klass, meth) #:nodoc:
35
- warn "DEBUG: Error while scraping arguments from #{klass.to_s[/\w+$/]}##{meth}: #{$!.message}"
36
- end
37
-
38
- # process params + values to return array of argument arrays
39
- def format_arguments(params, values, arity, num_args) #:nodoc:
40
- params ||= []
41
- params = params[0,num_args]
42
- params.inject([[], 0]) do |(a, i), x|
43
- if Array === values[i]
44
- [a << ["*#{x}"], i+1]
45
- else
46
- if arity < 0 && i >= arity.abs - 1
47
- [a << [x.to_s, values[i]], i + 1]
48
- else
49
- [a << [x.to_s], i+1]
50
- end
51
- end
52
- end.first
53
- end
54
-
55
- def trace_method_args(meth, klass, object) #:nodoc:
56
- file = line = params = values = nil
57
- arity = klass.instance_method(meth).arity
58
- set_trace_func lambda{|event, file, line, id, binding, classname|
59
- begin
60
- if event[/call/] && classname == klass && id == meth
61
- params = eval("local_variables", binding)
62
- values = eval("local_variables.map{|x| eval(x.to_s)}", binding)
63
- throw :done
64
- end
65
- rescue Exception
66
- print_debug_message(klass, meth) if Boson::Runner.debug
67
- end
68
- }
69
- if arity >= 0
70
- num_args = arity
71
- catch(:done){ object.send(meth, *(0...arity)) }
72
- else
73
- num_args = 0
74
- # determine number of args (including splat & block)
75
- MAX_ARGS.downto(arity.abs - 1) do |i|
76
- catch(:done) do
77
- begin
78
- object.send(meth, *(0...i))
79
- rescue Exception
80
- end
81
- end
82
- # all nils if there's no splat and we gave too many args
83
- next if !values || values.compact.empty?
84
- k = nil
85
- values.each_with_index{|x,j| break (k = j) if Array === x}
86
- num_args = k ? k+1 : i
87
- break
88
- end
89
- args = (0...arity.abs-1).to_a
90
- catch(:done) do
91
- args.empty? ? object.send(meth) : object.send(meth, *args)
92
- end
93
- end
94
- set_trace_func(nil)
95
- return [params, values, arity, num_args]
96
- end
97
- end
@@ -1,100 +0,0 @@
1
- module Boson
2
- # Scrapes comments right before a method for its attributes. Method attributes must begin with '@' i.e.:
3
- # # @desc Does foo
4
- # # @options :verbose=>true
5
- # def foo(options={})
6
- #
7
- # Some rules about these attributes:
8
- # * Attribute definitions can span multiple lines. When a new attribute starts a line or the comments end,
9
- # then a definition ends.
10
- # * If no @desc is found in the comment block, then the first comment line directly above the method
11
- # is assumed to be the value for @desc. This means that no multi-line attribute definitions can occur
12
- # without a description since the last line is assumed to be a description.
13
- # * options, option, config and render_options attributes can take any valid ruby since they're evaled in
14
- # their module's context.
15
- # * desc attribute is not evaled and is simply text to be set as a string.
16
- #
17
- # This module was inspired by
18
- # {pragdave}[http://github.com/pragdavespc/rake/commit/45231ac094854da9f4f2ac93465ed9b9ca67b2da].
19
- module CommentInspector
20
- extend self
21
- EVAL_ATTRIBUTES = [:options, :render_options, :config]
22
-
23
- # Given a method's file string, line number and defining module, returns a hash
24
- # of attributes defined for that method.
25
- def scrape(file_string, line, mod, attribute=nil)
26
- hash = scrape_file(file_string, line) || {}
27
- options = (arr = hash.delete(:option)) ? parse_option_comments(arr, mod) : {}
28
- hash.select {|k,v| v && (attribute.nil? || attribute == k) }.each do |k,v|
29
- hash[k] = EVAL_ATTRIBUTES.include?(k) ? eval_comment(v.join(' '), mod, k) : v.join(' ')
30
- end
31
- (hash[:options] ||= {}).merge!(options) if !options.empty?
32
- attribute ? hash[attribute] : hash
33
- end
34
-
35
- #:stopdoc:
36
- def parse_option_comments(arr, mod)
37
- arr.inject({}) {|t,e|
38
- key, val = e.join(' ').split(/\s*,\s*/, 2)
39
- if val
40
- key = key.sub(/^\s*:/, '').to_sym
41
- t[key] = eval_comment(val, mod, 'option')
42
- end
43
- t
44
- }
45
- end
46
-
47
- def eval_comment(value, mod, mattr)
48
- value = "{#{value}}" if !value[/^\s*\{/] && value[/=>/]
49
- mod.module_eval(value)
50
- rescue Exception
51
- if Runner.debug
52
- warn "DEBUG: Error while evaluating @#{mattr} in module #{mod.to_s[/\w+$/]}:\n " +
53
- $!.message.gsub(/\n/, "\n ")
54
- end
55
- nil
56
- end
57
-
58
- # Scrapes a given string for commented @keywords, starting with the line above the given line
59
- def scrape_file(file_string, line)
60
- lines = file_string.split("\n")
61
- saved = []
62
- i = line -2
63
- while lines[i] =~ /^\s*#\s*(\S+)/ && i >= 0
64
- saved << lines[i]
65
- i -= 1
66
- end
67
-
68
- saved.empty? ? {} : splitter(saved.reverse)
69
- end
70
-
71
- def splitter(lines)
72
- hash = {}
73
- i = 0
74
- # to magically make the last comment a description
75
- unless lines.any? {|e| e =~ /^\s*#\s*@desc/ }
76
- last_line = lines.pop
77
- hash[:desc] = (last_line =~ /^\s*#\s*([^@\s].*)/) ? [$1] : nil
78
- lines << last_line unless hash[:desc]
79
- end
80
-
81
- option = []
82
- while i < lines.size
83
- while lines[i] =~ /^\s*#\s*@(\w+)\s*(.*)/
84
- key = $1.to_sym
85
- hash[key] = [$2]
86
- i += 1
87
- while lines[i] =~ /^\s*#\s*([^@\s].*)/
88
- hash[key] << $1
89
- i+= 1
90
- end
91
- option << hash.delete(:option) if key == :option
92
- end
93
- i += 1
94
- end
95
- hash[:option] = option if !option.empty?
96
- hash
97
- end
98
- #:startdoc:
99
- end
100
- end