boson 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -3,6 +3,8 @@ A command/task framework similar to rake and thor that opens your ruby universe
3
3
  and irb. For my libraries that use this, see {irbfiles}[http://github.com/cldwalker/irbfiles].
4
4
  Works with Ruby 1.8.6 and 1.9.1.
5
5
 
6
+ Note: To read a linkable version of this README, {see here}[http://tagaholic.me/boson/doc/].
7
+
6
8
  == Features
7
9
  * Commands are just methods extended for a given object, the default being the top level object, main.
8
10
  * Commands are accessible from the commandline (Boson::BinRunner) or irb (Boson::ConsoleRunner).
@@ -11,7 +13,9 @@ Works with Ruby 1.8.6 and 1.9.1.
11
13
  * Comes with default commands to load, search, list and install commands and libraries (Boson::Commands::Core).
12
14
  * Commands can be full-blown commandline apps thanks to powerful options (Boson::OptionParser)
13
15
  and hirb's views.
14
- * Commands can have views toggled without adding view code to your original command (Boson::Scientist).
16
+ * There are 5 default option types: boolean, array, string, hash and numeric. Custom option types
17
+ can be defined to map to any Ruby class with one method (Boson::Options).
18
+ * Commands can have views toggled without adding view code to the original command (Boson::Scientist).
15
19
  * Command libraries are social as a user can install them from a url and then customize command
16
20
  names and options without changing the original library.
17
21
  * Namespaces are optional and when used are methods which allow for method_missing magic.
@@ -85,7 +89,7 @@ Having done that, let's start up irb:
85
89
  2 rows in set
86
90
 
87
91
  # Let's install another library
88
- bash> boson install http://github.com/cldwalker/irbfiles/raw/master/boson/commands/irb_core.rb
92
+ bash> boson install http://github.com/cldwalker/irbfiles/raw/master/boson/commands/public/irb_core.rb
89
93
  Saved to /Users/bozo/.boson/commands/irb_core.rb
90
94
 
91
95
  # Let's start irb ...
@@ -132,18 +136,26 @@ Having done that, let's start up irb:
132
136
  See Boson::FileLibrary or here[http://tagaholic.me/boson/doc/classes/Boson/FileLibrary.html].
133
137
 
134
138
  == Todo
135
- * More docs
136
139
  * More tests
137
140
  * Making commands out of existing gems easier and more powerful
138
141
  * Better local repositories, perhaps a BosonFile
139
142
  * Consider managing extensions to core and standard libraries
140
143
  * Consider dropping alias gem dependency if not using its full potential
141
144
 
145
+ == Bugs/Issues
146
+ Please report them {on github}[http://github.com/cldwalker/boson/issues].
147
+
142
148
  == Motivation
143
149
  My {tagging obsession}[http://github.com/cldwalker/tag-tree] from the ruby console.
144
150
 
151
+ == Links
152
+ * http://tagaholic.me/2009/10/14/boson-command-your-ruby-universe.html
153
+ * http://tagaholic.me/2009/10/15/boson-and-hirb-interactions.html
154
+ * http://tagaholic.me/2009/10/19/how-boson-enhances-your-irb-experience.html
155
+
145
156
  == Acknowledgements
146
157
  Boson stands on the shoulders of these people and their ideas:
158
+ * Yehuda Katz for inspiring me with Thor's power and elegant design
147
159
  * Yehuda Katz and Daniel Berger for an awesome option parser (Boson::OptionParser)
148
160
  * Dave Thomas for scraping a method's comments (Boson::CommentInspector)
149
161
  * Mauricio Fernandez for scraping a method's arguments (Boson::ArgumentInspector)
data/Rakefile CHANGED
@@ -21,11 +21,11 @@ begin
21
21
  s.description = "A command/task framework similar to rake and thor that opens your ruby universe to the commandline and irb."
22
22
  s.summary = "Boson provides users with the power to turn any ruby method into a full-fledged commandline tool. Boson achieves this with powerful options (borrowed from thor) and views (thanks to hirb). Some other unique features that differentiate it from rake and thor include being accessible from irb and the commandline, being able to write boson commands in non-dsl ruby and toggling a pretty view of a command's output without additional view code."
23
23
  s.email = "gabriel.horner@gmail.com"
24
- s.homepage = "http://github.com/cldwalker/boson"
24
+ s.homepage = "http://tagaholic.me/boson/"
25
25
  s.authors = ["Gabriel Horner"]
26
26
  s.has_rdoc = true
27
27
  s.rubyforge_project = 'tagaholic'
28
- s.add_dependency 'hirb', '>= 0.2.7'
28
+ s.add_dependency 'hirb', '>= 0.2.8'
29
29
  s.add_dependency 'alias', '>= 0.2.1'
30
30
  s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
31
31
  s.files = FileList["Rakefile", "VERSION.yml", "README.rdoc", "LICENSE.txt", "{bin,lib,test}/**/*"]
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 1
3
+ :minor: 2
4
4
  :patch: 0
data/lib/boson.rb CHANGED
@@ -4,9 +4,20 @@ $:.unshift File.dirname(__FILE__) unless $:.include? File.expand_path(File.dirna
4
4
  %w{argument method comment}.each {|e| require "boson/inspectors/#{e}_inspector" }
5
5
  # order of library subclasses matters
6
6
  %w{module file gem require}.each {|e| require "boson/libraries/#{e}_library" }
7
- %w{namespace view command util commands option_parser index scientist}.each {|e| require "boson/#{e}" }
7
+ (%w{namespace view command util commands option_parser options} +
8
+ %w{index scientist}).each {|e| require "boson/#{e}" }
8
9
 
9
10
  # This module stores the libraries, commands, repos and main object used throughout Boson.
11
+ #
12
+ # Useful documentation links:
13
+ # * Boson::BinRunner - Runs the boson executable
14
+ # * Boson::ConsoleRunner - Runs Boson from the ruby console
15
+ # * Boson::Repo.config - Explains main config file
16
+ # * Boson::Library - All about libraries
17
+ # * Boson::FileLibrary - Explains creating libraries as files
18
+ # * Boson::Loader - Explains library module callbacks
19
+ # * Boson::OptionParser - All about options
20
+ # * Boson::Scientist - Explains how commands can be both shell-commands and normal ruby methods
10
21
  module Boson
11
22
  # Module which is extended by Boson.main_object to give it command functionality.
12
23
  module Universe; include Commands::Namespace; end
@@ -28,7 +39,7 @@ module Boson
28
39
 
29
40
  # The main required repository which defaults to ~/.boson.
30
41
  def repo
31
- @repo ||= Repo.new("#{ENV['HOME']}/.boson")
42
+ @repo ||= Repo.new("#{Util.find_home}/.boson")
32
43
  end
33
44
 
34
45
  # An optional local repository which defaults to ./lib/boson or ./.boson.
@@ -64,9 +75,16 @@ module Boson
64
75
  main_object.send(*args, &block)
65
76
  end
66
77
 
78
+ # Invoke command string even with namespaces
79
+ def full_invoke(cmd, args) #:nodoc:
80
+ command, subcommand = cmd.include?('.') ? cmd.split('.', 2) : [cmd, nil]
81
+ dispatcher = subcommand ? Boson.invoke(command) : Boson.main_object
82
+ dispatcher.send(subcommand || command, *args)
83
+ end
84
+
67
85
  # Boolean indicating if the main object can invoke the given method/command.
68
- def can_invoke?(meth)
69
- Boson.main_object.respond_to? meth
86
+ def can_invoke?(meth, priv=true)
87
+ Boson.main_object.respond_to? meth, priv
70
88
  end
71
89
  end
72
90
 
data/lib/boson/command.rb CHANGED
@@ -19,7 +19,7 @@ module Boson
19
19
  end
20
20
 
21
21
  ATTRIBUTES = [:name, :lib, :alias, :description, :options, :args]
22
- attr_accessor *(ATTRIBUTES + [:render_options, :namespace])
22
+ attr_accessor *(ATTRIBUTES + [:render_options, :namespace, :default_option])
23
23
  # A hash of attributes which map to instance variables and values. :name
24
24
  # and :lib are required keys.
25
25
  #
@@ -32,6 +32,11 @@ module Boson
32
32
  # important for commands that have options/render_options. Its value can be an array
33
33
  # (as ArgumentInspector.scrape_with_eval produces), a number representing
34
34
  # the number of arguments or '*' if the command has a variable number of arguments.
35
+ # * *:default_option* Only for an option command that has one argument. This treats the given
36
+ # option as an optional first argument. Example:
37
+ # # For a command with default option 'query' and options --query and -v
38
+ # 'some -v' -> '--query=some -v'
39
+ # '-v' -> '-v'
35
40
  def initialize(hash)
36
41
  @name = hash[:name] or raise ArgumentError
37
42
  @lib = hash[:lib] or raise ArgumentError
@@ -40,6 +45,7 @@ module Boson
40
45
  @render_options = hash[:render_options] if hash[:render_options]
41
46
  @options = hash[:options] if hash[:options]
42
47
  @namespace = hash[:namespace] if hash[:namespace]
48
+ @default_option = hash[:default_option] if hash[:default_option]
43
49
  if hash[:args]
44
50
  if hash[:args].is_a?(Array)
45
51
  @args = hash[:args]
@@ -80,7 +86,9 @@ module Boson
80
86
  # Usage string for command, created from options and args.
81
87
  def usage
82
88
  return '' if options.nil? && args.nil?
83
- usage_args = args && @options ? args[0..-2] : args
89
+ usage_args = args && @options && !has_splat_args? ?
90
+ (@default_option ? [[@default_option.to_s, @file_parsed_args ? ''.inspect : '']] + args[0..-2] :
91
+ args[0..-2]) : args
84
92
  str = args ? usage_args.map {|e|
85
93
  (e.size < 2) ? "[#{e[0]}]" : "[#{e[0]}=#{@file_parsed_args ? e[1] : e[1].inspect}]"
86
94
  }.join(' ') : '[*unknown]'
@@ -116,11 +124,11 @@ module Boson
116
124
  @args.map! {|e| e.size == 2 ? [e[0], e[1].inspect] : e }
117
125
  @file_parsed_args = true
118
126
  end
119
- [@name, @alias, @lib, @description, @options, @render_options, @args]
127
+ [@name, @alias, @lib, @description, @options, @render_options, @args, @default_option]
120
128
  end
121
129
 
122
130
  def marshal_load(ary)
123
- @name, @alias, @lib, @description, @options, @render_options, @args = ary
131
+ @name, @alias, @lib, @description, @options, @render_options, @args, @default_option = ary
124
132
  end
125
133
  #:startdoc:
126
134
  end
@@ -1,5 +1,7 @@
1
1
  module Boson::Commands::Core #:nodoc:
2
- def self.config
2
+ extend self
3
+
4
+ def config
3
5
  command_attributes = Boson::Command::ATTRIBUTES + [:usage, :full_name, :render_options]
4
6
  library_attributes = Boson::Library::ATTRIBUTES + [:library_type]
5
7
 
@@ -7,35 +9,33 @@ module Boson::Commands::Core #:nodoc:
7
9
  'render'=>{:description=>"Render any object using Hirb"},
8
10
  'menu'=>{:description=>"Provide a menu to multi-select elements from a given array"},
9
11
  'usage'=>{:description=>"Print a command's usage", :options=>{[:verbose, :V]=>:boolean}},
10
- 'commands'=>{ :description=>"List or search commands",
11
- :options=>{:query_fields=>{:default=>['full_name'], :values=>command_attributes, :desc=>"Searchable fields"},
12
- :index=>{:type=>:boolean, :desc=>"Searches index"}},
13
- :render_options=>{:fields=>{:default=>[:full_name, :lib, :alias, :usage, :description], :values=>command_attributes} }
12
+ 'commands'=>{
13
+ :description=>"List or search commands", :default_option=>'query',
14
+ :options=>{ :index=>{:type=>:boolean, :desc=>"Searches index"}},
15
+ :render_options=>{
16
+ :query=>{:keys=>command_attributes, :default_keys=>'full_name'},
17
+ :fields=>{:default=>[:full_name, :lib, :alias, :usage, :description], :values=>command_attributes} }
14
18
  },
15
- 'libraries'=>{ :description=>"List or search libraries",
16
- :options=>{:query_fields=>{:default=>['name'], :values=>library_attributes, :desc=>"Searchable fields"},
17
- :index=>{:type=>:boolean, :desc=>"Searches index"} },
19
+ 'libraries'=>{
20
+ :description=>"List or search libraries", :default_option=>'query',
21
+ :options=>{ :index=>{:type=>:boolean, :desc=>"Searches index"} },
18
22
  :render_options=>{
23
+ :query=>{:keys=>library_attributes, :default_keys=>'name'},
19
24
  :fields=>{:default=>[:name, :commands, :gems, :library_type], :values=>library_attributes},
20
- :filters=>{:default=>{:gems=>[:join, ','],:commands=>:size}} }
25
+ :filters=>{:default=>{:gems=>[:join, ','],:commands=>:size}, :desc=>"Filters to apply to library fields" }}
21
26
  },
22
27
  'load_library'=>{:description=>"Load/reload a library", :options=>{:reload=>:boolean, [:verbose,:V]=>true}}
23
28
  }
24
29
 
25
- {:library_file=>File.expand_path(__FILE__), :commands=>commands}
30
+ {:namespace=>false, :library_file=>File.expand_path(__FILE__), :commands=>commands}
26
31
  end
27
32
 
28
- def commands(query='', options={})
29
- query_fields = options[:query_fields] || ['full_name']
30
- Boson::Index.read if options[:index]
31
- commands = options[:index] ? Boson::Index.commands : Boson.commands
32
- query_fields.map {|e| commands.select {|f| f.send(e).to_s =~ /#{query}/i } }.flatten.uniq
33
+ def commands(options={})
34
+ options[:index] ? (Boson::Index.read || true) && Boson::Index.commands : Boson.commands
33
35
  end
34
36
 
35
- def libraries(query='', options={})
36
- Boson::Index.read if options[:index]
37
- libraries = options[:index] ? Boson::Index.libraries : Boson.libraries
38
- options[:query_fields].map {|e| libraries.select {|f| f.send(e).to_s =~ /#{query}/i } }.flatten.uniq
37
+ def libraries(options={})
38
+ options[:index] ? (Boson::Index.read || true) && Boson::Index.libraries : Boson.libraries
39
39
  end
40
40
 
41
41
  def load_library(library, options={})
@@ -1,23 +1,36 @@
1
1
  module Boson::Commands::WebCore #:nodoc:
2
- def self.config
2
+ extend self
3
+
4
+ def config
3
5
  descriptions = {
4
6
  :install=>"Installs a library by url. Library should then be loaded with load_library.",
5
- :browser=>"Opens urls in a browser on a Mac", :get=>"Gets the body of a url" }
7
+ :browser=>"Opens urls in a browser on a Mac", :get=>"Gets the body of a url", :post=>'Posts to url' }
6
8
  commands = descriptions.inject({}) {|h,(k,v)| h[k.to_s] = {:description=>v}; h}
7
9
  commands['install'][:options] = {:name=>{:type=>:string, :desc=>"Library name to save to"},
8
10
  :force=>{:type=>:boolean, :desc=>'Overwrites an existing library'},
9
11
  :module_wrap=>{:type=>:boolean, :desc=>"Wraps a module around install using library name"},
10
12
  :method_wrap=>{:type=>:boolean, :desc=>"Wraps a method and module around installe library using library name"}}
11
- {:library_file=>File.expand_path(__FILE__), :commands=>commands}
13
+ {:library_file=>File.expand_path(__FILE__), :commands=>commands, :namespace=>false}
12
14
  end
13
15
 
14
- def get(url)
16
+ def get(url, options={})
15
17
  %w{uri net/http}.each {|e| require e }
16
- Net::HTTP.get(URI.parse(url))
18
+ if options[:success_only]
19
+ url = URI.parse(url)
20
+ res = Net::HTTP.start(url.host, url.port) {|http| http.get(url.request_uri) }
21
+ res.code == '200' ? res.body : nil
22
+ else
23
+ Net::HTTP.get(URI.parse(url))
24
+ end
17
25
  rescue
18
26
  raise "Error opening #{url}"
19
27
  end
20
28
 
29
+ def post(url, options={})
30
+ %w{uri net/http}.each {|e| require e }
31
+ Net::HTTP.post_form(URI.parse(url), options)
32
+ end
33
+
21
34
  def install(url, options={})
22
35
  options[:name] ||= strip_name_from_url(url)
23
36
  return puts("Please give a library name for this url.") if options[:name].empty?
data/lib/boson/index.rb CHANGED
@@ -1,22 +1,30 @@
1
1
  require 'digest/md5'
2
2
  module Boson
3
- # Used in all things indexing i.e. saving state for boson's commandline.
3
+ # This class is used by BinRunner to index/store all of Boson's commands and libraries. When this index updates,
4
+ # it detects library files whose md5 hash have changed and reindexes them. The index is stored with Marshal
5
+ # at ~/.boson/config/index.marshal. Since the index is marshaled, putting lambdas/procs in it will break it.
6
+ # If your index gets corrupted, simply delete it and next time Boson needs it, the index will be recreated.
4
7
  module Index
5
8
  extend self
6
9
  attr_reader :libraries, :commands
7
10
 
8
11
  # Updates the index.
9
12
  def update(options={})
10
- options[:all] = true if !exists? && !options.key?(:all)
11
- libraries_to_update = options[:all] ? Runner.all_libraries : changed_libraries
13
+ libraries_to_update = !exists? ? Runner.all_libraries : options[:libraries] || changed_libraries
12
14
  read_and_transfer(libraries_to_update)
13
15
  if options[:verbose]
14
- puts options[:all] ? "Generating index for all #{libraries_to_update.size} libraries. Patience ... is a bitch" :
16
+ puts !exists? ? "Generating index for all #{libraries_to_update.size} libraries. Patience ... is a bitch" :
15
17
  (libraries_to_update.empty? ? "No libraries indexed" :
16
18
  "Indexing the following libraries: #{libraries_to_update.join(', ')}")
17
19
  end
18
- Manager.load(libraries_to_update, options.merge(:index=>true)) unless libraries_to_update.empty?
19
- write
20
+ Manager.failed_libraries = []
21
+ unless libraries_to_update.empty?
22
+ Manager.load(libraries_to_update, options.merge(:index=>true))
23
+ unless Manager.failed_libraries.empty?
24
+ $stderr.puts("Error: These libraries failed to load while indexing: #{Manager.failed_libraries.join(', ')}")
25
+ end
26
+ end
27
+ write(Manager.failed_libraries)
20
28
  end
21
29
 
22
30
  # Reads and initializes index.
@@ -24,22 +32,25 @@ module Boson
24
32
  return if @read
25
33
  @libraries, @commands, @lib_hashes = exists? ? Marshal.load(File.read(marshal_file)) : [[], [], {}]
26
34
  delete_stale_libraries_and_commands
27
- set_latest_namespaces
35
+ set_command_namespaces
28
36
  @read = true
29
37
  end
30
38
 
31
39
  # Writes/saves current index to config/index.marshal.
32
- def write
33
- save_marshal_index Marshal.dump([Boson.libraries, Boson.commands, latest_hashes])
40
+ def write(failed_libraries=[])
41
+ latest = latest_hashes
42
+ failed_libraries.each {|e| latest.delete(e) }
43
+ save_marshal_index Marshal.dump([Boson.libraries, Boson.commands, latest])
34
44
  end
35
45
 
36
46
  #:stopdoc:
37
47
  def read_and_transfer(ignored_libraries=[])
38
48
  read
39
49
  existing_libraries = (Boson.libraries.map {|e| e.name} + ignored_libraries).uniq
40
- Boson.libraries += @libraries.select {|e| !existing_libraries.include?(e.name)}
41
- existing_commands = Boson.commands.map {|e| e.name} #td: consider namespace
42
- Boson.commands += @commands.select {|e| !existing_commands.include?(e.name) && !ignored_libraries.include?(e.lib)}
50
+ libraries_to_add = @libraries.select {|e| !existing_libraries.include?(e.name)}
51
+ Boson.libraries += libraries_to_add
52
+ # depends on saved commands being correctly associated with saved libraries
53
+ Boson.commands += libraries_to_add.map {|e| e.command_objects(e.commands, @commands) }.flatten
43
54
  end
44
55
 
45
56
  def exists?
@@ -58,21 +69,19 @@ module Boson
58
69
  @commands.delete_if {|e| names_to_delete.include? e.lib }
59
70
  end
60
71
 
61
- # get latest namespaces from config files
62
- def set_latest_namespaces
63
- namespace_libs = Boson.repo.config[:auto_namespace] ? @libraries.map {|e| [e.name, {:namespace=>true}]} :
64
- Boson.repo.config[:libraries].select {|k,v| v && v[:namespace] }
72
+ # set namespaces for commands
73
+ def set_command_namespaces
65
74
  lib_commands = @commands.inject({}) {|t,e| (t[e.lib] ||= []) << e; t }
66
- namespace_libs.each {|name, hash|
67
- if (lib = @libraries.find {|l| l.name == name})
68
- lib.namespace = (hash[:namespace] == true) ? lib.clean_name : hash[:namespace]
69
- (lib_commands[lib.name] || []).each {|e| e.namespace = lib.namespace }
70
- end
75
+ namespace_libs = @libraries.select {|e| e.namespace(e.indexed_namespace) }
76
+ namespace_libs.each {|lib|
77
+ (lib_commands[lib.name] || []).each {|e| e.namespace = lib.namespace }
71
78
  }
72
79
  end
73
80
 
74
81
  def namespaces
75
- @libraries.map {|e| e.namespace }.compact
82
+ nsps = @libraries.map {|e| e.namespace }.compact
83
+ nsps.delete(false)
84
+ nsps
76
85
  end
77
86
 
78
87
  def all_main_methods
@@ -49,20 +49,26 @@ module Boson
49
49
  (MethodInspector::METHODS + [:method_args]).each do |e|
50
50
  key = command_key(e)
51
51
  (@store[e] || []).each do |cmd, val|
52
- if no_command_config_for(cmd, key)
53
- (@commands_hash[cmd] ||= {})[key] = val
54
- end
52
+ @commands_hash[cmd] ||= {}
53
+ add_scraped_data_to_config(key, val, cmd)
55
54
  end
56
55
  end
57
56
  end
58
57
 
58
+ def add_scraped_data_to_config(key, value, cmd)
59
+ if value.is_a?(Hash)
60
+ @commands_hash[cmd][key] = Util.recursive_hash_merge value, @commands_hash[cmd][key] || {}
61
+ else
62
+ @commands_hash[cmd][key] ||= value
63
+ end
64
+ end
65
+
59
66
  def add_comment_scraped_data
60
67
  (@store[:method_locations] || []).select {|k,(f,l)| f == @library_file }.each do |cmd, (file, lineno)|
61
68
  scraped = CommentInspector.scrape(FileLibrary.read_library_file(file), lineno, MethodInspector.current_module)
69
+ @commands_hash[cmd] ||= {}
62
70
  MethodInspector::METHODS.each do |e|
63
- if no_command_config_for(cmd, e)
64
- (@commands_hash[cmd] ||= {})[command_key(e)] = scraped[e]
65
- end
71
+ add_scraped_data_to_config(command_key(e), scraped[e], cmd)
66
72
  end
67
73
  end
68
74
  end
@@ -71,10 +77,6 @@ module Boson
71
77
  def command_key(key)
72
78
  {:method_args=>:args, :desc=>:description}[key] || key
73
79
  end
74
-
75
- def no_command_config_for(cmd, attribute)
76
- !@commands_hash[cmd] || (@commands_hash[cmd] && !@commands_hash[cmd].key?(attribute))
77
- end
78
80
  #:startdoc:
79
81
  end
80
82
  end
@@ -43,7 +43,7 @@ module Boson
43
43
 
44
44
  METHODS.each do |e|
45
45
  define_method(e) do |mod, val|
46
- store(mod)[e] ||= {}
46
+ (@mod_store[mod] ||= {})[e] ||= {}
47
47
  (store(mod)[:temp] ||= {})[e] = val
48
48
  end
49
49
  end
@@ -3,8 +3,8 @@ module Boson
3
3
  # Since there can be multiple repositories, a library's file is looked for in the order given by
4
4
  # Boson.repos.
5
5
  #
6
- # To create this library, simply create a file with a module and some methods. Non-private methods
7
- # are automatically loaded as a library's commands.
6
+ # To create this library, simply create a file with a module and some methods (See Library
7
+ # for naming a module). Non-private methods are automatically loaded as a library's commands.
8
8
  #
9
9
  # Take for example a library brain.rb:
10
10
  # module Brain
@@ -83,30 +83,47 @@ module Boson
83
83
  end
84
84
 
85
85
  handles {|source|
86
- @repo = Boson.repos.find {|e|
87
- File.exists? library_file(source.to_s, e.dir)
88
- }
86
+ @repo = Boson.repos.find {|e| File.exists? library_file(source.to_s, e.dir) } ||
87
+ Boson.repos.find {|e|
88
+ Dir["#{e.commands_dir}/**/*.rb"].grep(/\/#{source}\.rb/).size == 1
89
+ }
89
90
  !!@repo
90
91
  }
91
92
 
92
- def library_file
93
- self.class.library_file(@name, @repo_dir)
93
+ def library_file(name=@name)
94
+ self.class.library_file(name, @repo_dir)
94
95
  end
95
96
 
96
97
  def set_repo
97
98
  self.class.matched_repo
98
99
  end
99
100
 
101
+ def set_name(name)
102
+ @lib_file = File.exists?(library_file(name.to_s)) ? library_file(name.to_s) :
103
+ Dir[self.class.matched_repo.commands_dir.to_s+'/**/*.rb'].find {|e| e =~ /\/#{name}\.rb$/}
104
+ super @lib_file.gsub(/^#{self.class.matched_repo.commands_dir}\/|\.rb$/, '')
105
+ end
106
+
100
107
  def load_source(reload=false)
101
- library_string = self.class.read_library_file(library_file, reload)
108
+ library_string = self.class.read_library_file(@lib_file, reload)
109
+ @base_module = @name.include?('/') ? create_module_from_path : Commands
102
110
  Inspector.enable
103
- Commands.module_eval(library_string, library_file)
111
+ @base_module.module_eval(library_string, @lib_file)
104
112
  Inspector.disable
105
113
  end
106
114
 
115
+ def create_module_from_path
116
+ @name.split('/')[0..-2].inject(Boson::Commands) {|base, e|
117
+ base.const_defined?(sub_mod = Util.camelize(e)) ? base.const_get(sub_mod) :
118
+ Util.create_module(base, e)
119
+ }
120
+ end
121
+
107
122
  def load_source_and_set_module
108
123
  detected = detect_additions(:modules=>true) { load_source }
109
124
  @module = determine_lib_module(detected[:modules]) unless @module
125
+ #without this, module's class methods weren't showing up
126
+ @module = Util.constantize(@module) if @base_module != Commands
110
127
  end
111
128
 
112
129
  def reload_source_and_set_module
@@ -122,8 +139,11 @@ module Boson
122
139
  when 1 then lib_module = detected_modules[0]
123
140
  when 0 then raise LoaderError, "Can't detect module. Make sure at least one module is defined in the library."
124
141
  else
125
- unless ((lib_module = Util.constantize("boson/commands/#{@name}")) && lib_module.to_s[/^Boson::Commands/])
126
- raise LoaderError, "Can't detect module. Specify a module in this library's config."
142
+ unless (lib_module = Util.constantize("boson/commands/#{@name}")) && lib_module.to_s[/^Boson::Commands/]
143
+ command_modules = detected_modules.map {|e| e.to_s}.grep(/^#{@base_module}::/)
144
+ unless command_modules.size == 1 && (lib_module = command_modules[0])
145
+ raise LoaderError, "Can't detect module. Specify a module in this library's config."
146
+ end
127
147
  end
128
148
  end
129
149
  lib_module