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,179 @@
1
+ module Boson
2
+ # A library is a group of commands (Command objects) usually grouped together by a module.
3
+ # Libraries are loaded from different sources depending on the library subclass. Default library
4
+ # subclasses are FileLibrary, GemLibrary, RequireLibrary, ModuleLibrary and LocalFileLibrary.
5
+ # See Loader for callbacks a library's module can have.
6
+ #
7
+ # == Naming a Library Module
8
+ # Although you can name a library module almost anything, here's the fine print:
9
+ # * A module can have any name if it's the only module in a library.
10
+ # * If there are multiple modules in a file library, the module's name must be a camelized version
11
+ # of the file's basename i.e. ~/.boson/commands/ruby_core.rb -> RubyCore.
12
+ # * Although modules are evaluated under the Boson::Commands namespace, Boson will warn you about creating
13
+ # modules whose name is the same as a top level class/module. The warning is to encourage users to stay
14
+ # away from error-prone libraries. Once you introduce such a module, _all_ libraries assume the nested module
15
+ # over the top level module and the top level module has to be prefixed with '::' _everywhere_.
16
+ #
17
+ # == Configuration
18
+ # Libraries and their commands can be configured in different ways in this order:
19
+ # * If library is a FileLibrary, commands be configured with a config method attribute (see Inspector).
20
+ # * If a library has a module, you can set library + command attributes via the config() callback (see Loader).
21
+ # * All libraries can be configured by passing a hash of {library attributes}[link:classes/Boson/Library.html#M000077] under
22
+ # {the :libraries key}[link:classes/Boson/Repo.html#M000070] to the main config file ~/.boson/config/boson.yml.
23
+ # For most libraries this may be the only way to configure a library's commands.
24
+ # An example of a GemLibrary config:
25
+ # :libraries:
26
+ # httparty:
27
+ # :class_commands:
28
+ # delete: HTTParty.delete
29
+ # :commands:
30
+ # delete:
31
+ # :alias: d
32
+ # :desc: Http delete a given url
33
+ #
34
+ # When installing a third-party library, use the config file as a way to override default library and command attributes
35
+ # without modifying the library.
36
+ #
37
+ # === Creating Your Own Library
38
+ # To create your own subclass you need to define what sources the subclass can handle with handles().
39
+ # If handles() returns true then the subclass is chosen to load. See Loader to see what instance methods
40
+ # to override for a subclass.
41
+ class Library
42
+ include Loader
43
+ class <<self
44
+ #:stopdoc:
45
+ attr_accessor :handle_blocks
46
+ def handles(&block)
47
+ (Library.handle_blocks ||= []) << [self,block]
48
+ end
49
+ #:startdoc:
50
+ end
51
+
52
+ # Public attributes for use outside of Boson.
53
+ ATTRIBUTES = [:gems, :dependencies, :commands, :loaded, :module, :name, :namespace, :indexed_namespace]
54
+ attr_reader *(ATTRIBUTES + [:commands_hash, :library_file, :object_namespace])
55
+ # Private attribute for use within Boson.
56
+ attr_reader :no_alias_creation, :new_module, :new_commands, :class_commands, :lib_file, :repo_dir
57
+ # Optional namespace name for a library. When enabled defaults to a library's name.
58
+ attr_writer :namespace
59
+
60
+ # Creates a library object with a hash of attributes which must include a :name attribute.
61
+ # Each hash pair maps directly to an instance variable and value. Defaults for attributes
62
+ # are read from config[:libraries][@library_name][@attribute]. When loading libraries, attributes
63
+ # can also be set via a library module's config() method (see Loader).
64
+ #
65
+ # Attributes that can be configured:
66
+ # [*:dependencies*] An array of libraries that this library depends on. A library won't load
67
+ # unless its dependencies are loaded first.
68
+ # [*:commands*] A hash or array of commands that belong to this library. A hash configures command attributes
69
+ # for the given commands with command names pointing to their configs. See Command.new for a
70
+ # command's configurable attributes. If an array, the commands are set for the given library,
71
+ # overidding default command detection. Example:
72
+ # :commands=>{'commands'=>{:desc=>'Lists commands', :alias=>'com'}}
73
+ # [*:class_commands*] A hash of commands to create. A hash key-pair can map command names to any string of ruby code
74
+ # that ends with a method call. Or a key-pair can map a class to an array of its class methods
75
+ # to create commands of the same name. Example:
76
+ # :class_commands=>{'spy'=>'Bond.spy', 'create'=>'Alias.manager.create',
77
+ # 'Boson::Util'=>['detect', 'any_const_get']}
78
+ # [*:force*] Boolean which forces a library to ignore when a library's methods are overriding existing ones.
79
+ # Use with caution. Default is false.
80
+ # [*:object_methods*] Boolean which detects any Object/Kernel methods created when loading a library and automatically
81
+ # adds them to a library's commands. Default is true.
82
+ # [*:namespace*] Boolean or string which namespaces a library. When true, the library is automatically namespaced
83
+ # to the library's name. When a string, the library is namespaced to the string. Default is nil.
84
+ # To control the namespacing of all libraries see Boson::Repo.config.
85
+ # [*:no_alias_creation*] Boolean which doesn't create aliases for a library. Useful for libraries that configure command
86
+ # aliases outside of Boson's control. Default is false.
87
+ def initialize(hash)
88
+ repo = set_repo
89
+ @repo_dir = repo.dir
90
+ @name = set_name(hash.delete(:name)) or raise ArgumentError, "Library missing required key :name"
91
+ @loaded = false
92
+ @commands_hash = {}
93
+ @commands = []
94
+ set_config (repo.config[:libraries][@name] || {}).merge(hash), true
95
+ set_command_aliases(repo.config[:command_aliases])
96
+ end
97
+
98
+ # A concise symbol version of a library type i.e. FileLibrary -> :file.
99
+ def library_type
100
+ str = self.class.to_s[/::(\w+)Library$/, 1] || 'library'
101
+ str.downcase.to_sym
102
+ end
103
+
104
+ def namespace(orig=@namespace)
105
+ @namespace = [String,FalseClass].include?(orig.class) ? orig : begin
106
+ if (@namespace == true || (Boson.repo.config[:auto_namespace] && !@index))
107
+ @namespace = clean_name
108
+ else
109
+ @namespace = false
110
+ end
111
+ end
112
+ end
113
+
114
+ # The object a library uses for executing its commands.
115
+ def namespace_object
116
+ @namespace_object ||= namespace ? Boson.invoke(namespace) : Boson.main_object
117
+ end
118
+
119
+ #:stopdoc:
120
+ # handles names under directories
121
+ def clean_name
122
+ @name[/\w+$/]
123
+ end
124
+
125
+ def local?
126
+ is_a?(LocalFileLibrary) || (Boson.local_repo && Boson.local_repo.dir == @repo_dir)
127
+ end
128
+
129
+ def set_name(name)
130
+ name.to_s
131
+ end
132
+
133
+ def set_config(config, force=false)
134
+ if (commands = config.delete(:commands))
135
+ if commands.is_a?(Array)
136
+ @commands += commands
137
+ @pre_defined_commands = true
138
+ elsif commands.is_a?(Hash)
139
+ @commands += commands.keys
140
+ @commands_hash = Util.recursive_hash_merge commands, @commands_hash
141
+ end
142
+ end
143
+ set_command_aliases config.delete(:command_aliases) if config[:command_aliases]
144
+ set_attributes config, force
145
+ end
146
+
147
+ def set_command_aliases(command_aliases)
148
+ (command_aliases || {}).each do |cmd, cmd_alias|
149
+ @commands_hash[cmd] ||= {}
150
+ @commands_hash[cmd][:alias] ||= cmd_alias
151
+ end
152
+ end
153
+
154
+ def set_repo
155
+ Boson.repo
156
+ end
157
+
158
+ def set_attributes(hash, force=false)
159
+ hash.each {|k,v| instance_variable_set("@#{k}", v) if instance_variable_get("@#{k}").nil? || force }
160
+ end
161
+
162
+ def command_objects(names=self.commands, command_array=Boson.commands)
163
+ command_array.select {|e| names.include?(e.name) && e.lib == self.name }
164
+ end
165
+
166
+ def command_object(name)
167
+ command_objects([name])[0]
168
+ end
169
+
170
+ def marshal_dump
171
+ [@name, @commands, @gems, @module.to_s, @repo_dir, @indexed_namespace]
172
+ end
173
+
174
+ def marshal_load(ary)
175
+ @name, @commands, @gems, @module, @repo_dir, @indexed_namespace = ary
176
+ end
177
+ #:startdoc:
178
+ end
179
+ end
@@ -0,0 +1,118 @@
1
+ module Boson
2
+ # Raised if a library has a method which conflicts with existing methods in Boson.main_object.
3
+ class MethodConflictError < LoaderError; end
4
+
5
+ # This module is mixed into Library to give it load() functionality.
6
+ # When creating your own Library subclass, you should override load_source_and_set_module
7
+ # You can override other methods in this module as needed.
8
+ #
9
+ # === Module Callbacks
10
+ # For libraries that have a module i.e. FileLibrary and GemLibrary, the following class methods
11
+ # are invoked in the order below when loading a library:
12
+ #
13
+ # [*:config*] This method returns a library's hash of attributes as explained by Library.new. This is useful
14
+ # for distributing libraries with a default configuration. The library attributes specified here
15
+ # are overridden by ones a user has in their config file except for the :commands attribute, which
16
+ # is recursively merged together.
17
+ # [*:append_features*] In addition to its normal behavior, this method's return value determines if a
18
+ # library is loaded in the current environment. This is useful for libraries that you
19
+ # want loaded by default but not in some environments i.e. different ruby versions or
20
+ # in irb but not in script/console. Remember to use super when returning true.
21
+ # [*:included*] In addition to its normal behavior, this method should be used to require external libraries.
22
+ # Although requiring dependencies could be done anywhere in a module, putting dependencies here
23
+ # are encouraged. By not having dependencies hardcoded in a module, it's possible to analyze
24
+ # and view a library's commands without having to install and load its dependencies.
25
+ # If creating commands here, note that conflicts with existing commands won't be detected.
26
+ # [*:after_included*] This method is called after included() to initialize functionality. This is useful for
27
+ # libraries that are primarily executing ruby code i.e. defining ruby extensions or
28
+ # setting irb features. This method isn't called when indexing a library.
29
+ module Loader
30
+ # Loads a library and its dependencies and returns true if library loads correctly.
31
+ def load
32
+ @gems ||= []
33
+ load_source_and_set_module
34
+ module_callbacks if @module
35
+ yield if block_given?
36
+ (@module || @class_commands) ? detect_additions { load_module_commands } : @namespace = false
37
+ set_library_commands
38
+ @indexed_namespace = (@namespace == false) ? nil : @namespace if @index
39
+ loaded_correctly? && (@loaded = true)
40
+ end
41
+
42
+ # Load the source and set instance variables necessary to make a library valid i.e. @module.
43
+ def load_source_and_set_module; end
44
+
45
+ # Boolean which indicates if library loaded correctly.
46
+ def loaded_correctly?
47
+ !!@module
48
+ end
49
+
50
+ #:stopdoc:
51
+ def module_callbacks
52
+ set_config(@module.config) if @module.respond_to?(:config)
53
+ if @module.respond_to?(:append_features)
54
+ raise AppendFeaturesFalseError unless @module.append_features(Module.new)
55
+ end
56
+ end
57
+
58
+ def load_module_commands
59
+ initialize_library_module
60
+ rescue MethodConflictError=>e
61
+ if Boson.repo.config[:error_method_conflicts] || namespace
62
+ raise MethodConflictError, e.message
63
+ else
64
+ @namespace = clean_name
65
+ @method_conflict = true
66
+ $stderr.puts "#{e.message}. Attempting load into the namespace #{@namespace}..."
67
+ initialize_library_module
68
+ end
69
+ end
70
+
71
+ def detect_additions(options={}, &block)
72
+ options[:object_methods] = @object_methods if !@object_methods.nil?
73
+ detected = Util.detect(options, &block)
74
+ @gems += detected[:gems] if detected[:gems]
75
+ @commands += detected[:methods].map {|e| e.to_s }
76
+ detected
77
+ end
78
+
79
+ def initialize_library_module
80
+ @module = @module ? Util.constantize(@module) : Util.create_module(Boson::Commands, clean_name)
81
+ raise(LoaderError, "No module for library #{@name}") unless @module
82
+ if (conflict = Util.top_level_class_conflict(Boson::Commands, @module.to_s))
83
+ warn "Library module '#{@module}' may conflict with top level class/module '#{conflict}' references in"+
84
+ " your libraries. Rename your module to avoid this warning."
85
+ end
86
+
87
+ Manager.create_class_aliases(@module, @class_commands) unless @class_commands.nil? ||
88
+ @class_commands.empty? || @method_conflict
89
+ check_for_method_conflicts unless @force
90
+ @namespace = clean_name if @object_namespace
91
+ namespace ? Namespace.create(namespace, self) : include_in_universe
92
+ end
93
+
94
+ def include_in_universe(lib_module=@module)
95
+ Boson::Universe.send :include, lib_module
96
+ @module.after_included if lib_module.respond_to?(:after_included) && !@index
97
+ Boson::Universe.send :extend_object, Boson.main_object
98
+ end
99
+
100
+ def check_for_method_conflicts
101
+ conflicts = namespace ? (Boson.can_invoke?(namespace) ? [namespace] : []) :
102
+ (@module.instance_methods + @module.private_instance_methods) & (Boson.main_object.methods +
103
+ Boson.main_object.private_methods)
104
+ unless conflicts.empty?
105
+ raise MethodConflictError,"The following commands conflict with existing commands: #{conflicts.join(', ')}"
106
+ end
107
+ end
108
+
109
+ def set_library_commands
110
+ aliases = @commands_hash.select {|k,v| @commands.include?(k) }.map {|k,v| v[:alias]}.compact
111
+ @commands -= aliases
112
+ @commands.delete(namespace) if namespace
113
+ @commands += Boson.invoke(namespace).boson_commands if namespace && !@pre_defined_commands
114
+ @commands.uniq!
115
+ end
116
+ #:startdoc:
117
+ end
118
+ end
@@ -0,0 +1,169 @@
1
+ module Boson
2
+ # Base class for library loading errors. Raised mostly in Boson::Loader and rescued by Boson::Manager.
3
+ class LoaderError < StandardError; end
4
+ # Raised when a library's append_features returns false.
5
+ class AppendFeaturesFalseError < StandardError; end
6
+
7
+ # Handles loading of libraries and commands.
8
+ class Manager
9
+ class <<self
10
+ attr_accessor :failed_libraries
11
+
12
+ # Loads a library or an array of libraries with options. Manager loads the first library subclass
13
+ # to meet a library subclass' criteria in this order: ModuleLibrary, FileLibrary, GemLibrary, RequireLibrary.
14
+ # ==== Examples:
15
+ # Manager.load 'my_commands' -> Loads a FileLibrary object from ~/.boson/commands/my_commands.rb
16
+ # Manager.load 'method_lister' -> Loads a GemLibrary object which requires the method_lister gem
17
+ # Any options that aren't listed here are passed as library attributes to the libraries (see Library.new)
18
+ # ==== Options:
19
+ # [:verbose] Boolean to print each library's loaded status along with more verbose errors. Default is false.
20
+ def load(libraries, options={})
21
+ Array(libraries).map {|e|
22
+ (@library = load_once(e, options)) ? after_load : false
23
+ }.all?
24
+ end
25
+
26
+ #:stopdoc:
27
+ def failed_libraries
28
+ @failed_libraries ||= []
29
+ end
30
+
31
+ def add_library(lib)
32
+ Boson.libraries.delete(Boson.library(lib.name))
33
+ Boson.libraries << lib
34
+ end
35
+
36
+ def loaded?(lib_name)
37
+ ((lib = Boson.library(lib_name)) && lib.loaded) ? true : false
38
+ end
39
+
40
+ def rescue_load_action(library, load_method)
41
+ yield
42
+ rescue AppendFeaturesFalseError
43
+ warn "DEBUG: Library #{library} didn't load due to append_features" if Runner.debug
44
+ rescue LoaderError=>e
45
+ FileLibrary.reset_file_cache(library.to_s)
46
+ failed_libraries << library
47
+ $stderr.puts "Unable to #{load_method} library #{library}. Reason: #{e.message}"
48
+ rescue StandardError, SyntaxError, LoadError =>e
49
+ FileLibrary.reset_file_cache(library.to_s)
50
+ failed_libraries << library
51
+ message = "Unable to #{load_method} library #{library}. Reason: #{$!}"
52
+ if Runner.debug
53
+ message += "\n" + e.backtrace.map {|e| " " + e }.join("\n")
54
+ elsif @options[:verbose]
55
+ message += "\n" + e.backtrace.slice(0,3).map {|e| " " + e }.join("\n")
56
+ end
57
+ $stderr.puts message
58
+ ensure
59
+ Inspector.disable if Inspector.enabled
60
+ end
61
+
62
+ def load_once(source, options={})
63
+ @options = options
64
+ rescue_load_action(source, :load) do
65
+ lib = loader_create(source)
66
+ if loaded?(lib.name)
67
+ $stderr.puts "Library #{lib.name} already exists." if options[:verbose] && !options[:dependency]
68
+ false
69
+ else
70
+ if lib.load { load_dependencies(lib, options) }
71
+ lib
72
+ else
73
+ $stderr.puts "Library #{lib.name} did not load successfully." if !options[:dependency]
74
+ $stderr.puts " "+lib.inspect if Runner.debug
75
+ false
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def lib_dependencies
82
+ @lib_dependencies ||= {}
83
+ end
84
+
85
+ def load_dependencies(lib, options={})
86
+ lib_dependencies[lib] = Array(lib.dependencies).map do |e|
87
+ next if loaded?(e)
88
+ load_once(e, options.merge(:dependency=>true)) ||
89
+ raise(LoaderError, "Can't load dependency #{e}")
90
+ end.compact
91
+ end
92
+
93
+ def loader_create(source)
94
+ lib_class = Library.handle_blocks.find {|k,v| v.call(source) } or raise(LoaderError, "Library #{source} not found.")
95
+ lib_class[0].new(@options.merge(:name=>source))
96
+ end
97
+
98
+ def after_load
99
+ create_commands(@library)
100
+ add_library(@library)
101
+ puts "Loaded library #{@library.name}" if @options[:verbose]
102
+ (lib_dependencies[@library] || []).each do |e|
103
+ create_commands(e)
104
+ add_library(e)
105
+ puts "Loaded library dependency #{e.name}" if @options[:verbose]
106
+ end
107
+ true
108
+ end
109
+
110
+ def before_create_commands(lib)
111
+ lib.is_a?(FileLibrary) && lib.module && Inspector.add_method_data_to_library(lib)
112
+ end
113
+
114
+ def create_commands(lib, commands=lib.commands)
115
+ before_create_commands(lib)
116
+ commands.each {|e| Boson.commands << Command.create(e, lib)}
117
+ create_command_aliases(lib, commands) if commands.size > 0 && !lib.no_alias_creation
118
+ redefine_commands(lib, commands)
119
+ end
120
+
121
+ def redefine_commands(lib, commands)
122
+ option_commands = lib.command_objects(commands).select {|e| e.option_command? }
123
+ accepted, rejected = option_commands.partition {|e| e.args(lib) || e.arg_size }
124
+ if @options[:verbose] && rejected.size > 0
125
+ puts "Following commands cannot have options until their arguments are configured: " +
126
+ rejected.map {|e| e.name}.join(', ')
127
+ end
128
+ accepted.each {|cmd| Scientist.redefine_command(lib.namespace_object, cmd) }
129
+ end
130
+
131
+ def create_command_aliases(lib, commands)
132
+ lib.module ? prep_and_create_instance_aliases(commands, lib.module) : check_for_uncreated_aliases(lib, commands)
133
+ end
134
+
135
+ def prep_and_create_instance_aliases(commands, lib_module)
136
+ aliases_hash = {}
137
+ select_commands = Boson.commands.select {|e| commands.include?(e.name)}
138
+ select_commands.each do |e|
139
+ if e.alias
140
+ aliases_hash[lib_module.to_s] ||= {}
141
+ aliases_hash[lib_module.to_s][e.name] = e.alias
142
+ end
143
+ end
144
+ create_instance_aliases(aliases_hash)
145
+ end
146
+
147
+ def create_instance_aliases(aliases_hash)
148
+ Alias.manager.create_aliases(:instance_method, aliases_hash)
149
+ end
150
+
151
+ def create_class_aliases(mod, class_commands)
152
+ class_commands.dup.each {|k,v|
153
+ if v.is_a?(Array)
154
+ class_commands.delete(k).each {|e| class_commands[e] = "#{k}.#{e}"}
155
+ end
156
+ }
157
+ Alias.manager.create_aliases(:any_to_instance_method, mod.to_s=>class_commands.invert)
158
+ end
159
+
160
+ def check_for_uncreated_aliases(lib, commands)
161
+ return if lib.is_a?(GemLibrary)
162
+ if (found_commands = Boson.commands.select {|e| commands.include?(e.name)}) && found_commands.find {|e| e.alias }
163
+ $stderr.puts "No aliases created for library #{lib.name} because it has no module"
164
+ end
165
+ end
166
+ #:startdoc:
167
+ end
168
+ end
169
+ end