boson 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +29 -12
- data/Rakefile +2 -2
- data/VERSION.yml +2 -2
- data/lib/boson.rb +4 -3
- data/lib/boson/command.rb +11 -1
- data/lib/boson/commands/core.rb +6 -6
- data/lib/boson/commands/web_core.rb +17 -4
- data/lib/boson/index.rb +30 -15
- data/lib/boson/inspector.rb +2 -2
- data/lib/boson/inspectors/argument_inspector.rb +4 -4
- data/lib/boson/inspectors/comment_inspector.rb +2 -2
- data/lib/boson/inspectors/method_inspector.rb +8 -6
- data/lib/boson/libraries/file_library.rb +60 -3
- data/lib/boson/libraries/module_library.rb +1 -1
- data/lib/boson/library.rb +23 -2
- data/lib/boson/loader.rb +2 -2
- data/lib/boson/manager.rb +3 -2
- data/lib/boson/namespace.rb +18 -13
- data/lib/boson/option_parser.rb +63 -18
- data/lib/boson/repo.rb +29 -10
- data/lib/boson/runner.rb +19 -13
- data/lib/boson/runners/bin_runner.rb +42 -21
- data/lib/boson/runners/console_runner.rb +54 -0
- data/lib/boson/scientist.rb +92 -17
- data/lib/boson/util.rb +22 -3
- data/lib/boson/view.rb +15 -3
- data/test/bin_runner_test.rb +18 -8
- data/test/loader_test.rb +1 -1
- data/test/manager_test.rb +2 -2
- data/test/repo_test.rb +1 -1
- data/test/runner_test.rb +7 -7
- data/test/scientist_test.rb +8 -7
- data/test/test_helper.rb +14 -3
- metadata +5 -6
- data/lib/boson/runners/repl_runner.rb +0 -40
- data/test/config/index.marshal +0 -0
data/README.rdoc
CHANGED
@@ -1,24 +1,28 @@
|
|
1
1
|
== Description
|
2
2
|
A command/task framework similar to rake and thor that opens your ruby universe to the commandline
|
3
3
|
and irb. For my libraries that use this, see {irbfiles}[http://github.com/cldwalker/irbfiles].
|
4
|
+
Works with Ruby 1.8.6 and 1.9.1.
|
4
5
|
|
5
6
|
== Features
|
6
|
-
* Commands are just methods
|
7
|
-
* Commands are accessible from the commandline or irb.
|
8
|
-
* Command libraries
|
9
|
-
|
10
|
-
|
7
|
+
* Commands are just methods extended for a given object, the default being the top level object, main.
|
8
|
+
* Commands are accessible from the commandline (Boson::BinRunner) or irb (Boson::ConsoleRunner).
|
9
|
+
* Command libraries, which are just modules, are written in non-dsl ruby which allows for easy testing
|
10
|
+
and use outside of boson (Boson::FileLibrary).
|
11
|
+
* Comes with default commands to load, search, list and install commands and libraries (Boson::Commands::Core).
|
12
|
+
* Commands can be full-blown commandline apps thanks to powerful options (Boson::OptionParser)
|
13
|
+
and hirb's views.
|
14
|
+
* Commands can have views toggled without adding view code to your original command (Boson::Scientist).
|
11
15
|
* Command libraries are social as a user can install them from a url and then customize command
|
12
16
|
names and options without changing the original library.
|
13
|
-
* Namespaces are optional and when used are methods which
|
17
|
+
* Namespaces are optional and when used are methods which allow for method_missing magic.
|
14
18
|
|
15
19
|
== Irb Example
|
16
20
|
|
17
|
-
To use in irb, drop this in your irbrc:
|
21
|
+
To use in irb, drop this in your ~/.irbrc:
|
18
22
|
require 'boson'
|
19
|
-
Boson.start
|
23
|
+
Boson.start
|
20
24
|
|
21
|
-
|
25
|
+
Having done that, let's start up irb:
|
22
26
|
|
23
27
|
bash> irb
|
24
28
|
Loaded library core
|
@@ -124,10 +128,23 @@ Let's start up irb:
|
|
124
128
|
|
125
129
|
# Sweet! Now we have a list and description of commands that come with irb.
|
126
130
|
|
127
|
-
== Creating
|
128
|
-
|
131
|
+
== Creating Command Libraries
|
132
|
+
See Boson::FileLibrary or here[http://tagaholic.me/boson/doc/classes/Boson/FileLibrary.html].
|
129
133
|
|
130
134
|
== Todo
|
131
135
|
* More docs
|
132
136
|
* More tests
|
133
|
-
*
|
137
|
+
* Making commands out of existing gems easier and more powerful
|
138
|
+
* Better local repositories, perhaps a BosonFile
|
139
|
+
* Consider managing extensions to core and standard libraries
|
140
|
+
* Consider dropping alias gem dependency if not using its full potential
|
141
|
+
|
142
|
+
== Motivation
|
143
|
+
My {tagging obsession}[http://github.com/cldwalker/tag-tree] from the ruby console.
|
144
|
+
|
145
|
+
== Acknowledgements
|
146
|
+
Boson stands on the shoulders of these people and their ideas:
|
147
|
+
* Yehuda Katz and Daniel Berger for an awesome option parser (Boson::OptionParser)
|
148
|
+
* Dave Thomas for scraping a method's comments (Boson::CommentInspector)
|
149
|
+
* Mauricio Fernandez for scraping a method's arguments (Boson::ArgumentInspector)
|
150
|
+
* Chris Wanstrath for inspiring Boson's libraries with Rip's packages.
|
data/Rakefile
CHANGED
@@ -19,13 +19,13 @@ begin
|
|
19
19
|
Jeweler::Tasks.new do |s|
|
20
20
|
s.name = "boson"
|
21
21
|
s.description = "A command/task framework similar to rake and thor that opens your ruby universe to the commandline and irb."
|
22
|
-
s.summary = "
|
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
24
|
s.homepage = "http://github.com/cldwalker/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.
|
28
|
+
s.add_dependency 'hirb', '>= 0.2.7'
|
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
data/lib/boson.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__) unless $:.include? File.expand_path(File.dirname(__FILE__))
|
2
2
|
%w{hirb alias}.each {|e| require e }
|
3
|
-
%w{runner runners/
|
3
|
+
%w{runner runners/console_runner repo manager loader inspector library}.each {|e| require "boson/#{e}" }
|
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" }
|
@@ -40,7 +40,7 @@ module Boson
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
# The array of loaded repositories
|
43
|
+
# The array of loaded repositories
|
44
44
|
def repos
|
45
45
|
@repos ||= [repo, local_repo].compact
|
46
46
|
end
|
@@ -54,8 +54,9 @@ module Boson
|
|
54
54
|
end
|
55
55
|
|
56
56
|
# Start Boson by loading repositories and their configured libraries.
|
57
|
+
# See ConsoleRunner.start for its options.
|
57
58
|
def start(options={})
|
58
|
-
|
59
|
+
ConsoleRunner.start(options)
|
59
60
|
end
|
60
61
|
|
61
62
|
# Invoke an action on the main object.
|
data/lib/boson/command.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Boson
|
2
|
-
# A command
|
2
|
+
# A command starts with the functionality of a ruby method and adds benefits with options, render_options, etc.
|
3
3
|
class Command
|
4
4
|
# Creates a command given its name and a library.
|
5
5
|
def self.create(name, library)
|
@@ -22,6 +22,16 @@ module Boson
|
|
22
22
|
attr_accessor *(ATTRIBUTES + [:render_options, :namespace])
|
23
23
|
# A hash of attributes which map to instance variables and values. :name
|
24
24
|
# and :lib are required keys.
|
25
|
+
#
|
26
|
+
# Attributes that can be configured:
|
27
|
+
# * *:description*: Description that shows up in command listings
|
28
|
+
# * *:alias*: Alternative name for command
|
29
|
+
# * *:options*: Hash of options passed to OptionParser
|
30
|
+
# * *:render_options*: Hash of rendering options passed to OptionParser
|
31
|
+
# * *:args*: Should only be set if not automatically set. This attribute is only
|
32
|
+
# important for commands that have options/render_options. Its value can be an array
|
33
|
+
# (as ArgumentInspector.scrape_with_eval produces), a number representing
|
34
|
+
# the number of arguments or '*' if the command has a variable number of arguments.
|
25
35
|
def initialize(hash)
|
26
36
|
@name = hash[:name] or raise ArgumentError
|
27
37
|
@lib = hash[:lib] or raise ArgumentError
|
data/lib/boson/commands/core.rb
CHANGED
@@ -6,20 +6,20 @@ module Boson::Commands::Core #:nodoc:
|
|
6
6
|
commands = {
|
7
7
|
'render'=>{:description=>"Render any object using Hirb"},
|
8
8
|
'menu'=>{:description=>"Provide a menu to multi-select elements from a given array"},
|
9
|
-
'usage'=>{:description=>"Print a command's usage", :options=>{:verbose=>:boolean}},
|
9
|
+
'usage'=>{:description=>"Print a command's usage", :options=>{[:verbose, :V]=>:boolean}},
|
10
10
|
'commands'=>{ :description=>"List or search commands",
|
11
|
-
:options=>{:query_fields=>{:default=>['full_name'], :values=>command_attributes},
|
11
|
+
:options=>{:query_fields=>{:default=>['full_name'], :values=>command_attributes, :desc=>"Searchable fields"},
|
12
12
|
:index=>{:type=>:boolean, :desc=>"Searches index"}},
|
13
13
|
:render_options=>{:fields=>{:default=>[:full_name, :lib, :alias, :usage, :description], :values=>command_attributes} }
|
14
14
|
},
|
15
15
|
'libraries'=>{ :description=>"List or search libraries",
|
16
|
-
:options=>{:query_fields=>{:default=>['name'], :values=>library_attributes},
|
16
|
+
:options=>{:query_fields=>{:default=>['name'], :values=>library_attributes, :desc=>"Searchable fields"},
|
17
17
|
:index=>{:type=>:boolean, :desc=>"Searches index"} },
|
18
18
|
:render_options=>{
|
19
19
|
:fields=>{:default=>[:name, :commands, :gems, :library_type], :values=>library_attributes},
|
20
20
|
:filters=>{:default=>{:gems=>[:join, ','],:commands=>:size}} }
|
21
21
|
},
|
22
|
-
'load_library'=>{:description=>"Load/reload a library", :options=>{:reload=>:boolean, :verbose=>true}}
|
22
|
+
'load_library'=>{:description=>"Load/reload a library", :options=>{:reload=>:boolean, [:verbose,:V]=>true}}
|
23
23
|
}
|
24
24
|
|
25
25
|
{:library_file=>File.expand_path(__FILE__), :commands=>commands}
|
@@ -29,13 +29,13 @@ module Boson::Commands::Core #:nodoc:
|
|
29
29
|
query_fields = options[:query_fields] || ['full_name']
|
30
30
|
Boson::Index.read if options[:index]
|
31
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
|
32
|
+
query_fields.map {|e| commands.select {|f| f.send(e).to_s =~ /#{query}/i } }.flatten.uniq
|
33
33
|
end
|
34
34
|
|
35
35
|
def libraries(query='', options={})
|
36
36
|
Boson::Index.read if options[:index]
|
37
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
|
38
|
+
options[:query_fields].map {|e| libraries.select {|f| f.send(e).to_s =~ /#{query}/i } }.flatten.uniq
|
39
39
|
end
|
40
40
|
|
41
41
|
def load_library(library, options={})
|
@@ -2,9 +2,12 @@ module Boson::Commands::WebCore #:nodoc:
|
|
2
2
|
def self.config
|
3
3
|
descriptions = {
|
4
4
|
:install=>"Installs a library by url. Library should then be loaded with load_library.",
|
5
|
-
:browser=>"Opens urls in a browser", :get=>"Gets the body of a url" }
|
5
|
+
:browser=>"Opens urls in a browser on a Mac", :get=>"Gets the body of a url" }
|
6
6
|
commands = descriptions.inject({}) {|h,(k,v)| h[k.to_s] = {:description=>v}; h}
|
7
|
-
commands['install'][:options] = {:name=>:string, :
|
7
|
+
commands['install'][:options] = {:name=>{:type=>:string, :desc=>"Library name to save to"},
|
8
|
+
:force=>{:type=>:boolean, :desc=>'Overwrites an existing library'},
|
9
|
+
:module_wrap=>{:type=>:boolean, :desc=>"Wraps a module around install using library name"},
|
10
|
+
:method_wrap=>{:type=>:boolean, :desc=>"Wraps a method and module around installe library using library name"}}
|
8
11
|
{:library_file=>File.expand_path(__FILE__), :commands=>commands}
|
9
12
|
end
|
10
13
|
|
@@ -17,10 +20,20 @@ module Boson::Commands::WebCore #:nodoc:
|
|
17
20
|
|
18
21
|
def install(url, options={})
|
19
22
|
options[:name] ||= strip_name_from_url(url)
|
20
|
-
return puts("Please give a library name
|
23
|
+
return puts("Please give a library name for this url.") if options[:name].empty?
|
21
24
|
filename = File.join Boson.repo.commands_dir, "#{options[:name]}.rb"
|
22
25
|
return puts("Library name #{options[:name]} already exists. Try a different name.") if File.exists?(filename) && !options[:force]
|
23
26
|
File.open(filename, 'w') {|f| f.write get(url) }
|
27
|
+
|
28
|
+
if options[:method_wrap] || options[:module_wrap]
|
29
|
+
file_string = File.read(filename)
|
30
|
+
file_string = "def #{options[:name]}\n#{file_string}\nend" if options[:method_wrap]
|
31
|
+
unless (mod_name = Boson::Util.camelize(options[:name]))
|
32
|
+
return puts("Can't wrap install with name #{options[:name]}")
|
33
|
+
end
|
34
|
+
file_string = "module #{mod_name}\n#{file_string}\nend"
|
35
|
+
File.open(filename, 'w') {|f| f.write file_string }
|
36
|
+
end
|
24
37
|
puts "Saved to #{filename}."
|
25
38
|
end
|
26
39
|
|
@@ -31,6 +44,6 @@ module Boson::Commands::WebCore #:nodoc:
|
|
31
44
|
|
32
45
|
private
|
33
46
|
def strip_name_from_url(url)
|
34
|
-
url[/\/([^\/.]+)(\.[a-z]+)?$/, 1]
|
47
|
+
url[/\/([^\/.]+)(\.[a-z]+)?$/, 1].to_s.gsub('-', '_').gsub(/[^a-zA-Z_]/, '')
|
35
48
|
end
|
36
49
|
end
|
data/lib/boson/index.rb
CHANGED
@@ -1,17 +1,11 @@
|
|
1
1
|
require 'digest/md5'
|
2
2
|
module Boson
|
3
|
+
# Used in all things indexing i.e. saving state for boson's commandline.
|
3
4
|
module Index
|
4
5
|
extend self
|
5
6
|
attr_reader :libraries, :commands
|
6
7
|
|
7
|
-
|
8
|
-
read
|
9
|
-
existing_libraries = (Boson.libraries.map {|e| e.name} + ignored_libraries).uniq
|
10
|
-
Boson.libraries += @libraries.select {|e| !existing_libraries.include?(e.name)}
|
11
|
-
existing_commands = Boson.commands.map {|e| e.name} #td: consider namespace
|
12
|
-
Boson.commands += @commands.select {|e| !existing_commands.include?(e.name) && !ignored_libraries.include?(e.lib)}
|
13
|
-
end
|
14
|
-
|
8
|
+
# Updates the index.
|
15
9
|
def update(options={})
|
16
10
|
options[:all] = true if !exists? && !options.key?(:all)
|
17
11
|
libraries_to_update = options[:all] ? Runner.all_libraries : changed_libraries
|
@@ -25,23 +19,43 @@ module Boson
|
|
25
19
|
write
|
26
20
|
end
|
27
21
|
|
28
|
-
|
29
|
-
|
22
|
+
# Reads and initializes index.
|
23
|
+
def read
|
24
|
+
return if @read
|
25
|
+
@libraries, @commands, @lib_hashes = exists? ? Marshal.load(File.read(marshal_file)) : [[], [], {}]
|
26
|
+
delete_stale_libraries_and_commands
|
27
|
+
set_latest_namespaces
|
28
|
+
@read = true
|
30
29
|
end
|
31
30
|
|
31
|
+
# Writes/saves current index to config/index.marshal.
|
32
32
|
def write
|
33
33
|
save_marshal_index Marshal.dump([Boson.libraries, Boson.commands, latest_hashes])
|
34
34
|
end
|
35
35
|
|
36
|
+
#:stopdoc:
|
37
|
+
def read_and_transfer(ignored_libraries=[])
|
38
|
+
read
|
39
|
+
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)}
|
43
|
+
end
|
44
|
+
|
45
|
+
def exists?
|
46
|
+
File.exists? marshal_file
|
47
|
+
end
|
48
|
+
|
36
49
|
def save_marshal_index(marshal_string)
|
37
50
|
File.open(marshal_file, 'w') {|f| f.write marshal_string }
|
38
51
|
end
|
39
52
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@
|
53
|
+
def delete_stale_libraries_and_commands
|
54
|
+
cached_libraries = @lib_hashes.keys
|
55
|
+
libs_to_delete = @libraries.select {|e| !cached_libraries.include?(e.name) && e.is_a?(FileLibrary) }
|
56
|
+
names_to_delete = libs_to_delete.map {|e| e.name }
|
57
|
+
libs_to_delete.each {|e| @libraries.delete(e) }
|
58
|
+
@commands.delete_if {|e| names_to_delete.include? e.lib }
|
45
59
|
end
|
46
60
|
|
47
61
|
# get latest namespaces from config files
|
@@ -91,5 +105,6 @@ module Boson
|
|
91
105
|
h
|
92
106
|
}
|
93
107
|
end
|
108
|
+
#:startdoc:
|
94
109
|
end
|
95
110
|
end
|
data/lib/boson/inspector.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Boson
|
2
|
-
# Scrapes and processes method
|
2
|
+
# Scrapes and processes method attributes with the inspectors (MethodInspector, CommentInspector
|
3
3
|
# and ArgumentInspector) and hands off the usable data to FileLibrary objects.
|
4
4
|
module Inspector
|
5
5
|
extend self
|
@@ -34,7 +34,7 @@ module Boson
|
|
34
34
|
@enabled = false
|
35
35
|
end
|
36
36
|
|
37
|
-
# Adds method
|
37
|
+
# Adds method attributes scraped for the library's module to the library's commands.
|
38
38
|
def add_method_data_to_library(library)
|
39
39
|
@commands_hash = library.commands_hash
|
40
40
|
@library_file = library.library_file
|
@@ -6,7 +6,7 @@ module Boson::ArgumentInspector
|
|
6
6
|
# Returns same argument arrays as scrape_with_eval but argument defaults haven't been evaluated.
|
7
7
|
def scrape_with_text(file_string, meth)
|
8
8
|
tabspace = "[ \t]"
|
9
|
-
if match = /^#{tabspace}*def#{tabspace}+#{meth}#{tabspace}*($|\(?\s*([^\)]+)\s*\)?\s*$)/.match(file_string)
|
9
|
+
if match = /^#{tabspace}*def#{tabspace}+#{meth}\b#{tabspace}*($|\(?\s*([^\)]+)\s*\)?\s*$)/.match(file_string)
|
10
10
|
(match.to_a[2] || '').split(/\s*,\s*/).map {|e| e.split(/\s*=\s*/)}
|
11
11
|
end
|
12
12
|
end
|
@@ -20,7 +20,7 @@ module Boson::ArgumentInspector
|
|
20
20
|
# def meth2(*args) -> [['*args']]
|
21
21
|
def scrape_with_eval(meth, klass, object)
|
22
22
|
unless %w[initialize].include?(meth.to_s)
|
23
|
-
return if class << object; private_instance_methods(true) end.include?(meth.to_s)
|
23
|
+
return if class << object; private_instance_methods(true).map {|e| e.to_s } end.include?(meth.to_s)
|
24
24
|
end
|
25
25
|
params, values, arity, num_args = trace_method_args(meth, klass, object)
|
26
26
|
return if local_variables == params # nothing new found
|
@@ -40,9 +40,9 @@ module Boson::ArgumentInspector
|
|
40
40
|
[a << ["*#{x}"], i+1]
|
41
41
|
else
|
42
42
|
if arity < 0 && i >= arity.abs - 1
|
43
|
-
[a << [x, values[i]], i + 1]
|
43
|
+
[a << [x.to_s, values[i]], i + 1]
|
44
44
|
else
|
45
|
-
[a << [x], i+1]
|
45
|
+
[a << [x.to_s], i+1]
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end.first
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Boson
|
2
|
-
# Scrapes comments right before a method for its
|
3
|
-
# same as MethodInspector: desc, options, render_options. Attributes must begin with '@' i.e.:
|
2
|
+
# Scrapes comments right before a method for its attributes. Metadata attributes are the
|
3
|
+
# same as MethodInspector : desc, options, render_options. Attributes must begin with '@' i.e.:
|
4
4
|
#
|
5
5
|
# # @desc Does foo
|
6
6
|
# # @options :verbose=>true
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module Boson
|
2
|
-
#
|
2
|
+
# Gathers method attributes with new_method_added and the
|
3
3
|
# following Module methods:
|
4
|
-
# * desc: Defines a description for a method/command
|
4
|
+
# * desc: Defines a description for a method/command.
|
5
5
|
# * options: Defines an OptionParser object for a method's options.
|
6
|
-
# * render_options: Defines an OptionParser object for a method's rendering options.
|
6
|
+
# * render_options: Defines an OptionParser object for a method's rendering options. These options
|
7
|
+
# are passed to View.render when rendering a command.
|
7
8
|
#
|
8
9
|
# These method calls must come immediately before a method i.e.:
|
9
10
|
#
|
@@ -11,7 +12,7 @@ module Boson
|
|
11
12
|
# options :verbose=>:boolean
|
12
13
|
# def foo(options={})
|
13
14
|
#
|
14
|
-
# This module also allows for defining method
|
15
|
+
# This module also allows for defining method attributes as comments. Although the actual
|
15
16
|
# scraping of comments is handled by CommentInspector, MethodInspector gather's the method
|
16
17
|
# location it needs with with find_method_locations().
|
17
18
|
module MethodInspector
|
@@ -21,7 +22,7 @@ module Boson
|
|
21
22
|
@mod_store ||= {}
|
22
23
|
METHODS = [:desc, :options, :render_options]
|
23
24
|
|
24
|
-
# The method_added used while scraping method
|
25
|
+
# The method_added used while scraping method attributes.
|
25
26
|
def new_method_added(mod, meth)
|
26
27
|
return unless mod.name[/^Boson::Commands::/]
|
27
28
|
self.current_module = mod
|
@@ -59,10 +60,11 @@ module Boson
|
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
63
|
+
CALLER_REGEXP = RUBY_VERSION < '1.9' ? /in `load_source'/ : /in `<module:.*>'/
|
62
64
|
# Returns an array of the file and line number at which a method starts using
|
63
65
|
# a caller array. Necessary information for CommentInspector to function.
|
64
66
|
def find_method_locations(stack)
|
65
|
-
if (line = stack.find {|e| e =~
|
67
|
+
if (line = stack.find {|e| e =~ CALLER_REGEXP })
|
66
68
|
(line =~ /^(.*):(\d+)/) ? [$1, $2.to_i] : nil
|
67
69
|
end
|
68
70
|
end
|
@@ -1,7 +1,64 @@
|
|
1
1
|
module Boson
|
2
|
-
# This library
|
3
|
-
#
|
4
|
-
#
|
2
|
+
# This library is based on a file of the same name under the commands directory of a repository.
|
3
|
+
# Since there can be multiple repositories, a library's file is looked for in the order given by
|
4
|
+
# Boson.repos.
|
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.
|
8
|
+
#
|
9
|
+
# Take for example a library brain.rb:
|
10
|
+
# module Brain
|
11
|
+
# def take_over(destination)
|
12
|
+
# puts "Pinky, it's time to take over the #{destination}!"
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Once loaded, this library can be run from the commandline or irb:
|
17
|
+
# bash> boson take_over world
|
18
|
+
# irb>> take_over 'world'
|
19
|
+
#
|
20
|
+
# If the library is namespaced, the command would be run as brain.take_over.
|
21
|
+
#
|
22
|
+
# Let's give Brain an option in his conquest:
|
23
|
+
# module Brain
|
24
|
+
# options :execute=>:string
|
25
|
+
# def take_over(destination, options={})
|
26
|
+
# puts "Pinky, it's time to take over the #{destination}!"
|
27
|
+
# system(options[:execute]) if options[:execute]
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# From the commandline and irb this runs as:
|
32
|
+
# bash> boson take_over world -e initiate_brainiac
|
33
|
+
# irb>> take_over 'world -e initiate_brainiac'
|
34
|
+
#
|
35
|
+
# To learn more about the depth of option types available to a command, see OptionParser.
|
36
|
+
#
|
37
|
+
# Since boson aims to make your libraries just standard ruby, we can achieve the above
|
38
|
+
# by placing options in comments above a method:
|
39
|
+
# module Brain
|
40
|
+
# # @options :execute=>:string
|
41
|
+
# # Help Brain live the dream
|
42
|
+
# def take_over(destination, options={})
|
43
|
+
# puts "Pinky, it's time to take over the #{destination}!"
|
44
|
+
# system(options[:execute]) if options[:execute]
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# Some points about the above:
|
49
|
+
# * A '@' must prefix options and other method calls that become comments.
|
50
|
+
# * Note the comment above the method. One-line comments right before a method set a command's description.
|
51
|
+
# * See MethodInspector for other command attributes, like options, that can be placed above a method.
|
52
|
+
# * See CommentInspector for the rules about commenting command attributes.
|
53
|
+
#
|
54
|
+
# Once a command has a defined option, a command can also recognize a slew of global options:
|
55
|
+
# irb>> take_over '-h'
|
56
|
+
# take_over [destination] [--execute=STRING]
|
57
|
+
#
|
58
|
+
# # prints much more verbose help
|
59
|
+
# irb>> take_over '-hv'
|
60
|
+
#
|
61
|
+
# For more about these global options see Scientist.
|
5
62
|
class FileLibrary < Library
|
6
63
|
#:stopdoc:
|
7
64
|
def self.library_file(library, dir)
|