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