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,100 @@
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
@@ -0,0 +1,98 @@
1
+ module Boson
2
+ # Gathers method attributes by redefining method_added and capturing method
3
+ # calls before a method. This module also saves method locations so CommentInspector
4
+ # can scrape their commented method attributes.
5
+ module MethodInspector
6
+ extend self
7
+ attr_accessor :current_module, :mod_store
8
+ @mod_store ||= {}
9
+ METHODS = [:config, :desc, :options, :render_options]
10
+ METHOD_CLASSES = {:config=>Hash, :desc=>String, :options=>Hash, :render_options=>Hash}
11
+ ALL_METHODS = METHODS + [:option]
12
+
13
+ # The method_added used while scraping method attributes.
14
+ def new_method_added(mod, meth)
15
+ return unless mod.to_s[/^Boson::Commands::/]
16
+ self.current_module = mod
17
+ store[:temp] ||= {}
18
+ METHODS.each do |e|
19
+ store[e][meth.to_s] = store[:temp][e] if store[:temp][e]
20
+ end
21
+ (store[:options][meth.to_s] ||= {}).merge! store[:temp][:option] if store[:temp][:option]
22
+
23
+ if store[:temp].size < ALL_METHODS.size
24
+ store[:method_locations] ||= {}
25
+ if (result = find_method_locations(caller))
26
+ store[:method_locations][meth.to_s] = result
27
+ end
28
+ end
29
+ store[:temp] = {}
30
+ scrape_arguments(meth) if has_inspector_method?(meth, :options) || has_inspector_method?(meth,:render_options)
31
+ end
32
+
33
+ METHODS.each do |e|
34
+ define_method(e) do |mod, val|
35
+ (@mod_store[mod] ||= {})[e] ||= {}
36
+ (store(mod)[:temp] ||= {})[e] = val
37
+ end
38
+ end
39
+
40
+ def option(mod, name, value)
41
+ (@mod_store[mod] ||= {})[:options] ||= {}
42
+ (store(mod)[:temp] ||= {})[:option] ||= {}
43
+ (store(mod)[:temp] ||= {})[:option][name] = value
44
+ end
45
+
46
+ # Scrapes a method's arguments using ArgumentInspector.
47
+ def scrape_arguments(meth)
48
+ store[:args] ||= {}
49
+
50
+ o = Object.new
51
+ o.extend(@current_module)
52
+ # private methods return nil
53
+ if (val = ArgumentInspector.scrape_with_eval(meth, @current_module, o))
54
+ store[:args][meth.to_s] = val
55
+ end
56
+ end
57
+
58
+ CALLER_REGEXP = RUBY_VERSION < '1.9' ? /in `load_source'/ : /in `<module:.*>'/
59
+ # Returns an array of the file and line number at which a method starts using
60
+ # a caller array. Necessary information for CommentInspector to function.
61
+ def find_method_locations(stack)
62
+ if (line = stack.find {|e| e =~ CALLER_REGEXP })
63
+ (line =~ /^(.*):(\d+)/) ? [$1, $2.to_i] : nil
64
+ end
65
+ end
66
+
67
+ #:stopdoc:
68
+ def find_method_locations_for_19(klass, meth)
69
+ if (klass = Util.any_const_get(klass)) && (meth_location = klass.method(meth).source_location) &&
70
+ meth_location[0]
71
+ meth_location
72
+ end
73
+ end
74
+
75
+ # Hash of a module's method attributes i.e. descriptions, options by method and then attribute
76
+ def store(mod=@current_module)
77
+ @mod_store[mod]
78
+ end
79
+
80
+ def current_module=(mod)
81
+ @current_module = mod
82
+ @mod_store[mod] ||= {}
83
+ end
84
+
85
+ def has_inspector_method?(meth, inspector)
86
+ (store[inspector] && store[inspector].key?(meth.to_s)) || inspector_in_file?(meth.to_s, inspector)
87
+ end
88
+
89
+ def inspector_in_file?(meth, inspector_method)
90
+ return false if !(file_line = store[:method_locations] && store[:method_locations][meth])
91
+ if File.exists?(file_line[0]) && (options = CommentInspector.scrape(
92
+ FileLibrary.read_library_file(file_line[0]), file_line[1], @current_module, inspector_method) )
93
+ (store[inspector_method] ||= {})[meth] = options
94
+ end
95
+ end
96
+ #:startdoc:
97
+ end
98
+ end
@@ -0,0 +1,144 @@
1
+ module Boson
2
+ # This class loads a file by its path relative to the commands directory of a repository.
3
+ # For example the library 'public/misc' could refer to the file '~/.boson/commands/public/misc.rb'.
4
+ # If a file's basename is unique in its repository, then it can be loaded with its basename i.e. 'misc'
5
+ # for the previous example. When loading a library, this class searches repositories in the order given by
6
+ # Boson.repos.
7
+ #
8
+ # === Creating a FileLibrary
9
+ # Start by creating a file with a module and some methods (See Library for naming a module).
10
+ # Non-private methods are automatically loaded as a library's commands.
11
+ #
12
+ # Take for example a library brain.rb:
13
+ # # Drop this in ~/.boson/commands/brain.rb
14
+ # module Brain
15
+ # def take_over(destination)
16
+ # puts "Pinky, it's time to take over the #{destination}!"
17
+ # end
18
+ # end
19
+ #
20
+ # Once loaded, this library can be run from the commandline or irb:
21
+ # $ boson take_over world
22
+ # >> take_over 'world'
23
+ #
24
+ # If the library is namespaced, the command would be run as brain.take_over.
25
+ #
26
+ # Let's give Brain an option in his conquest:
27
+ # module Brain
28
+ # options :execute=>:string
29
+ # def take_over(destination, options={})
30
+ # puts "Pinky, it's time to take over the #{destination}!"
31
+ # system(options[:execute]) if options[:execute]
32
+ # end
33
+ # end
34
+ #
35
+ # From the commandline and irb this runs as:
36
+ # $ boson take_over world -e initiate_brainiac
37
+ # >> take_over 'world -e initiate_brainiac'
38
+ #
39
+ # Since Boson aims to make your libraries just standard ruby, we can achieve the above
40
+ # by making options a commented method attribute:
41
+ # module Brain
42
+ # # @options :execute=>:string
43
+ # # Help Brain live the dream
44
+ # def take_over(destination, options={})
45
+ # puts "Pinky, it's time to take over the #{destination}!"
46
+ # system(options[:execute]) if options[:execute]
47
+ # end
48
+ # end
49
+ #
50
+ # Some points about the above:
51
+ # * A '@' must prefix options and other method attributes that become comments.
52
+ # * Note the comment above the method. One-line comments right before a method set a command's description.
53
+ # * See Inspector for other method attributes, like config and render_options, that can be placed above a method.
54
+ #
55
+ # Once a command has a defined option, a command can also recognize a slew of global options:
56
+ # >> take_over '-h'
57
+ # take_over [destination] [--execute=STRING]
58
+ #
59
+ # # prints much more verbose help
60
+ # >> take_over '-hv'
61
+ #
62
+ # For more about these global options see OptionCommand and View.
63
+ class FileLibrary < Library
64
+ #:stopdoc:
65
+ def self.library_file(library, dir)
66
+ File.join(Repo.commands_dir(dir), library + ".rb")
67
+ end
68
+
69
+ def self.matched_repo; @repo; end
70
+
71
+ def self.read_library_file(file, reload=false)
72
+ @file_cache ||= {}
73
+ @file_cache[file] = File.read(file) if (!@file_cache.has_key?(file) || reload)
74
+ @file_cache[file]
75
+ end
76
+
77
+ def self.reset_file_cache(name=nil)
78
+ if name && @file_cache
79
+ @file_cache.delete(library_file(name, (matched_repo || Boson.repo).dir))
80
+ else
81
+ @file_cache = nil
82
+ end
83
+ end
84
+
85
+ handles {|source|
86
+ @repo = Boson.repos.find {|e| File.exists? library_file(source.to_s, e.dir) } ||
87
+ Boson.repos.find {|e|
88
+ Dir["#{e.commands_dir}/**/*.rb"].grep(/\/#{source}\.rb/).size == 1
89
+ }
90
+ !!@repo
91
+ }
92
+
93
+ def library_file(name=@name)
94
+ self.class.library_file(name, @repo_dir)
95
+ end
96
+
97
+ def set_repo
98
+ self.class.matched_repo
99
+ end
100
+
101
+ def set_name(name)
102
+ @lib_file = File.exists?(library_file(name.to_s)) ? library_file(name.to_s) :
103
+ Dir[self.class.matched_repo.commands_dir.to_s+'/**/*.rb'].find {|e| e =~ /\/#{name}\.rb$/}
104
+ @lib_file.gsub(/^#{self.class.matched_repo.commands_dir}\/|\.rb$/, '')
105
+ end
106
+
107
+ def base_module
108
+ @base_module ||= @name.include?('/') ? create_module_from_path : Commands
109
+ end
110
+
111
+ def load_source(reload=false)
112
+ library_string = self.class.read_library_file(@lib_file, reload)
113
+ Inspector.enable
114
+ base_module.module_eval(library_string, @lib_file)
115
+ Inspector.disable
116
+ end
117
+
118
+ def create_module_from_path(index=-2)
119
+ @name.split('/')[0..index].inject(Boson::Commands) {|base, e|
120
+ base.const_defined?(sub_mod = Util.camelize(e)) ? base.const_get(sub_mod) :
121
+ Util.create_module(base, e)
122
+ }
123
+ end
124
+
125
+ def load_source_and_set_module
126
+ detected = detect_additions(:modules=>true) { load_source }
127
+ @module = determine_lib_module(detected[:modules]) unless @module
128
+ end
129
+
130
+ def determine_lib_module(detected_modules)
131
+ detected_modules = detected_modules.select {|e| e.to_s[/^#{base_module}::/] }
132
+ case detected_modules.size
133
+ when 1 then lib_module = detected_modules[0]
134
+ when 0 then lib_module = create_module_from_path(-1)
135
+ else
136
+ unless (lib_module = Util.constantize("boson/commands/#{@name}")) && lib_module.to_s[/^Boson::Commands/]
137
+ raise LoaderError, "Can't detect module. Specify a module in this library's config."
138
+ end
139
+ end
140
+ lib_module
141
+ end
142
+ #:startdoc:
143
+ end
144
+ end
@@ -0,0 +1,30 @@
1
+ module Boson
2
+ # This library loads a gem by the given name. Unlike FileLibrary or ModuleLibrary, this library
3
+ # doesn't need a module to provide its functionality.
4
+ #
5
+ # Example:
6
+ # >> load_library 'httparty', :class_commands=>{'put'=>'HTTParty.put',
7
+ # 'delete'=>'HTTParty.delete' }
8
+ # => true
9
+ # >> put 'http://someurl.com'
10
+ class GemLibrary < Library
11
+ #:stopdoc:
12
+ def self.is_a_gem?(name)
13
+ return false unless defined? Gem
14
+ Gem::VERSION >= '1.8.0' ?
15
+ Gem::Specification.find_all_by_name(name)[0].is_a?(Gem::Specification) :
16
+ Gem.searcher.find(name).is_a?(Gem::Specification)
17
+ end
18
+
19
+ handles {|source| is_a_gem?(source.to_s) }
20
+
21
+ def loaded_correctly?
22
+ !@gems.empty? || !@commands.empty? || !!@module
23
+ end
24
+
25
+ def load_source_and_set_module
26
+ detect_additions { Util.safe_require @name }
27
+ end
28
+ #:startdoc:
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # This class loads any local file and is most commonly used to load a local
2
+ # Bosonfile. Since this file doesn't exist inside a normal Repo, it is not indexed with any repo.
3
+ # Since file-based libraries need to be associated with a repository, Boson associates it
4
+ # with a local repository if it exists or defaults to Boson.repo. See Boson::FileLibrary
5
+ # for more info about this library.
6
+ #
7
+ # Example:
8
+ # >> load_library 'Bosonfile'
9
+ # => true
10
+ class Boson::LocalFileLibrary < Boson::FileLibrary
11
+ handles {|source|
12
+ @repo = (File.exists?(source.to_s) ? (Boson.local_repo || Boson.repo) : nil)
13
+ !!@repo
14
+ }
15
+
16
+ #:stopdoc:
17
+ def set_name(name)
18
+ @lib_file = File.expand_path(name.to_s)
19
+ File.basename(@lib_file).downcase
20
+ end
21
+
22
+ def base_module
23
+ Boson::Commands
24
+ end
25
+
26
+ def library_file
27
+ @lib_file
28
+ end
29
+ #:startdoc:
30
+ end
@@ -0,0 +1,37 @@
1
+ module Boson
2
+ # This library takes a module or class as a library's name and loads its class methods
3
+ # as commands. If no commands are given it defaults to loading all of its class methods
4
+ # as commands. The only method callback (see Loader) this library calls on the
5
+ # original module/class is config().
6
+ #
7
+ # Example:
8
+ # >> load_library Math, :commands=>%w{sin cos tan}
9
+ # => true
10
+ #
11
+ # # Let's brush up on ol trig
12
+ # >> sin (Math::PI/2)
13
+ # => 1.0
14
+ # >> tan (Math::PI/4)
15
+ # => 1.0
16
+ # # Close enough :)
17
+ # >> cos (Math::PI/2)
18
+ # => 6.12323399573677e-17
19
+
20
+ class ModuleLibrary < Library
21
+ #:stopdoc:
22
+ handles {|source| source.is_a?(Module) }
23
+
24
+ def set_name(name)
25
+ @module = name
26
+ underscore_lib = name.to_s[/^Boson::Commands/] ? name.to_s.split('::')[-1] : name.to_s
27
+ Util.underscore(underscore_lib)
28
+ end
29
+
30
+ def initialize_library_module
31
+ @class_commands = {@module.to_s=>Array(@commands).empty? ? @module.methods(false) : @commands }
32
+ @module = nil
33
+ super
34
+ end
35
+ #:startdoc:
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ # This library requires the given name. This is useful for loading standard libraries,
2
+ # non-gem libraries (i.e. rip packages) and anything else in $LOAD_PATH.
3
+ #
4
+ # Example:
5
+ # >> load_library 'fileutils', :class_commands=>{'cd'=>'FileUtils.cd', 'cp'=>'FileUtils.cp'}
6
+ # => true
7
+ # >> cd '/home'
8
+ # => 0
9
+ # >> Dir.pwd
10
+ # >> '/home'
11
+ class Boson::RequireLibrary < Boson::GemLibrary
12
+ EXTENSIONS = ['', '.rb', '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar']
13
+ handles {|source|
14
+ extensions_glob = "{#{EXTENSIONS.join(',')}}"
15
+ ($LOAD_PATH - ['.']).any? {|dir|
16
+ Dir["#{File.expand_path source.to_s, dir}#{extensions_glob}"].size > 0
17
+ }
18
+ }
19
+
20
+ def loaded_correctly?
21
+ super || $".grep(/^#{@name}\.([a-z]+)?$/).size > 0
22
+ end
23
+ end