boson-more 0.1.0

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