boson-more 0.1.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 (67) hide show
  1. data/.gemspec +22 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +97 -0
  4. data/Rakefile +35 -0
  5. data/deps.rip +1 -0
  6. data/lib/boson/alias.rb +75 -0
  7. data/lib/boson/argument_inspector.rb +90 -0
  8. data/lib/boson/commands/core.rb +67 -0
  9. data/lib/boson/commands/view_core.rb +19 -0
  10. data/lib/boson/commands/web_core.rb +153 -0
  11. data/lib/boson/comment_inspector.rb +100 -0
  12. data/lib/boson/console.rb +40 -0
  13. data/lib/boson/console_runner.rb +60 -0
  14. data/lib/boson/index.rb +48 -0
  15. data/lib/boson/libraries/file_library.rb +144 -0
  16. data/lib/boson/libraries/gem_library.rb +30 -0
  17. data/lib/boson/libraries/local_file_library.rb +30 -0
  18. data/lib/boson/libraries/module_library.rb +37 -0
  19. data/lib/boson/libraries/require_library.rb +23 -0
  20. data/lib/boson/libraries.rb +183 -0
  21. data/lib/boson/more/version.rb +5 -0
  22. data/lib/boson/more.rb +18 -0
  23. data/lib/boson/more_commands.rb +14 -0
  24. data/lib/boson/more_inspector.rb +42 -0
  25. data/lib/boson/more_manager.rb +34 -0
  26. data/lib/boson/more_method_inspector.rb +74 -0
  27. data/lib/boson/more_option_parser.rb +28 -0
  28. data/lib/boson/more_scientist.rb +68 -0
  29. data/lib/boson/more_util.rb +30 -0
  30. data/lib/boson/namespace.rb +31 -0
  31. data/lib/boson/namespacer.rb +117 -0
  32. data/lib/boson/pipe.rb +156 -0
  33. data/lib/boson/pipe_runner.rb +44 -0
  34. data/lib/boson/pipes.rb +75 -0
  35. data/lib/boson/repo.rb +96 -0
  36. data/lib/boson/repo_index.rb +135 -0
  37. data/lib/boson/runner_options.rb +88 -0
  38. data/lib/boson/save.rb +198 -0
  39. data/lib/boson/science.rb +273 -0
  40. data/lib/boson/view.rb +98 -0
  41. data/lib/boson/viewable.rb +48 -0
  42. data/test/alias_test.rb +55 -0
  43. data/test/argument_inspector_test.rb +40 -0
  44. data/test/command_test.rb +22 -0
  45. data/test/commands_test.rb +53 -0
  46. data/test/comment_inspector_test.rb +126 -0
  47. data/test/console_runner_test.rb +58 -0
  48. data/test/deps.rip +4 -0
  49. data/test/file_library_test.rb +41 -0
  50. data/test/gem_library_test.rb +40 -0
  51. data/test/libraries_test.rb +55 -0
  52. data/test/loader_test.rb +38 -0
  53. data/test/module_library_test.rb +30 -0
  54. data/test/more_manager_test.rb +29 -0
  55. data/test/more_method_inspector_test.rb +42 -0
  56. data/test/more_scientist_test.rb +10 -0
  57. data/test/namespacer_test.rb +61 -0
  58. data/test/pipes_test.rb +65 -0
  59. data/test/repo_index_test.rb +123 -0
  60. data/test/repo_test.rb +23 -0
  61. data/test/runner_options_test.rb +29 -0
  62. data/test/save_test.rb +86 -0
  63. data/test/science_test.rb +58 -0
  64. data/test/scientist_test.rb +195 -0
  65. data/test/test_helper.rb +165 -0
  66. data/test/web_test.rb +22 -0
  67. metadata +169 -0
@@ -0,0 +1,183 @@
1
+ require 'boson/save'
2
+ require 'boson/namespace'
3
+ require 'boson/more_util'
4
+ # order of library subclasses matters
5
+ %w{module file gem require local_file}.each {|e| require "boson/libraries/#{e}_library" }
6
+
7
+ module Boson
8
+ # == Naming a Library Module
9
+ # Although you can name a library module almost anything, here's the fine print:
10
+ # * A module can have any name if it's the only module in a library.
11
+ # * If there are multiple modules in a file library, the module's name must
12
+ # be a camelized version of the file's basename
13
+ # i.e. ~/.boson/commands/ruby_core.rb -> RubyCore.
14
+ # * Although modules are evaluated under the Boson::Commands namespace, Boson
15
+ # will warn you about creating modules whose name is the same as a top level
16
+ # class/module. The warning is to encourage users to stay away from
17
+ # error-prone libraries. Once you introduce such a module, _all_ libraries
18
+ # assume the nested module over the top level module and the top level
19
+ # module has to be prefixed with '::' _everywhere_.
20
+ #
21
+ # == Configuration
22
+ # Libraries and their commands can be configured in different ways in this order:
23
+ # * If library is a FileLibrary, commands be configured with a config method
24
+ # attribute (see Inspector).
25
+ # * If a library has a module, you can set library + command attributes via
26
+ # the config() callback (see Loader).
27
+ # === Module Callbacks
28
+ # For libraries that have a module i.e. RunnerLibrary, the following class methods
29
+ # are invoked in the order below when loading a library:
30
+ #
31
+ # [*:config*] This method returns a library's hash of attributes as explained by Library.new. This is useful
32
+ # for distributing libraries with a default configuration. The library attributes specified here
33
+ # are overridden by ones a user has in their config file except for the :commands attribute, which
34
+ # is recursively merged together.
35
+ # [*:append_features*] In addition to its normal behavior, this method's return value determines if a
36
+ # library is loaded in the current environment. This is useful for libraries that you
37
+ # want loaded by default but not in some environments i.e. different ruby versions or
38
+ # in irb but not in script/console. Remember to use super when returning true.
39
+ # [*:included*] In addition to its normal behavior, this method should be used to require external libraries.
40
+ # Although requiring dependencies could be done anywhere in a module, putting dependencies here
41
+ # are encouraged. By not having dependencies hardcoded in a module, it's possible to analyze
42
+ # and view a library's commands without having to install and load its dependencies.
43
+ # If creating commands here, note that conflicts with existing commands won't be detected.
44
+ # [*:after_included*] This method is called after included() to initialize functionality. This is useful for
45
+ # libraries that are primarily executing ruby code i.e. defining ruby extensions or
46
+ # setting irb features. This method isn't called when indexing a library.
47
+ class Library
48
+ ATTRIBUTES << :gems
49
+
50
+ module Libraries
51
+ attr_reader :gems
52
+ def local?
53
+ is_a?(LocalFileLibrary) ||
54
+ (Boson.local_repo && Boson.local_repo.dir == repo_dir)
55
+ end
56
+ end
57
+ include Libraries
58
+
59
+ # [*:object_methods*] Boolean which detects any Object/Kernel methods created when loading a library and automatically
60
+ # adds them to a library's commands. Default is true.
61
+ module LibrariesLoader
62
+ def detect_additions(options={}, &block)
63
+ options[:object_methods] = @object_methods if !@object_methods.nil?
64
+ super(options, &block).tap do |detected|
65
+ if detected[:gems]
66
+ @gems ||= []
67
+ @gems.concat detected[:gems]
68
+ end
69
+ end
70
+ end
71
+
72
+ def module_callbacks
73
+ set_config(@module.config) if @module.respond_to?(:config)
74
+ if @module.respond_to?(:append_features)
75
+ raise AppendFeaturesFalseError unless @module.append_features(Module.new)
76
+ end
77
+ super
78
+ end
79
+
80
+ def before_load_commands
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
+ super
87
+ end
88
+
89
+ def after_include
90
+ @module.after_included if @module.respond_to?(:after_included) && !@index
91
+ super
92
+ end
93
+ end
94
+ include LibrariesLoader
95
+ end
96
+
97
+ # Raised when a library's append_features returns false.
98
+ class AppendFeaturesFalseError < StandardError; end
99
+
100
+ class Manager
101
+ module Libraries
102
+ def handle_load_action_error(library, load_method, err)
103
+ if err.is_a? Boson::AppendFeaturesFalseError
104
+ warn "DEBUG: Library #{library} didn't load due to append_features" if Boson.debug
105
+ else
106
+ super
107
+ end
108
+ end
109
+
110
+ def before_create_commands(lib)
111
+ super
112
+ if lib.is_a?(FileLibrary) && lib.module
113
+ Inspector.add_method_data_to_library(lib)
114
+ end
115
+ end
116
+
117
+ def add_failed_library(library)
118
+ FileLibrary.reset_file_cache(library.to_s)
119
+ super
120
+ end
121
+
122
+ def check_for_uncreated_aliases(lib, commands)
123
+ return if lib.is_a?(GemLibrary)
124
+ super
125
+ end
126
+ end
127
+ include Libraries
128
+ end
129
+
130
+ class Command
131
+ # hack: have to override
132
+ # Array of array args with optional defaults. Scraped with MethodInspector
133
+ def args(lib=library)
134
+ @args = !@args.nil? ? @args : begin
135
+ if lib
136
+ file_string, meth = file_string_and_method_for_args(lib)
137
+ (file_string && meth && (@file_parsed_args = true) &&
138
+ MethodInspector.scrape_arguments(file_string, meth))
139
+ end || false
140
+ end
141
+ end
142
+
143
+ module Libraries
144
+ def file_string_and_method_for_args(lib)
145
+ if !lib.is_a?(ModuleLibrary) && (klass_method = (lib.class_commands || {})[@name])
146
+ klass, meth = klass_method.split(NAMESPACE, 2)
147
+ if (meth_locations = MethodInspector.find_class_method_locations(klass, meth))
148
+ file_string = File.read meth_locations[0]
149
+ end
150
+ elsif File.exists?(lib.library_file || '')
151
+ file_string, meth = FileLibrary.read_library_file(lib.library_file), @name
152
+ end
153
+ [file_string, meth]
154
+ end
155
+ end
156
+ include Libraries
157
+ end
158
+
159
+ class MethodInspector
160
+ module Libraries
161
+ def find_class_method_locations(klass, meth)
162
+ if (klass = Util.any_const_get(klass)) && (meth_location = klass.method(meth).source_location) &&
163
+ meth_location[0]
164
+ meth_location
165
+ end
166
+ end
167
+ end
168
+ extend Libraries
169
+ end
170
+
171
+ module Scientist
172
+ module Libraries
173
+ def help_options
174
+ @global_options[:verbose] ? ['--verbose'] : []
175
+ end
176
+
177
+ def run_help_option(cmd)
178
+ Boson.invoke :usage, cmd.full_name + " " + help_options.join(' ')
179
+ end
180
+ end
181
+ extend Libraries
182
+ end
183
+ end
@@ -0,0 +1,5 @@
1
+ module Boson
2
+ module More
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
data/lib/boson/more.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'boson/alias'
2
+ require 'boson/namespacer'
3
+ require 'boson/more_util'
4
+ require 'boson/more_manager'
5
+ require 'boson/save'
6
+ require 'boson/libraries'
7
+ require 'boson/more_commands'
8
+ require 'boson/pipe_runner'
9
+ require 'boson/more_inspector'
10
+ require 'boson/more_method_inspector'
11
+ require 'boson/more_scientist'
12
+ require 'boson/science'
13
+ require 'boson/runner_options'
14
+ require 'boson/console'
15
+ require 'boson/viewable'
16
+
17
+ module Boson::More
18
+ end
@@ -0,0 +1,14 @@
1
+ require 'boson/commands/core'
2
+ require 'boson/commands/web_core'
3
+ require 'boson/commands/view_core'
4
+ require 'boson/bare_runner'
5
+ require 'fileutils'
6
+
7
+ Boson::BareRunner::DEFAULT_LIBRARIES << Boson::Commands::Core
8
+ Boson::BareRunner::DEFAULT_LIBRARIES << Boson::Commands::WebCore
9
+ Boson::BareRunner::DEFAULT_LIBRARIES << Boson::Commands::ViewCore
10
+
11
+ # TODO: Use Boson.home when it exists
12
+ dir = Dir.home + '/.boson/.more_commands'
13
+ FileUtils.mkdir_p dir
14
+ Boson.repos << Boson::Repo.new(dir)
@@ -0,0 +1,42 @@
1
+ require 'boson/comment_inspector'
2
+ require 'boson/more_method_inspector'
3
+
4
+ module Boson
5
+ # When deciding whether to use commented or normal Module methods, remember that commented Module methods allow
6
+ # independence from Boson (useful for testing).
7
+ # See CommentInspector for more about commented method attributes.
8
+ class Inspector
9
+ module MoreInspector
10
+ def add_data
11
+ super
12
+ add_comment_scraped_data
13
+ end
14
+
15
+ def add_comment_scraped_data
16
+ (@store[:method_locations] || []).select {|k,(f,l)| f == @library_file }.each do |cmd, (file, lineno)|
17
+ scraped = CommentInspector.scrape(FileLibrary.read_library_file(file), lineno, MethodInspector.instance.current_module)
18
+ @commands_hash[cmd] ||= {}
19
+ MethodInspector::METHODS.each do |e|
20
+ add_valid_data_to_config(e, scraped[e], cmd)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ include MoreInspector
26
+ end
27
+
28
+ # This module also saves method locations so CommentInspector
29
+ # can scrape their commented method attributes.
30
+ class MethodInspector
31
+ module MoreInspector
32
+ def inspector_in_file?(meth, inspector_method)
33
+ return false if !super
34
+ if File.exists?(file_line[0]) && (options = CommentInspector.scrape(
35
+ FileLibrary.read_library_file(file_line[0]), file_line[1], @current_module, inspector_method) )
36
+ (store[inspector_method] ||= {})[meth] = options
37
+ end
38
+ end
39
+ end
40
+ include MoreInspector
41
+ end
42
+ end
@@ -0,0 +1,34 @@
1
+ # Adds dependencies to manager
2
+ module Boson
3
+ class Manager
4
+ module MoreManager
5
+ def load_dependencies(lib, options={})
6
+ lib_dependencies[lib] = Array(lib.dependencies).map do |e|
7
+ next if self.class.loaded?(e)
8
+ load_once(e, options.merge(:dependency=>true)) ||
9
+ raise(LoaderError, "Can't load dependency #{e}")
10
+ end.compact
11
+ end
12
+
13
+ def lib_dependencies
14
+ @lib_dependencies ||= {}
15
+ end
16
+
17
+ def during_after_load
18
+ (lib_dependencies[@library] || []).each do |e|
19
+ create_commands(e)
20
+ self.class.add_library(e)
21
+ puts "Loaded library dependency #{e.name}" if verbose
22
+ end
23
+ end
24
+ end
25
+ include MoreManager
26
+ end
27
+
28
+ # [*:dependencies*] An array of libraries that this library depends on. A library won't load
29
+ # unless its dependencies are loaded first.
30
+ class Library
31
+ ATTRIBUTES << :dependencies
32
+ attr_reader :dependencies
33
+ end
34
+ end
@@ -0,0 +1,74 @@
1
+ module Boson
2
+ class MethodInspector
3
+ # Returns argument arrays
4
+ def self.scrape_arguments(file_string, meth)
5
+ tabspace = "[ \t]"
6
+ if match = /^#{tabspace}*def#{tabspace}+(?:\w+\.)?#{Regexp.quote(meth)}#{tabspace}*($|(?:\(|\s+)([^\n\)]+)\s*\)?\s*$)/.match(file_string)
7
+ (match.to_a[2] || '').strip.split(/\s*,\s*/).map {|e| e.split(/\s*=\s*/)}
8
+ end
9
+ end
10
+
11
+ # investigate why this can't be included
12
+ def inspector_in_file?(meth, inspector_method)
13
+ !(file_line = store[:method_locations] && store[:method_locations][meth]) ?
14
+ false : true
15
+ end
16
+
17
+ module MoreMethodInspector
18
+ def during_new_method_added(mod, meth)
19
+ if store[:temp].size < ALL_METHODS.size
20
+ store[:method_locations] ||= {}
21
+ if (result = find_method_locations(mod, meth))
22
+ store[:method_locations][meth.to_s] = result
23
+ end
24
+ end
25
+ end
26
+
27
+ def set_arguments(mod, meth)
28
+ store[:args] ||= {}
29
+ file = find_method_locations(mod, meth)[0]
30
+
31
+ if File.exists?(file)
32
+ body = File.read(file)
33
+ store[:args][meth.to_s] = self.class.scrape_arguments body, meth
34
+ end
35
+ end
36
+
37
+ def has_inspector_method?(meth, inspector)
38
+ (store[inspector] && store[inspector].key?(meth.to_s)) ||
39
+ inspector_in_file?(meth.to_s, inspector)
40
+ end
41
+
42
+ # Returns an array of the file and line number at which a method starts
43
+ # using a method
44
+ def find_method_locations(mod, meth)
45
+ mod.instance_method(meth).source_location
46
+ end
47
+ end
48
+ include MoreMethodInspector
49
+ end
50
+
51
+ class Command
52
+ module MoreMethodInspector
53
+ # One-line usage of args with default values
54
+ def basic_usage
55
+ return '' if options.nil? && args.nil?
56
+ args ? usage_args.map {|e|
57
+ (e.size < 2) ? "[#{e[0]}]" :
58
+ "[#{e[0]}=#{@file_parsed_args ? e[1] : e[1].inspect}]"
59
+ }.join(' ') : '[*unknown]'
60
+ end
61
+
62
+ # Help string for options if a command has it.
63
+ def option_help
64
+ @options ? option_parser.to_s : ''
65
+ end
66
+
67
+ # keep?
68
+ def usage
69
+ basic_usage + option_help
70
+ end
71
+ end
72
+ include MoreMethodInspector
73
+ end
74
+ end
@@ -0,0 +1,28 @@
1
+ module Boson
2
+ class OptionParser
3
+ module MoreOptionParser
4
+ # Given options to pass to OptionParser.new, this method parses ARGV and
5
+ # returns the remaining arguments and a hash of parsed options. This is
6
+ # useful for scripts outside of Boson.
7
+ def parse(options, args=ARGV)
8
+ @opt_parser ||= new(options)
9
+ parsed_options = @opt_parser.parse(args)
10
+ [@opt_parser.non_opts, parsed_options]
11
+ end
12
+
13
+ # Usage string summarizing options defined in parse
14
+ def usage
15
+ @opt_parser.to_s
16
+ end
17
+
18
+ def make_mergeable!(opts)
19
+ opts.each {|k,v|
20
+ if !v.is_a?(Hash) && !v.is_a?(Symbol)
21
+ opts[k] = {:default=>v}
22
+ end
23
+ }
24
+ end
25
+ end
26
+ extend MoreOptionParser
27
+ end
28
+ end
@@ -0,0 +1,68 @@
1
+ module Boson
2
+ # Any command with options comes with basic global options. For example '-hv'
3
+ # on an option command prints a help summarizing global and local options.
4
+ # Another basic global option is --pretend. This option displays what global
5
+ # options have been parsed and the actual arguments to be passed to a
6
+ # command if executed. For example:
7
+ #
8
+ # # Define this command in a library
9
+ # options :level=>:numeric, :verbose=>:boolean
10
+ # def foo(*args)
11
+ # args
12
+ # end
13
+ #
14
+ # irb>> foo 'testin -p -l=1'
15
+ # Arguments: ["testin", {:level=>1}]
16
+ # Global options: {:pretend=>true}
17
+ #
18
+ # If a global option conflicts with a local option, the local option takes
19
+ # precedence. You can get around this by passing global options after a '-'.
20
+ # For example, if the global option -h (--help) conflicts with a local -h
21
+ # (--force):
22
+ # foo 'arg1 -v -f - -f=f1,f2'
23
+ # # is the same as
24
+ # foo 'arg1 -v --fields=f1,f2 -f'
25
+ class OptionCommand
26
+ BASIC_OPTIONS.update(
27
+ :verbose=>{:type=>:boolean, :desc=>"Increase verbosity for help, errors, etc."},
28
+ :pretend=>{:type=>:boolean,
29
+ :desc=>"Display what a command would execute without executing it"}
30
+ )
31
+ end
32
+ module Scientist
33
+ module MoreScientist
34
+ def during_analyze(&block)
35
+ run_pretend_option(@args)
36
+ super unless @global_options[:pretend]
37
+ end
38
+
39
+ def analyze(*)
40
+ super
41
+ rescue OptionCommand::CommandArgumentError
42
+ run_pretend_option(@args ||= [])
43
+ return if !@global_options[:pretend] &&
44
+ run_verbose_help(option_command, @original_args)
45
+ raise unless @global_options[:pretend]
46
+ end
47
+
48
+ private
49
+ def run_verbose_help(option_command, original_args)
50
+ global_opts = option_command.parse_global_options(original_args)
51
+ if global_opts[:help] && global_opts[:verbose]
52
+ @global_options = global_opts
53
+ run_help_option @command
54
+ return true
55
+ end
56
+ false
57
+ end
58
+
59
+ def run_pretend_option(args)
60
+ if @global_options[:verbose] || @global_options[:pretend]
61
+ puts "Arguments: #{args.inspect}",
62
+ "Global options: #{@global_options.inspect}"
63
+ end
64
+ end
65
+ end
66
+ extend MoreScientist
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ module Boson
2
+ module MoreUtil
3
+ # Behaves just like the unix which command, returning the full path to an executable based on ENV['PATH'].
4
+ def which(command)
5
+ ENV['PATH'].split(File::PATH_SEPARATOR).map {|e| File.join(e, command) }.find {|e| File.exists?(e) }
6
+ end
7
+
8
+ # Deep copies any object if it can be marshaled. Useful for deep hashes.
9
+ def deep_copy(obj)
10
+ Marshal::load(Marshal::dump(obj))
11
+ end
12
+
13
+ # Safely calls require, returning false if LoadError occurs.
14
+ def safe_require(lib)
15
+ begin
16
+ require lib
17
+ true
18
+ rescue LoadError
19
+ false
20
+ end
21
+ end
22
+
23
+ # Returns name of top level class that conflicts if it exists. For example, for base module Boson::Commands,
24
+ # Boson::Commands::Hirb conflicts with Hirb if Hirb exists.
25
+ def top_level_class_conflict(base_module, conflicting_module)
26
+ (conflicting_module =~ /^#{base_module}.*::([^:]+)/) && Object.const_defined?($1) && $1
27
+ end
28
+ end
29
+ Util.extend MoreUtil
30
+ end
@@ -0,0 +1,31 @@
1
+ module Boson
2
+ # Used in all things namespace.
3
+ class Namespace
4
+ # Hash of created namespace names to namespace objects
5
+ def self.namespaces
6
+ @namespaces ||= {}
7
+ end
8
+
9
+ # Creates a namespace given its name and the library it belongs to.
10
+ def self.create(name, library)
11
+ namespaces[name.to_s] = new(name, library)
12
+ Commands::Namespace.send(:define_method, name) { Boson::Namespace.namespaces[name.to_s] }
13
+ end
14
+
15
+ def initialize(name, library)
16
+ raise ArgumentError unless library.module
17
+ @name, @library = name.to_s, library
18
+ class <<self; self end.send :include, @library.module
19
+ end
20
+
21
+ def method_missing(method, *args, &block)
22
+ Boson.can_invoke?(method) ? Boson.invoke(method, *args, &block) : super
23
+ end
24
+
25
+ #:startdoc:
26
+ # List of subcommands for the namespace.
27
+ def boson_commands
28
+ @library.module.instance_methods.map {|e| e.to_s }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,117 @@
1
+ require 'boson/namespace'
2
+
3
+ module Boson
4
+ NAMESPACE = '.' # Delimits namespace from command
5
+ module Commands
6
+ # Used for defining namespaces.
7
+ module Namespace; end
8
+ end
9
+ Universe.send :include, Commands::Namespace
10
+
11
+ module Namespacer
12
+ # Invoke command string even with namespaces
13
+ def full_invoke(cmd, args) #:nodoc:
14
+ command, subcommand = cmd.include?(NAMESPACE) ? cmd.split(NAMESPACE, 2) : [cmd, nil]
15
+ dispatcher = subcommand ? Boson.invoke(command) : Boson.main_object
16
+ dispatcher.send(subcommand || command, *args)
17
+ end
18
+ end
19
+ extend Namespacer
20
+
21
+ class Command
22
+ INIT_ATTRIBUTES << :namespace
23
+ module NamespacerClass
24
+ def library_attributes(library)
25
+ super.update(namespace: library.namespace)
26
+ end
27
+
28
+ def find(command, commands=Boson.commands)
29
+ if command.to_s.include?(NAMESPACE)
30
+ command, subcommand = command.to_s.split(NAMESPACE, 2)
31
+ commands.find {|current_command|
32
+ [current_command.name, current_command.alias].include?(subcommand) &&
33
+ current_command.library && (current_command.library.namespace == command)
34
+ }
35
+ else
36
+ commands.find {|e| [e.name, e.alias].include?(command) && !e.namespace}
37
+ end
38
+ end
39
+ end
40
+ extend NamespacerClass
41
+
42
+ module Namespacer
43
+ attr_accessor :namespace
44
+
45
+ # Full name is only different than name if a command has a namespace.
46
+ # The full name should be what you would type to execute the command.
47
+ def full_name
48
+ @namespace ? "#{@namespace}.#{@name}" : @name
49
+ end
50
+ end
51
+ include Namespacer
52
+ end
53
+
54
+ class Library
55
+ # [*:namespace*] Boolean or string which namespaces a library. When true, the library is automatically namespaced
56
+ # to the library's name. When a string, the library is namespaced to the string. Default is nil.
57
+ module Namespacer
58
+ # Optional namespace name for a library. When enabled defaults to a library's name.
59
+ attr_writer :namespace
60
+
61
+ # The object a library uses for executing its commands.
62
+ def namespace_object
63
+ @namespace_object ||= namespace ? Boson.invoke(namespace) : Boson.main_object
64
+ end
65
+
66
+ def namespace(orig=@namespace)
67
+ @namespace = [String,FalseClass].include?(orig.class) ? orig : begin
68
+ if (@namespace == true || (Boson.config[:auto_namespace] && !@index))
69
+ @namespace = clean_name
70
+ else
71
+ @namespace = false
72
+ end
73
+ end
74
+ end
75
+ end
76
+ include Namespacer
77
+
78
+ module NamespaceLoader
79
+ attr_reader :indexed_namespace, :object_namespace
80
+
81
+ def handle_method_conflict_error(err)
82
+ if Boson.config[:error_method_conflicts] || namespace
83
+ raise err
84
+ else
85
+ @namespace = clean_name
86
+ @method_conflict = true
87
+ $stderr.puts "#{err.message}. Attempting load into the namespace #{@namespace}..."
88
+ load_commands
89
+ end
90
+ end
91
+
92
+ def actual_load_commands
93
+ @namespace = clean_name if @object_namespace
94
+ namespace ? Namespace.create(namespace, self) : include_in_universe
95
+ end
96
+
97
+ def method_conflicts
98
+ namespace ?
99
+ (Boson.can_invoke?(namespace) ? [namespace] : []) :
100
+ super
101
+ end
102
+
103
+ def clean_library_commands
104
+ @commands.delete(namespace) if namespace
105
+ @commands += Boson.invoke(namespace).boson_commands if namespace && !@pre_defined_commands
106
+ super
107
+ end
108
+
109
+ def set_library_commands
110
+ @namespace = false if !(@module || @class_commands)
111
+ super
112
+ @indexed_namespace = (@namespace == false) ? nil : @namespace if @index
113
+ end
114
+ end
115
+ include NamespaceLoader
116
+ end
117
+ end