bosonson 0.304.1

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