boson 0.4.0 → 1.0.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 +6 -7
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.rdoc +1 -1
- data/README.md +144 -0
- data/README.rdoc +2 -2
- data/Upgrading.md +23 -0
- data/bin/boson +2 -2
- data/lib/boson.rb +44 -52
- data/lib/boson/bare_runner.rb +83 -0
- data/lib/boson/bin_runner.rb +114 -0
- data/lib/boson/command.rb +92 -132
- data/lib/boson/inspector.rb +49 -48
- data/lib/boson/library.rb +71 -120
- data/lib/boson/loader.rb +73 -84
- data/lib/boson/manager.rb +131 -135
- data/lib/boson/method_inspector.rb +112 -0
- data/lib/boson/option_command.rb +71 -154
- data/lib/boson/option_parser.rb +178 -173
- data/lib/boson/options.rb +46 -32
- data/lib/boson/runner.rb +58 -66
- data/lib/boson/runner_library.rb +31 -0
- data/lib/boson/scientist.rb +48 -81
- data/lib/boson/util.rb +46 -61
- data/lib/boson/version.rb +1 -1
- data/test/bin_runner_test.rb +53 -191
- data/test/command_test.rb +5 -9
- data/test/deps.rip +2 -2
- data/test/loader_test.rb +18 -216
- data/test/manager_test.rb +69 -79
- data/test/method_inspector_test.rb +12 -36
- data/test/option_parser_test.rb +45 -32
- data/test/runner_library_test.rb +10 -0
- data/test/runner_test.rb +158 -28
- data/test/scientist_test.rb +9 -147
- data/test/test_helper.rb +87 -52
- metadata +30 -72
- data/deps.rip +0 -2
- data/lib/boson/commands.rb +0 -7
- data/lib/boson/commands/core.rb +0 -77
- data/lib/boson/commands/web_core.rb +0 -153
- data/lib/boson/index.rb +0 -48
- data/lib/boson/inspectors/argument_inspector.rb +0 -97
- data/lib/boson/inspectors/comment_inspector.rb +0 -100
- data/lib/boson/inspectors/method_inspector.rb +0 -98
- data/lib/boson/libraries/file_library.rb +0 -144
- data/lib/boson/libraries/gem_library.rb +0 -30
- data/lib/boson/libraries/local_file_library.rb +0 -30
- data/lib/boson/libraries/module_library.rb +0 -37
- data/lib/boson/libraries/require_library.rb +0 -23
- data/lib/boson/namespace.rb +0 -31
- data/lib/boson/pipe.rb +0 -147
- data/lib/boson/pipes.rb +0 -75
- data/lib/boson/repo.rb +0 -107
- data/lib/boson/runners/bin_runner.rb +0 -208
- data/lib/boson/runners/console_runner.rb +0 -58
- data/lib/boson/view.rb +0 -95
- data/test/argument_inspector_test.rb +0 -62
- data/test/commands_test.rb +0 -22
- data/test/comment_inspector_test.rb +0 -126
- data/test/file_library_test.rb +0 -42
- data/test/pipes_test.rb +0 -65
- data/test/repo_index_test.rb +0 -122
- data/test/repo_test.rb +0 -23
@@ -1,98 +0,0 @@
|
|
1
|
-
module Boson
|
2
|
-
# Gathers method attributes by redefining method_added and capturing method
|
3
|
-
# calls before a method. This module also saves method locations so CommentInspector
|
4
|
-
# can scrape their commented method attributes.
|
5
|
-
module MethodInspector
|
6
|
-
extend self
|
7
|
-
attr_accessor :current_module, :mod_store
|
8
|
-
@mod_store ||= {}
|
9
|
-
METHODS = [:config, :desc, :options, :render_options]
|
10
|
-
METHOD_CLASSES = {:config=>Hash, :desc=>String, :options=>Hash, :render_options=>Hash}
|
11
|
-
ALL_METHODS = METHODS + [:option]
|
12
|
-
|
13
|
-
# The method_added used while scraping method attributes.
|
14
|
-
def new_method_added(mod, meth)
|
15
|
-
return unless mod.to_s[/^Boson::Commands::/]
|
16
|
-
self.current_module = mod
|
17
|
-
store[:temp] ||= {}
|
18
|
-
METHODS.each do |e|
|
19
|
-
store[e][meth.to_s] = store[:temp][e] if store[:temp][e]
|
20
|
-
end
|
21
|
-
(store[:options][meth.to_s] ||= {}).merge! store[:temp][:option] if store[:temp][:option]
|
22
|
-
|
23
|
-
if store[:temp].size < ALL_METHODS.size
|
24
|
-
store[:method_locations] ||= {}
|
25
|
-
if (result = find_method_locations(caller))
|
26
|
-
store[:method_locations][meth.to_s] = result
|
27
|
-
end
|
28
|
-
end
|
29
|
-
store[:temp] = {}
|
30
|
-
scrape_arguments(meth) if has_inspector_method?(meth, :options) || has_inspector_method?(meth,:render_options)
|
31
|
-
end
|
32
|
-
|
33
|
-
METHODS.each do |e|
|
34
|
-
define_method(e) do |mod, val|
|
35
|
-
(@mod_store[mod] ||= {})[e] ||= {}
|
36
|
-
(store(mod)[:temp] ||= {})[e] = val
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def option(mod, name, value)
|
41
|
-
(@mod_store[mod] ||= {})[:options] ||= {}
|
42
|
-
(store(mod)[:temp] ||= {})[:option] ||= {}
|
43
|
-
(store(mod)[:temp] ||= {})[:option][name] = value
|
44
|
-
end
|
45
|
-
|
46
|
-
# Scrapes a method's arguments using ArgumentInspector.
|
47
|
-
def scrape_arguments(meth)
|
48
|
-
store[:args] ||= {}
|
49
|
-
|
50
|
-
o = Object.new
|
51
|
-
o.extend(@current_module)
|
52
|
-
# private methods return nil
|
53
|
-
if (val = ArgumentInspector.scrape_with_eval(meth, @current_module, o))
|
54
|
-
store[:args][meth.to_s] = val
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
CALLER_REGEXP = RUBY_VERSION < '1.9' ? /in `load_source'/ : /in `<module:.*>'/
|
59
|
-
# Returns an array of the file and line number at which a method starts using
|
60
|
-
# a caller array. Necessary information for CommentInspector to function.
|
61
|
-
def find_method_locations(stack)
|
62
|
-
if (line = stack.find {|e| e =~ CALLER_REGEXP })
|
63
|
-
(line =~ /^(.*):(\d+)/) ? [$1, $2.to_i] : nil
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
#:stopdoc:
|
68
|
-
def find_method_locations_for_19(klass, meth)
|
69
|
-
if (klass = Util.any_const_get(klass)) && (meth_location = klass.method(meth).source_location) &&
|
70
|
-
meth_location[0]
|
71
|
-
meth_location
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# Hash of a module's method attributes i.e. descriptions, options by method and then attribute
|
76
|
-
def store(mod=@current_module)
|
77
|
-
@mod_store[mod]
|
78
|
-
end
|
79
|
-
|
80
|
-
def current_module=(mod)
|
81
|
-
@current_module = mod
|
82
|
-
@mod_store[mod] ||= {}
|
83
|
-
end
|
84
|
-
|
85
|
-
def has_inspector_method?(meth, inspector)
|
86
|
-
(store[inspector] && store[inspector].key?(meth.to_s)) || inspector_in_file?(meth.to_s, inspector)
|
87
|
-
end
|
88
|
-
|
89
|
-
def inspector_in_file?(meth, inspector_method)
|
90
|
-
return false if !(file_line = store[:method_locations] && store[:method_locations][meth])
|
91
|
-
if File.exists?(file_line[0]) && (options = CommentInspector.scrape(
|
92
|
-
FileLibrary.read_library_file(file_line[0]), file_line[1], @current_module, inspector_method) )
|
93
|
-
(store[inspector_method] ||= {})[meth] = options
|
94
|
-
end
|
95
|
-
end
|
96
|
-
#:startdoc:
|
97
|
-
end
|
98
|
-
end
|
@@ -1,144 +0,0 @@
|
|
1
|
-
module Boson
|
2
|
-
# This class loads a file by its path relative to the commands directory of a repository.
|
3
|
-
# For example the library 'public/misc' could refer to the file '~/.boson/commands/public/misc.rb'.
|
4
|
-
# If a file's basename is unique in its repository, then it can be loaded with its basename i.e. 'misc'
|
5
|
-
# for the previous example. When loading a library, this class searches repositories in the order given by
|
6
|
-
# Boson.repos.
|
7
|
-
#
|
8
|
-
# === Creating a FileLibrary
|
9
|
-
# Start by creating a file with a module and some methods (See Library for naming a module).
|
10
|
-
# Non-private methods are automatically loaded as a library's commands.
|
11
|
-
#
|
12
|
-
# Take for example a library brain.rb:
|
13
|
-
# # Drop this in ~/.boson/commands/brain.rb
|
14
|
-
# module Brain
|
15
|
-
# def take_over(destination)
|
16
|
-
# puts "Pinky, it's time to take over the #{destination}!"
|
17
|
-
# end
|
18
|
-
# end
|
19
|
-
#
|
20
|
-
# Once loaded, this library can be run from the commandline or irb:
|
21
|
-
# $ boson take_over world
|
22
|
-
# >> take_over 'world'
|
23
|
-
#
|
24
|
-
# If the library is namespaced, the command would be run as brain.take_over.
|
25
|
-
#
|
26
|
-
# Let's give Brain an option in his conquest:
|
27
|
-
# module Brain
|
28
|
-
# options :execute=>:string
|
29
|
-
# def take_over(destination, options={})
|
30
|
-
# puts "Pinky, it's time to take over the #{destination}!"
|
31
|
-
# system(options[:execute]) if options[:execute]
|
32
|
-
# end
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# From the commandline and irb this runs as:
|
36
|
-
# $ boson take_over world -e initiate_brainiac
|
37
|
-
# >> take_over 'world -e initiate_brainiac'
|
38
|
-
#
|
39
|
-
# Since Boson aims to make your libraries just standard ruby, we can achieve the above
|
40
|
-
# by making options a commented method attribute:
|
41
|
-
# module Brain
|
42
|
-
# # @options :execute=>:string
|
43
|
-
# # Help Brain live the dream
|
44
|
-
# def take_over(destination, options={})
|
45
|
-
# puts "Pinky, it's time to take over the #{destination}!"
|
46
|
-
# system(options[:execute]) if options[:execute]
|
47
|
-
# end
|
48
|
-
# end
|
49
|
-
#
|
50
|
-
# Some points about the above:
|
51
|
-
# * A '@' must prefix options and other method attributes that become comments.
|
52
|
-
# * Note the comment above the method. One-line comments right before a method set a command's description.
|
53
|
-
# * See Inspector for other method attributes, like config and render_options, that can be placed above a method.
|
54
|
-
#
|
55
|
-
# Once a command has a defined option, a command can also recognize a slew of global options:
|
56
|
-
# >> take_over '-h'
|
57
|
-
# take_over [destination] [--execute=STRING]
|
58
|
-
#
|
59
|
-
# # prints much more verbose help
|
60
|
-
# >> take_over '-hv'
|
61
|
-
#
|
62
|
-
# For more about these global options see OptionCommand and View.
|
63
|
-
class FileLibrary < Library
|
64
|
-
#:stopdoc:
|
65
|
-
def self.library_file(library, dir)
|
66
|
-
File.join(Repo.commands_dir(dir), library + ".rb")
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.matched_repo; @repo; end
|
70
|
-
|
71
|
-
def self.read_library_file(file, reload=false)
|
72
|
-
@file_cache ||= {}
|
73
|
-
@file_cache[file] = File.read(file) if (!@file_cache.has_key?(file) || reload)
|
74
|
-
@file_cache[file]
|
75
|
-
end
|
76
|
-
|
77
|
-
def self.reset_file_cache(name=nil)
|
78
|
-
if name && @file_cache
|
79
|
-
@file_cache.delete(library_file(name, (matched_repo || Boson.repo).dir))
|
80
|
-
else
|
81
|
-
@file_cache = nil
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
handles {|source|
|
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
|
-
}
|
90
|
-
!!@repo
|
91
|
-
}
|
92
|
-
|
93
|
-
def library_file(name=@name)
|
94
|
-
self.class.library_file(name, @repo_dir)
|
95
|
-
end
|
96
|
-
|
97
|
-
def set_repo
|
98
|
-
self.class.matched_repo
|
99
|
-
end
|
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
|
-
@lib_file.gsub(/^#{self.class.matched_repo.commands_dir}\/|\.rb$/, '')
|
105
|
-
end
|
106
|
-
|
107
|
-
def base_module
|
108
|
-
@base_module ||= @name.include?('/') ? create_module_from_path : Commands
|
109
|
-
end
|
110
|
-
|
111
|
-
def load_source(reload=false)
|
112
|
-
library_string = self.class.read_library_file(@lib_file, reload)
|
113
|
-
Inspector.enable
|
114
|
-
base_module.module_eval(library_string, @lib_file)
|
115
|
-
Inspector.disable
|
116
|
-
end
|
117
|
-
|
118
|
-
def create_module_from_path(index=-2)
|
119
|
-
@name.split('/')[0..index].inject(Boson::Commands) {|base, e|
|
120
|
-
base.const_defined?(sub_mod = Util.camelize(e)) ? base.const_get(sub_mod) :
|
121
|
-
Util.create_module(base, e)
|
122
|
-
}
|
123
|
-
end
|
124
|
-
|
125
|
-
def load_source_and_set_module
|
126
|
-
detected = detect_additions(:modules=>true) { load_source }
|
127
|
-
@module = determine_lib_module(detected[:modules]) unless @module
|
128
|
-
end
|
129
|
-
|
130
|
-
def determine_lib_module(detected_modules)
|
131
|
-
detected_modules = detected_modules.select {|e| e.to_s[/^#{base_module}::/] }
|
132
|
-
case detected_modules.size
|
133
|
-
when 1 then lib_module = detected_modules[0]
|
134
|
-
when 0 then lib_module = create_module_from_path(-1)
|
135
|
-
else
|
136
|
-
unless (lib_module = Util.constantize("boson/commands/#{@name}")) && lib_module.to_s[/^Boson::Commands/]
|
137
|
-
raise LoaderError, "Can't detect module. Specify a module in this library's config."
|
138
|
-
end
|
139
|
-
end
|
140
|
-
lib_module
|
141
|
-
end
|
142
|
-
#:startdoc:
|
143
|
-
end
|
144
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
module Boson
|
2
|
-
# This library loads a gem by the given name. Unlike FileLibrary or ModuleLibrary, this library
|
3
|
-
# doesn't need a module to provide its functionality.
|
4
|
-
#
|
5
|
-
# Example:
|
6
|
-
# >> load_library 'httparty', :class_commands=>{'put'=>'HTTParty.put',
|
7
|
-
# 'delete'=>'HTTParty.delete' }
|
8
|
-
# => true
|
9
|
-
# >> put 'http://someurl.com'
|
10
|
-
class GemLibrary < Library
|
11
|
-
#:stopdoc:
|
12
|
-
def self.is_a_gem?(name)
|
13
|
-
return false unless defined? Gem
|
14
|
-
Gem::VERSION >= '1.8.0' ?
|
15
|
-
Gem::Specification.find_all_by_name(name)[0].is_a?(Gem::Specification) :
|
16
|
-
Gem.searcher.find(name).is_a?(Gem::Specification)
|
17
|
-
end
|
18
|
-
|
19
|
-
handles {|source| is_a_gem?(source.to_s) }
|
20
|
-
|
21
|
-
def loaded_correctly?
|
22
|
-
!@gems.empty? || !@commands.empty? || !!@module
|
23
|
-
end
|
24
|
-
|
25
|
-
def load_source_and_set_module
|
26
|
-
detect_additions { Util.safe_require @name }
|
27
|
-
end
|
28
|
-
#:startdoc:
|
29
|
-
end
|
30
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# This class loads any local file and is most commonly used to load a local
|
2
|
-
# Bosonfile. Since this file doesn't exist inside a normal Repo, it is not indexed with any repo.
|
3
|
-
# Since file-based libraries need to be associated with a repository, Boson associates it
|
4
|
-
# with a local repository if it exists or defaults to Boson.repo. See Boson::FileLibrary
|
5
|
-
# for more info about this library.
|
6
|
-
#
|
7
|
-
# Example:
|
8
|
-
# >> load_library 'Bosonfile'
|
9
|
-
# => true
|
10
|
-
class Boson::LocalFileLibrary < Boson::FileLibrary
|
11
|
-
handles {|source|
|
12
|
-
@repo = (File.exists?(source.to_s) ? (Boson.local_repo || Boson.repo) : nil)
|
13
|
-
!!@repo
|
14
|
-
}
|
15
|
-
|
16
|
-
#:stopdoc:
|
17
|
-
def set_name(name)
|
18
|
-
@lib_file = File.expand_path(name.to_s)
|
19
|
-
File.basename(@lib_file).downcase
|
20
|
-
end
|
21
|
-
|
22
|
-
def base_module
|
23
|
-
Boson::Commands
|
24
|
-
end
|
25
|
-
|
26
|
-
def library_file
|
27
|
-
@lib_file
|
28
|
-
end
|
29
|
-
#:startdoc:
|
30
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module Boson
|
2
|
-
# This library takes a module or class as a library's name and loads its class methods
|
3
|
-
# as commands. If no commands are given it defaults to loading all of its class methods
|
4
|
-
# as commands. The only method callback (see Loader) this library calls on the
|
5
|
-
# original module/class is config().
|
6
|
-
#
|
7
|
-
# Example:
|
8
|
-
# >> load_library Math, :commands=>%w{sin cos tan}
|
9
|
-
# => true
|
10
|
-
#
|
11
|
-
# # Let's brush up on ol trig
|
12
|
-
# >> sin (Math::PI/2)
|
13
|
-
# => 1.0
|
14
|
-
# >> tan (Math::PI/4)
|
15
|
-
# => 1.0
|
16
|
-
# # Close enough :)
|
17
|
-
# >> cos (Math::PI/2)
|
18
|
-
# => 6.12323399573677e-17
|
19
|
-
|
20
|
-
class ModuleLibrary < Library
|
21
|
-
#:stopdoc:
|
22
|
-
handles {|source| source.is_a?(Module) }
|
23
|
-
|
24
|
-
def set_name(name)
|
25
|
-
@module = name
|
26
|
-
underscore_lib = name.to_s[/^Boson::Commands/] ? name.to_s.split('::')[-1] : name.to_s
|
27
|
-
Util.underscore(underscore_lib)
|
28
|
-
end
|
29
|
-
|
30
|
-
def initialize_library_module
|
31
|
-
@class_commands = {@module.to_s=>Array(@commands).empty? ? @module.methods(false) : @commands }
|
32
|
-
@module = nil
|
33
|
-
super
|
34
|
-
end
|
35
|
-
#:startdoc:
|
36
|
-
end
|
37
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# This library requires the given name. This is useful for loading standard libraries,
|
2
|
-
# non-gem libraries (i.e. rip packages) and anything else in $LOAD_PATH.
|
3
|
-
#
|
4
|
-
# Example:
|
5
|
-
# >> load_library 'fileutils', :class_commands=>{'cd'=>'FileUtils.cd', 'cp'=>'FileUtils.cp'}
|
6
|
-
# => true
|
7
|
-
# >> cd '/home'
|
8
|
-
# => 0
|
9
|
-
# >> Dir.pwd
|
10
|
-
# >> '/home'
|
11
|
-
class Boson::RequireLibrary < Boson::GemLibrary
|
12
|
-
EXTENSIONS = ['', '.rb', '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar']
|
13
|
-
handles {|source|
|
14
|
-
extensions_glob = "{#{EXTENSIONS.join(',')}}"
|
15
|
-
($LOAD_PATH - ['.']).any? {|dir|
|
16
|
-
Dir["#{File.expand_path source.to_s, dir}#{extensions_glob}"].size > 0
|
17
|
-
}
|
18
|
-
}
|
19
|
-
|
20
|
-
def loaded_correctly?
|
21
|
-
super || $".grep(/^#{@name}\.([a-z]+)?$/).size > 0
|
22
|
-
end
|
23
|
-
end
|
data/lib/boson/namespace.rb
DELETED
@@ -1,31 +0,0 @@
|
|
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
|
data/lib/boson/pipe.rb
DELETED
@@ -1,147 +0,0 @@
|
|
1
|
-
module Boson
|
2
|
-
# This module passes an original command's return value through methods/commands specified as pipe options. Pipe options
|
3
|
-
# are processed in this order:
|
4
|
-
# * A :query option searches an array of objects or hashes using Pipes.query_pipe.
|
5
|
-
# * A :sort option sorts an array of objects or hashes using Pipes.sort_pipe.
|
6
|
-
# * A :reverse_sort pipe option reverses an array.
|
7
|
-
# * A :pipes option takes an array of commands that modify the return value using Pipes.pipes_pipe.
|
8
|
-
# * All user-defined pipe options (:pipe_options key in Repo.config) are processed in random order.
|
9
|
-
#
|
10
|
-
# Some points:
|
11
|
-
# * User-defined pipes call a command (the option's name by default). It's the user's responsibility to have this
|
12
|
-
# command loaded when used. The easiest way to do this is by adding the pipe command's library to :defaults in main config.
|
13
|
-
# * By default, pipe commands do not modify the value their given. This means you can activate multiple pipes using
|
14
|
-
# a method's original return value.
|
15
|
-
# * A pipe command expects a command's return value as its first argument. If the pipe option takes an argument, it's passed
|
16
|
-
# on as a second argument.
|
17
|
-
# * When piping occurs in relation to rendering depends on the Hirb view. With the default Hirb view, piping occurs
|
18
|
-
# occurs in the middle of the rendering, after Hirb has converted the return value into an array of hashes.
|
19
|
-
# If using a custom Hirb view, piping occurs before rendering.
|
20
|
-
# * What the pipe command should expect as a return value depends on the type of command. If it's a command rendered with hirb's
|
21
|
-
# tables, the return value is a an array of hashes. For everything else, it's the method's original return value.
|
22
|
-
#
|
23
|
-
# === User Pipes
|
24
|
-
# User pipes have the following attributes which alter their behavior:
|
25
|
-
# [*:pipe*] Pipe command the pipe executes when called. Default is the pipe's name.
|
26
|
-
# [*:env*] Boolean which enables passing an additional hash to the pipe command. This hash contains information from the first
|
27
|
-
# command's input with the following keys: :args (command's arguments), :options (command's options),
|
28
|
-
# :global_options (command's global options) and :config (a command's configuration hash). Default is false.
|
29
|
-
# [*:filter*] Boolean which has the pipe command modify the original command's output with the value it returns. Default is false.
|
30
|
-
# [*:no_render*] Boolean to turn off auto-rendering of the original command's final output. Only applicable to :filter enabled
|
31
|
-
# pipes. Default is false.
|
32
|
-
# [*:solo*] Boolean to indicate this pipe can't run with other user pipes or pipes from :pipes option.
|
33
|
-
# If a user calls multiple solo pipes, only the first one detected is called.
|
34
|
-
#
|
35
|
-
# === User Pipes Example
|
36
|
-
# Let's say you want to have two commands, browser and copy, you want to make available as pipe options:
|
37
|
-
# # Opens url in browser. This command already ships with Boson.
|
38
|
-
# def browser(url)
|
39
|
-
# system('open', url)
|
40
|
-
# end
|
41
|
-
#
|
42
|
-
# # Copy to clipboard
|
43
|
-
# def copy(str)
|
44
|
-
# IO.popen('pbcopy', 'w+') {|clipboard| clipboard.write(str)}
|
45
|
-
# end
|
46
|
-
#
|
47
|
-
# To configure them, drop the following config in ~/.boson/config/boson.yml:
|
48
|
-
# :pipe_options:
|
49
|
-
# :browser:
|
50
|
-
# :type: :boolean
|
51
|
-
# :desc: Open in browser
|
52
|
-
# :copy:
|
53
|
-
# :type: :boolean
|
54
|
-
# :desc: Copy to clipboard
|
55
|
-
#
|
56
|
-
# Now for any command that returns a url string, these pipe options can be turned on to execute the url.
|
57
|
-
#
|
58
|
-
# Some examples of these options using commands from {my libraries}[http://github.com/cldwalker/irbfiles]:
|
59
|
-
# # Creates a gist and then opens url in browser and copies it.
|
60
|
-
# $ cat some_file | boson gist -bC # or cat some_file | boson gist --browser --copy
|
61
|
-
#
|
62
|
-
# # Generates rdoc in current directory and then opens it in browser
|
63
|
-
# irb>> rdoc '-b' # or rdoc '--browser'
|
64
|
-
module Pipe
|
65
|
-
extend self
|
66
|
-
|
67
|
-
# Process pipes for Scientist
|
68
|
-
def scientist_process(object, global_opt, env={})
|
69
|
-
@env = env
|
70
|
-
[:query, :sort, :reverse_sort].each {|e| global_opt.delete(e) } unless object.is_a?(Array)
|
71
|
-
process_pipes(object, global_opt)
|
72
|
-
end
|
73
|
-
|
74
|
-
# Main method which processes all pipe commands, both default and user-defined ones.
|
75
|
-
def process_pipes(obj, options)
|
76
|
-
internal_pipes(options).each {|pipe|
|
77
|
-
obj = Pipes.send("#{pipe}_pipe", obj, options[pipe]) if options[pipe]
|
78
|
-
}
|
79
|
-
process_user_pipes(obj, options)
|
80
|
-
end
|
81
|
-
|
82
|
-
# A hash that defines user pipes in the same way as the :pipe_options key in Repo.config.
|
83
|
-
# This method should be called when a pipe's library is loading.
|
84
|
-
def add_pipes(hash)
|
85
|
-
pipe_options.merge! setup_pipes(hash)
|
86
|
-
end
|
87
|
-
|
88
|
-
#:stopdoc:
|
89
|
-
def internal_pipes(global_opt)
|
90
|
-
internals = [:query, :sort, :reverse_sort, :pipes]
|
91
|
-
internals.delete(:pipes) if pipes_to_process(global_opt).any? {|e| pipe(e)[:solo] }
|
92
|
-
internals
|
93
|
-
end
|
94
|
-
|
95
|
-
def pipe_options
|
96
|
-
@pipe_options ||= setup_pipes(Boson.repo.config[:pipe_options] || {})
|
97
|
-
end
|
98
|
-
|
99
|
-
def setup_pipes(hash)
|
100
|
-
hash.each {|k,v| v[:pipe] ||= k }
|
101
|
-
end
|
102
|
-
|
103
|
-
def pipe(key)
|
104
|
-
pipe_options[key] || {}
|
105
|
-
end
|
106
|
-
|
107
|
-
# global_opt can come from Hirb callback or Scientist
|
108
|
-
def process_user_pipes(result, global_opt)
|
109
|
-
pipes_to_process(global_opt).each {|e|
|
110
|
-
args = [pipe(e)[:pipe], result]
|
111
|
-
args << global_opt[e] unless pipe(e)[:type] == :boolean
|
112
|
-
args << get_env(e, global_opt) if pipe(e)[:env]
|
113
|
-
pipe_result = Boson.invoke(*args)
|
114
|
-
result = pipe_result if pipe(e)[:filter]
|
115
|
-
}
|
116
|
-
result
|
117
|
-
end
|
118
|
-
|
119
|
-
def get_env(key, global_opt)
|
120
|
-
{ :global_options=>global_opt.merge(:delete_callbacks=>[:z_boson_pipes]),
|
121
|
-
:config=>(@env[:config].dup[key] || {}),
|
122
|
-
:args=>@env[:args],
|
123
|
-
:options=>@env[:options] || {}
|
124
|
-
}
|
125
|
-
end
|
126
|
-
|
127
|
-
def any_no_render_pipes?(global_opt)
|
128
|
-
!(pipes = pipes_to_process(global_opt)).empty? &&
|
129
|
-
pipes.any? {|e| pipe(e)[:no_render] }
|
130
|
-
end
|
131
|
-
|
132
|
-
def pipes_to_process(global_opt)
|
133
|
-
pipes = (global_opt.keys & pipe_options.keys)
|
134
|
-
(solo_pipe = pipes.find {|e| pipe(e)[:solo] }) ? [solo_pipe] : pipes
|
135
|
-
end
|
136
|
-
#:startdoc:
|
137
|
-
|
138
|
-
# Callbacks used by Hirb::Helpers::Table to search,sort and run custom pipe commands on arrays of hashes.
|
139
|
-
module TableCallbacks
|
140
|
-
# Processes boson's pipes
|
141
|
-
def z_boson_pipes_callback(obj, options)
|
142
|
-
Pipe.process_pipes(obj, options)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
Hirb::Helpers::Table.send :include, Boson::Pipe::TableCallbacks
|