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/lib/boson/library.rb CHANGED
@@ -1,98 +1,51 @@
1
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.
2
+ # A library is a group of commands (Command objects) usually grouped together
3
+ # by a module. Libraries are loaded from different sources depending on the
4
+ # library subclass.
36
5
  #
37
6
  # === 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.
7
+ # To create your own subclass you need to define what sources the subclass can
8
+ # handle with handles(). See Loader to see what instance methods to override
9
+ # for a subclass.
41
10
  class Library
42
11
  include Loader
43
12
  class <<self
44
- #:stopdoc:
45
13
  attr_accessor :handle_blocks
14
+ # Returns true when the subclass is chosen to load.
46
15
  def handles(&block)
47
16
  (Library.handle_blocks ||= []) << [self,block]
48
17
  end
49
- #:startdoc:
50
18
  end
51
19
 
52
20
  # 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])
21
+ ATTRIBUTES = [:commands, :loaded, :module, :name]
22
+ attr_reader *(ATTRIBUTES + [:commands_hash, :library_file])
55
23
  # 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
24
+ attr_reader :new_module, :new_commands, :lib_file
59
25
 
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).
26
+ # Creates a library object with the given hash. Each hash pair maps
27
+ # directly to an instance variable and value. Defaults for attributes are
28
+ # read from config[:libraries][@library_name][@attribute].
64
29
  #
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.
30
+ # @param [Hash] hash
31
+ # @option hash [String] :name Required attribute
32
+ # @option hash [Array,Hash] :commands Commands belonging to a library. A
33
+ # hash configures command attributes for the given commands with command
34
+ # names pointing to their configs. See Command.new for a command's
35
+ # configurable attributes. If an array, the commands are set for the
36
+ # given library, overidding default command detection. Example:
37
+ # :commands=>{'commands'=>{:desc=>'Lists commands', :alias=>'com'}}
38
+ # @option hash [Boolean] :force Forces a library to ignore when a library's
39
+ # methods are overriding existing ones. Use with caution. Default is false.
87
40
  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"
41
+ before_initialize
42
+ @name = set_name(hash.delete(:name)) or
43
+ raise ArgumentError, "Library missing required key :name"
91
44
  @loaded = false
92
45
  @commands_hash = {}
93
46
  @commands = []
94
- set_config (repo.config[:libraries][@name] || {}).merge(hash), true
95
- set_command_aliases(repo.config[:command_aliases])
47
+ set_config (config[:libraries][@name] || {}).merge(hash), true
48
+ set_command_aliases(config[:command_aliases])
96
49
  end
97
50
 
98
51
  # A concise symbol version of a library type i.e. FileLibrary -> :file.
@@ -101,33 +54,56 @@ module Boson
101
54
  str.downcase.to_sym
102
55
  end
103
56
 
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
57
+ # handles names under directories
58
+ def clean_name
59
+ @name[/\w+$/]
112
60
  end
113
61
 
114
- # The object a library uses for executing its commands.
115
- def namespace_object
116
- @namespace_object ||= namespace ? Boson.invoke(namespace) : Boson.main_object
62
+ # sets name
63
+ def set_name(name)
64
+ name.to_s
117
65
  end
118
66
 
119
- #:stopdoc:
120
- # handles names under directories
121
- def clean_name
122
- @name[/\w+$/]
67
+ module API
68
+ # The object a library uses for executing its commands.
69
+ def namespace_object
70
+ @namespace_object ||= Boson.main_object
71
+ end
72
+
73
+ # Method hook called at the beginning of initialize
74
+ def before_initialize
75
+ end
76
+
77
+ # Determines if library is local i.e. scoped to current directory/project
78
+ def local?
79
+ false
80
+ end
81
+
82
+ # @return [Hash] Attributes used internally by a library. Defaults to
83
+ # using Boson.config but can be overridden to be library-specific.
84
+ def config
85
+ Boson.config
86
+ end
123
87
  end
88
+ include API
124
89
 
125
- def local?
126
- is_a?(LocalFileLibrary) || (Boson.local_repo && Boson.local_repo.dir == @repo_dir)
90
+ # Command objects of library's commands
91
+ def command_objects(names=self.commands, command_array=Boson.commands)
92
+ command_array.select {|e| names.include?(e.name) && e.lib == self.name }
127
93
  end
128
94
 
129
- def set_name(name)
130
- name.to_s
95
+ # Command object for given command name
96
+ def command_object(name)
97
+ command_objects([name])[0]
98
+ end
99
+
100
+ private
101
+ def set_attributes(hash, force=false)
102
+ hash.each do |k,v|
103
+ if instance_variable_get("@#{k}").nil? || force
104
+ instance_variable_set("@#{k}", v)
105
+ end
106
+ end
131
107
  end
132
108
 
133
109
  def set_config(config, force=false)
@@ -150,30 +126,5 @@ module Boson
150
126
  @commands_hash[cmd][:alias] ||= cmd_alias
151
127
  end
152
128
  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
129
  end
179
- end
130
+ end
data/lib/boson/loader.rb CHANGED
@@ -1,118 +1,107 @@
1
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.
2
+ # Raised if a library has methods which conflict with existing methods
3
+ class MethodConflictError < LoaderError
4
+ MESSAGE = "The following commands conflict with existing commands: %s"
5
+ def initialize(conflicts)
6
+ super MESSAGE % conflicts.join(', ')
7
+ end
8
+ end
9
+
10
+ # This module is mixed into Library to give it load() functionality. When
11
+ # creating your own Library subclass, you should at least override
12
+ # load_source_and_set_module.
29
13
  module Loader
30
- # Loads a library and its dependencies and returns true if library loads correctly.
14
+ # Loads a library and its dependencies and returns true if library loads
15
+ # correctly.
31
16
  def load
32
- @gems ||= []
33
17
  load_source_and_set_module
34
18
  module_callbacks if @module
35
- yield if block_given?
36
- (@module || @class_commands) ? detect_additions { load_module_commands } : @namespace = false
19
+ yield if block_given? # load dependencies
20
+ detect_additions { load_commands } if load_commands?
37
21
  set_library_commands
38
- @indexed_namespace = (@namespace == false) ? nil : @namespace if @index
39
22
  loaded_correctly? && (@loaded = true)
40
23
  end
41
24
 
42
- # Load the source and set instance variables necessary to make a library valid i.e. @module.
25
+ # Method hook at the beginning of #load. This method should load the source
26
+ # and set instance variables necessary to make a library valid i.e. @module.
43
27
  def load_source_and_set_module; end
44
28
 
29
+ # Method hook for @module before loading
30
+ def module_callbacks; end
31
+
32
+ # Determines if load_commands should be called
33
+ def load_commands?
34
+ @module
35
+ end
36
+
37
+ # Wraps around module loading for unexpected additions
38
+ def detect_additions(options={}, &block)
39
+ Util.detect(options, &block).tap do |detected|
40
+ @commands.concat detected[:methods].map(&:to_s)
41
+ end
42
+ end
43
+
44
+ # Prepares for command loading, loads commands and rescues certain errors.
45
+ def load_commands
46
+ @module = @module ? Util.constantize(@module) :
47
+ Util.create_module(Boson::Commands, clean_name)
48
+ before_load_commands
49
+ check_for_method_conflicts unless @force
50
+ actual_load_commands
51
+ rescue MethodConflictError => err
52
+ handle_method_conflict_error err
53
+ end
54
+
45
55
  # Boolean which indicates if library loaded correctly.
46
56
  def loaded_correctly?
47
57
  !!@module
48
58
  end
49
59
 
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
60
+ # Method hook for @module after it's been included
61
+ def after_include; end
62
+
63
+ # called when MethodConflictError is rescued
64
+ def handle_method_conflict_error(err)
65
+ raise err
56
66
  end
57
67
 
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
68
+ # Method hook called after @module has been created
69
+ def before_load_commands; end
70
+
71
+ # Actually includes module and its commands
72
+ def actual_load_commands
73
+ include_in_universe
69
74
  end
70
75
 
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
76
+ # Returns array of method conflicts
77
+ def method_conflicts
78
+ (@module.instance_methods + @module.private_instance_methods) &
79
+ (Boson.main_object.methods + Boson.main_object.private_methods)
77
80
  end
78
81
 
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
82
+ # Handles setting and cleaning @commands
83
+ def set_library_commands
84
+ clean_library_commands
85
+ end
86
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
87
+ # Cleans @commands from set_library_commands
88
+ def clean_library_commands
89
+ aliases = @commands_hash.select {|k,v| @commands.include?(k) }.
90
+ map {|k,v| v[:alias] }.compact
91
+ @commands -= aliases
92
+ @commands.uniq!
92
93
  end
93
94
 
95
+ private
94
96
  def include_in_universe(lib_module=@module)
95
97
  Boson::Universe.send :include, lib_module
96
- @module.after_included if lib_module.respond_to?(:after_included) && !@index
98
+ after_include
97
99
  Boson::Universe.send :extend_object, Boson.main_object
98
100
  end
99
101
 
100
102
  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!
103
+ conflicts = method_conflicts
104
+ raise MethodConflictError.new(conflicts) unless conflicts.empty?
115
105
  end
116
- #:startdoc:
117
106
  end
118
107
  end
data/lib/boson/manager.rb CHANGED
@@ -1,169 +1,165 @@
1
1
  module Boson
2
- # Base class for library loading errors. Raised mostly in Boson::Loader and rescued by Boson::Manager.
2
+ # Base class for library loading errors. Raised mostly in Boson::Loader and
3
+ # rescued by Boson::Manager.
3
4
  class LoaderError < StandardError; end
4
- # Raised when a library's append_features returns false.
5
- class AppendFeaturesFalseError < StandardError; end
6
5
 
7
6
  # Handles loading of libraries and commands.
8
7
  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
8
+ # Loads a library or an array of libraries with options. Manager loads the
9
+ # first library subclass to return true for Library#handles. Any options
10
+ # that aren't listed here are passed as library attributes to the libraries
11
+ # (see Library.new)
12
+ #
13
+ # @param [Hash] options
14
+ # @option options [Boolean] :verbose Prints each library's loaded status
15
+ # along with more verbose errors. Default is false.
16
+ # @example Manager.load MyRunner
17
+ def self.load(libraries, options={})
18
+ instance.load(libraries, options)
19
+ end
25
20
 
26
- #:stopdoc:
27
- def failed_libraries
28
- @failed_libraries ||= []
29
- end
21
+ class <<self; attr_accessor :instance; end
30
22
 
31
- def add_library(lib)
32
- Boson.libraries.delete(Boson.library(lib.name))
33
- Boson.libraries << lib
34
- end
23
+ def self.instance
24
+ @instance ||= new
25
+ end
35
26
 
36
- def loaded?(lib_name)
37
- ((lib = Boson.library(lib_name)) && lib.loaded) ? true : false
38
- end
27
+ # Adds a library to Boson.libraries
28
+ def self.add_library(lib)
29
+ Boson.libraries.delete(Boson.library(lib.name))
30
+ Boson.libraries << lib
31
+ end
39
32
 
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
33
+ # Given a library name, determines if it's loaded
34
+ def self.loaded?(lib_name)
35
+ ((lib = Boson.library(lib_name)) && lib.loaded) ? true : false
36
+ end
61
37
 
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
38
+ attr_accessor :failed_libraries, :verbose
39
+ def initialize
40
+ @failed_libraries = []
41
+ end
80
42
 
81
- def lib_dependencies
82
- @lib_dependencies ||= {}
83
- end
43
+ # Loads libraries
44
+ def load(libraries, options={})
45
+ Array(libraries).map {|e|
46
+ (@library = load_once(e, options)) ? after_load : false
47
+ }.all?
48
+ end
84
49
 
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
50
+ # Adds a library to the failed list
51
+ def add_failed_library(library)
52
+ failed_libraries << library
53
+ end
92
54
 
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
55
+ # Called after a library is loaded
56
+ def after_load
57
+ create_commands(@library)
58
+ self.class.add_library(@library)
59
+ puts "Loaded library #{@library.name}" if verbose
60
+ during_after_load
61
+ true
62
+ end
97
63
 
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
64
+ # Redefines commands
65
+ def redefine_commands(lib, commands)
66
+ option_commands = lib.command_objects(commands).select(&:option_command?)
67
+ accepted, rejected = option_commands.partition {|e|
68
+ e.args(lib) || e.arg_size }
69
+ if verbose && rejected.size > 0
70
+ puts "Following commands cannot have options until their arguments " +
71
+ "are configured: " + rejected.map {|e| e.name}.join(', ')
108
72
  end
73
+ accepted.each {|cmd| Scientist.redefine_command(lib.namespace_object, cmd) }
74
+ end
109
75
 
110
- def before_create_commands(lib)
111
- lib.is_a?(FileLibrary) && lib.module && Inspector.add_method_data_to_library(lib)
112
- end
76
+ module API
77
+ # Method hook for loading dependencies or anything else before loading
78
+ # a library
79
+ def load_dependencies(lib, options); end
113
80
 
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
81
+ # Method hook in middle of after_load
82
+ def during_after_load; end
120
83
 
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(', ')
84
+ # Method hook called before create_commands
85
+ def before_create_commands(lib)
86
+ if lib.is_a?(RunnerLibrary) && lib.module
87
+ Inspector.add_method_data_to_library(lib)
127
88
  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
89
  end
134
90
 
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
91
+ # Method hook called after create_commands
92
+ def after_create_commands(lib, commands); end
93
+
94
+ # Handles an error from a load action
95
+ def handle_load_action_error(library, load_method, err)
96
+ case err
97
+ when LoaderError
98
+ add_failed_library library
99
+ warn "Unable to #{load_method} library #{library}. Reason: #{err.message}"
100
+ else
101
+ add_failed_library library
102
+ message = "Unable to #{load_method} library #{library}. Reason: #{err}"
103
+ if Boson.debug
104
+ message << "\n" + err.backtrace.map {|e| " " + e }.join("\n")
105
+ elsif verbose
106
+ message << "\n" + err.backtrace.slice(0,3).map {|e| " " + e }.join("\n")
142
107
  end
108
+ warn message
143
109
  end
144
- create_instance_aliases(aliases_hash)
145
110
  end
111
+ end
112
+ include API
113
+
114
+ private
115
+ def call_load_action(library, load_method)
116
+ yield
117
+ rescue StandardError, SyntaxError, LoadError => err
118
+ handle_load_action_error(library, load_method, err)
119
+ ensure
120
+ Inspector.disable if Inspector.enabled
121
+ end
146
122
 
147
- def create_instance_aliases(aliases_hash)
148
- Alias.manager.create_aliases(:instance_method, aliases_hash)
149
- end
123
+ def load_once(source, options={})
124
+ self.verbose = options[:verbose]
150
125
 
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}"}
126
+ call_load_action(source, :load) do
127
+ lib = loader_create(source, options)
128
+ if self.class.loaded?(lib.name)
129
+ if verbose && !options[:dependency]
130
+ warn "Library #{lib.name} already exists."
155
131
  end
156
- }
157
- Alias.manager.create_aliases(:any_to_instance_method, mod.to_s=>class_commands.invert)
132
+ false
133
+ else
134
+ actual_load_once lib, options
135
+ end
158
136
  end
137
+ end
159
138
 
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"
139
+ def actual_load_once(lib, options)
140
+ if lib.load { load_dependencies(lib, options) }
141
+ lib
142
+ else
143
+ if !options[:dependency]
144
+ warn "Library #{lib.name} did not load successfully."
164
145
  end
146
+ warn " "+lib.inspect if Boson.debug
147
+ false
165
148
  end
166
- #:startdoc:
149
+ end
150
+
151
+ def loader_create(source, options)
152
+ options = options.dup.tap {|h| h.delete(:verbose) }
153
+ lib_class = Library.handle_blocks.find {|k,v| v.call(source) } or
154
+ raise(LoaderError, "Library #{source} not found.")
155
+ lib_class[0].new(options.merge(name: source))
156
+ end
157
+
158
+ def create_commands(lib, commands=lib.commands)
159
+ before_create_commands(lib)
160
+ commands.each {|e| Boson.commands << Command.create(e, lib)}
161
+ after_create_commands(lib, commands)
162
+ redefine_commands(lib, commands)
167
163
  end
168
164
  end
169
165
  end