boson 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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