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.
- data/.gemspec +22 -0
- data/LICENSE.txt +22 -0
- data/README.md +97 -0
- data/Rakefile +35 -0
- data/deps.rip +1 -0
- data/lib/boson/alias.rb +75 -0
- data/lib/boson/argument_inspector.rb +90 -0
- data/lib/boson/commands/core.rb +67 -0
- data/lib/boson/commands/view_core.rb +19 -0
- data/lib/boson/commands/web_core.rb +153 -0
- data/lib/boson/comment_inspector.rb +100 -0
- data/lib/boson/console.rb +40 -0
- data/lib/boson/console_runner.rb +60 -0
- data/lib/boson/index.rb +48 -0
- data/lib/boson/libraries/file_library.rb +144 -0
- data/lib/boson/libraries/gem_library.rb +30 -0
- data/lib/boson/libraries/local_file_library.rb +30 -0
- data/lib/boson/libraries/module_library.rb +37 -0
- data/lib/boson/libraries/require_library.rb +23 -0
- data/lib/boson/libraries.rb +183 -0
- data/lib/boson/more/version.rb +5 -0
- data/lib/boson/more.rb +18 -0
- data/lib/boson/more_commands.rb +14 -0
- data/lib/boson/more_inspector.rb +42 -0
- data/lib/boson/more_manager.rb +34 -0
- data/lib/boson/more_method_inspector.rb +74 -0
- data/lib/boson/more_option_parser.rb +28 -0
- data/lib/boson/more_scientist.rb +68 -0
- data/lib/boson/more_util.rb +30 -0
- data/lib/boson/namespace.rb +31 -0
- data/lib/boson/namespacer.rb +117 -0
- data/lib/boson/pipe.rb +156 -0
- data/lib/boson/pipe_runner.rb +44 -0
- data/lib/boson/pipes.rb +75 -0
- data/lib/boson/repo.rb +96 -0
- data/lib/boson/repo_index.rb +135 -0
- data/lib/boson/runner_options.rb +88 -0
- data/lib/boson/save.rb +198 -0
- data/lib/boson/science.rb +273 -0
- data/lib/boson/view.rb +98 -0
- data/lib/boson/viewable.rb +48 -0
- data/test/alias_test.rb +55 -0
- data/test/argument_inspector_test.rb +40 -0
- data/test/command_test.rb +22 -0
- data/test/commands_test.rb +53 -0
- data/test/comment_inspector_test.rb +126 -0
- data/test/console_runner_test.rb +58 -0
- data/test/deps.rip +4 -0
- data/test/file_library_test.rb +41 -0
- data/test/gem_library_test.rb +40 -0
- data/test/libraries_test.rb +55 -0
- data/test/loader_test.rb +38 -0
- data/test/module_library_test.rb +30 -0
- data/test/more_manager_test.rb +29 -0
- data/test/more_method_inspector_test.rb +42 -0
- data/test/more_scientist_test.rb +10 -0
- data/test/namespacer_test.rb +61 -0
- data/test/pipes_test.rb +65 -0
- data/test/repo_index_test.rb +123 -0
- data/test/repo_test.rb +23 -0
- data/test/runner_options_test.rb +29 -0
- data/test/save_test.rb +86 -0
- data/test/science_test.rb +58 -0
- data/test/scientist_test.rb +195 -0
- data/test/test_helper.rb +165 -0
- data/test/web_test.rb +22 -0
- 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
|
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
|