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
data/lib/boson/save.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'boson/namespacer'
|
2
|
+
require 'boson/repo'
|
3
|
+
require 'boson/index'
|
4
|
+
require 'boson/repo_index'
|
5
|
+
|
6
|
+
module Boson
|
7
|
+
module Save
|
8
|
+
def config
|
9
|
+
repo.config
|
10
|
+
end
|
11
|
+
|
12
|
+
def config=(val)
|
13
|
+
repo.config = val
|
14
|
+
end
|
15
|
+
|
16
|
+
# The main required repository which defaults to ~/.boson.
|
17
|
+
def repo
|
18
|
+
@repo ||= Repo.new("#{ENV['BOSON_HOME'] || Dir.home}/.boson")
|
19
|
+
end
|
20
|
+
|
21
|
+
# An optional local repository which defaults to ./lib/boson or ./.boson.
|
22
|
+
def local_repo
|
23
|
+
@local_repo ||= begin
|
24
|
+
ignored_dirs = (config[:ignore_directories] || []).map {|e| File.expand_path(e) }
|
25
|
+
dir = ["lib/boson", ".boson"].find {|e| File.directory?(e) &&
|
26
|
+
File.expand_path(e) != repo.dir && !ignored_dirs.include?(File.expand_path('.')) }
|
27
|
+
Repo.new(dir) if dir
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# The array of loaded repositories containing the main repo and possible local and global repos
|
32
|
+
def repos
|
33
|
+
@repos ||= [repo, local_repo, global_repo].compact
|
34
|
+
end
|
35
|
+
|
36
|
+
# Optional global repository at /etc/boson
|
37
|
+
def global_repo
|
38
|
+
File.exists?('/etc/boson') ? Repo.new('/etc/boson') : nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
extend Save
|
42
|
+
|
43
|
+
class BareRunner
|
44
|
+
module Save
|
45
|
+
def init
|
46
|
+
add_load_path
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def autoload_command(cmd, opts={verbose: Boson.verbose})
|
51
|
+
Index.read
|
52
|
+
(lib = Index.find_library(cmd)) && Manager.load(lib, opts)
|
53
|
+
lib
|
54
|
+
end
|
55
|
+
|
56
|
+
def define_autoloader
|
57
|
+
class << ::Boson.main_object
|
58
|
+
def method_missing(method, *args, &block)
|
59
|
+
if BareRunner.autoload_command(method.to_s)
|
60
|
+
send(method, *args, &block) if respond_to?(method)
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_libraries
|
69
|
+
Boson.repos.map {|e| e.config[:defaults] || [] }.flatten + super
|
70
|
+
end
|
71
|
+
|
72
|
+
# Libraries detected in repositories
|
73
|
+
def detected_libraries
|
74
|
+
Boson.repos.map {|e| e.detected_libraries }.flatten.uniq
|
75
|
+
end
|
76
|
+
|
77
|
+
# Libraries specified in config files and detected_libraries
|
78
|
+
def all_libraries
|
79
|
+
Boson.repos.map {|e| e.all_libraries }.flatten.uniq
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_load_path
|
83
|
+
Boson.repos.each {|repo|
|
84
|
+
if repo.config[:add_load_path] || File.exists?(File.join(repo.dir, 'lib'))
|
85
|
+
$: << File.join(repo.dir, 'lib') unless $:.include? File.expand_path(File.join(repo.dir, 'lib'))
|
86
|
+
end
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
extend Save
|
91
|
+
end
|
92
|
+
|
93
|
+
# * All libraries can be configured by passing a hash of {library attributes}[link:classes/Boson/Library.html#M000077] under
|
94
|
+
# {the :libraries key}[link:classes/Boson/Repo.html#M000070] to the main config file ~/.boson/config/boson.yml.
|
95
|
+
# For most libraries this may be the only way to configure a library's commands.
|
96
|
+
# An example of a GemLibrary config:
|
97
|
+
# :libraries:
|
98
|
+
# httparty:
|
99
|
+
# :class_commands:
|
100
|
+
# delete: HTTParty.delete
|
101
|
+
# :commands:
|
102
|
+
# delete:
|
103
|
+
# :alias: d
|
104
|
+
# :desc: Http delete a given url
|
105
|
+
#
|
106
|
+
# When installing a third-party library, use the config file as a way to override default library and command attributes
|
107
|
+
# without modifying the library.
|
108
|
+
class Library
|
109
|
+
module Save
|
110
|
+
attr_accessor :repo_dir
|
111
|
+
|
112
|
+
def before_initialize
|
113
|
+
@repo_dir = set_repo.dir
|
114
|
+
end
|
115
|
+
|
116
|
+
def config
|
117
|
+
set_repo.config
|
118
|
+
end
|
119
|
+
|
120
|
+
def set_repo
|
121
|
+
Boson.repo
|
122
|
+
end
|
123
|
+
|
124
|
+
def marshal_dump
|
125
|
+
[@name, @commands, @gems, @module.to_s, @repo_dir, @indexed_namespace]
|
126
|
+
end
|
127
|
+
|
128
|
+
def marshal_load(ary)
|
129
|
+
@name, @commands, @gems, @module, @repo_dir, @indexed_namespace = ary
|
130
|
+
end
|
131
|
+
end
|
132
|
+
include Save
|
133
|
+
end
|
134
|
+
|
135
|
+
class Command
|
136
|
+
module Save
|
137
|
+
def marshal_dump
|
138
|
+
if @args && @args.any? {|e| e[1].is_a?(Module) }
|
139
|
+
@args.map! {|e| e.size == 2 ? [e[0], e[1].inspect] : e }
|
140
|
+
@file_parsed_args = true
|
141
|
+
end
|
142
|
+
[@name, @alias, @lib, @desc, @options, @render_options, @args, @default_option]
|
143
|
+
end
|
144
|
+
|
145
|
+
def marshal_load(ary)
|
146
|
+
@name, @alias, @lib, @desc, @options, @render_options, @args, @default_option = ary
|
147
|
+
end
|
148
|
+
end
|
149
|
+
include Save
|
150
|
+
end
|
151
|
+
|
152
|
+
if defined? BinRunner
|
153
|
+
# Any changes to your commands are immediately available from
|
154
|
+
# the commandline except for changes to the main config file. For those
|
155
|
+
# changes to take effect you need to explicitly load and index the libraries
|
156
|
+
# with --index. See RepoIndex to understand how Boson can immediately detect
|
157
|
+
# the latest commands.
|
158
|
+
class BinRunner
|
159
|
+
module Save
|
160
|
+
def autoload_command(cmd)
|
161
|
+
if !Boson.can_invoke?(cmd, false)
|
162
|
+
update_index
|
163
|
+
super(cmd, load_options)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def update_index
|
168
|
+
Index.update(verbose: verbose)
|
169
|
+
end
|
170
|
+
|
171
|
+
def execute_command(cmd, args)
|
172
|
+
@command = cmd # for external errors
|
173
|
+
autoload_command cmd
|
174
|
+
super
|
175
|
+
end
|
176
|
+
|
177
|
+
def command_not_found?(cmd)
|
178
|
+
super && (!(Index.read && Index.find_command(cmd[/\w+/])) || cmd.include?(NAMESPACE))
|
179
|
+
end
|
180
|
+
|
181
|
+
def command_name(cmd)
|
182
|
+
cmd.split(Boson::NAMESPACE)[-1]
|
183
|
+
end
|
184
|
+
|
185
|
+
def eval_execute_option(str)
|
186
|
+
define_autoloader
|
187
|
+
super
|
188
|
+
end
|
189
|
+
|
190
|
+
def default_libraries
|
191
|
+
super + Boson.repos.map {|e| e.config[:bin_defaults] || [] }.flatten +
|
192
|
+
Dir.glob('Bosonfile')
|
193
|
+
end
|
194
|
+
end
|
195
|
+
extend Save
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'boson/view'
|
2
|
+
require 'boson/pipe'
|
3
|
+
require 'boson/pipes'
|
4
|
+
require 'boson/more_scientist'
|
5
|
+
|
6
|
+
module Boson
|
7
|
+
class OptionCommand
|
8
|
+
BASIC_OPTIONS.update(
|
9
|
+
:delete_options=>{:type=>:array, :desc=>'Deletes global options starting with given strings' },
|
10
|
+
:usage_options=>{:type=>:string, :desc=>"Render options to pass to usage/help"},
|
11
|
+
:render=> {:type=>:boolean, :desc=>"Toggle a command's default rendering behavior"})
|
12
|
+
PIPE_OPTIONS = {
|
13
|
+
:sort=>{:type=>:string, :desc=>"Sort by given field"},
|
14
|
+
:reverse_sort=>{:type=>:boolean, :desc=>"Reverse a given sort"},
|
15
|
+
:query=>{:type=>:hash, :desc=>"Queries fields given field:search pairs"},
|
16
|
+
:pipes=>{:alias=>'P', :type=>:array, :desc=>"Pipe to commands sequentially"}
|
17
|
+
} #:nodoc:
|
18
|
+
|
19
|
+
RENDER_OPTIONS = {
|
20
|
+
:fields=>{:type=>:array, :desc=>"Displays fields in the order given"},
|
21
|
+
:class=>{:type=>:string, :desc=>"Hirb helper class which renders"},
|
22
|
+
:max_width=>{:type=>:numeric, :desc=>"Max width of a table"},
|
23
|
+
:vertical=>{:type=>:boolean, :desc=>"Display a vertical table"},
|
24
|
+
} #:nodoc:
|
25
|
+
|
26
|
+
# Adds render and pipe global options
|
27
|
+
# For more about pipe and render options see Pipe and View respectively.
|
28
|
+
# === Toggling Views With the Basic Global Option --render
|
29
|
+
# One of the more important global options is --render. This option toggles the rendering of a command's
|
30
|
+
# output done with View and Hirb[http://github.com/cldwalker/hirb].
|
31
|
+
#
|
32
|
+
# Here's a simple example of toggling Hirb's table view:
|
33
|
+
# # Defined in a library file:
|
34
|
+
# #@options {}
|
35
|
+
# def list(options={})
|
36
|
+
# [1,2,3]
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Using it in irb:
|
40
|
+
# >> list
|
41
|
+
# => [1,2,3]
|
42
|
+
# >> list '-r' # or list --render
|
43
|
+
# +-------+
|
44
|
+
# | value |
|
45
|
+
# +-------+
|
46
|
+
# | 1 |
|
47
|
+
# | 2 |
|
48
|
+
# | 3 |
|
49
|
+
# +-------+
|
50
|
+
# 3 rows in set
|
51
|
+
# => true
|
52
|
+
# == Additional config keys for the main repo config
|
53
|
+
# [:render_options] Hash of render options available to all option commands to be passed to a Hirb view (see View). Since
|
54
|
+
# this merges with default render options, it's possible to override default render options.
|
55
|
+
# [:no_auto_render] When set, turns off commandline auto-rendering of a command's output. Default is false.
|
56
|
+
module ClassRender
|
57
|
+
|
58
|
+
def default_options
|
59
|
+
default_pipe_options.merge(default_render_options.merge(BASIC_OPTIONS))
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_pipe_options
|
63
|
+
@default_pipe_options ||= PIPE_OPTIONS.merge Pipe.pipe_options
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_render_options
|
67
|
+
@default_render_options ||= RENDER_OPTIONS.merge Boson.repo.config[:render_options] || {}
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete_non_render_options(opt)
|
71
|
+
opt.delete_if {|k,v| BASIC_OPTIONS.keys.include?(k) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
extend ClassRender
|
75
|
+
|
76
|
+
module Render
|
77
|
+
def option_parser
|
78
|
+
@option_parser ||= @command.render_options ? OptionParser.new(all_global_options) :
|
79
|
+
self.class.default_option_parser
|
80
|
+
end
|
81
|
+
|
82
|
+
def all_global_options
|
83
|
+
OptionParser.make_mergeable! @command.render_options
|
84
|
+
render_opts = Util.recursive_hash_merge(@command.render_options, Util.deep_copy(self.class.default_render_options))
|
85
|
+
merged_opts = Util.recursive_hash_merge Util.deep_copy(self.class.default_pipe_options), render_opts
|
86
|
+
opts = Util.recursive_hash_merge merged_opts, Util.deep_copy(BASIC_OPTIONS)
|
87
|
+
set_global_option_defaults opts
|
88
|
+
end
|
89
|
+
|
90
|
+
def set_global_option_defaults(opts)
|
91
|
+
if !opts[:fields].key?(:values)
|
92
|
+
if opts[:fields][:default]
|
93
|
+
opts[:fields][:values] = opts[:fields][:default]
|
94
|
+
else
|
95
|
+
if opts[:change_fields] && (changed = opts[:change_fields][:default])
|
96
|
+
opts[:fields][:values] = changed.is_a?(Array) ? changed : changed.values
|
97
|
+
end
|
98
|
+
opts[:fields][:values] ||= opts[:headers][:default].keys if opts[:headers] && opts[:headers][:default]
|
99
|
+
end
|
100
|
+
opts[:fields][:enum] = false if opts[:fields][:values] && !opts[:fields].key?(:enum)
|
101
|
+
end
|
102
|
+
if opts[:fields][:values]
|
103
|
+
opts[:sort][:values] ||= opts[:fields][:values]
|
104
|
+
opts[:query][:keys] ||= opts[:fields][:values]
|
105
|
+
opts[:query][:default_keys] ||= "*"
|
106
|
+
end
|
107
|
+
opts
|
108
|
+
end
|
109
|
+
end
|
110
|
+
include Render
|
111
|
+
end
|
112
|
+
|
113
|
+
module Scientist
|
114
|
+
# * Before a method returns its value, it pipes its return value through pipe commands if pipe options are specified. See Pipe.
|
115
|
+
# * Methods can have any number of optional views associated with them via global render options (see View). Views can be toggled
|
116
|
+
# on/off with the global option --render (see OptionCommand).
|
117
|
+
module Render
|
118
|
+
attr_accessor :rendered, :render
|
119
|
+
|
120
|
+
def after_parse
|
121
|
+
(@global_options[:delete_options] || []).map {|e|
|
122
|
+
@global_options.keys.map {|k| k.to_s }.grep(/^#{e}/)
|
123
|
+
}.flatten.each {|e| @global_options.delete(e.to_sym) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def process_result(result)
|
127
|
+
if (@rendered = can_render?)
|
128
|
+
if @global_options.key?(:class) || @global_options.key?(:method)
|
129
|
+
result = Pipe.scientist_process(result, @global_options, :config=>@command.config, :args=>@args, :options=>@current_options)
|
130
|
+
end
|
131
|
+
View.render(result, OptionCommand.delete_non_render_options(@global_options.dup), false)
|
132
|
+
else
|
133
|
+
Pipe.scientist_process(result, @global_options, :config=>@command.config, :args=>@args, :options=>@current_options)
|
134
|
+
end
|
135
|
+
rescue StandardError
|
136
|
+
raise Scientist::Error, $!.message, $!.backtrace
|
137
|
+
end
|
138
|
+
|
139
|
+
def can_render?
|
140
|
+
render.nil? ? command_renders? : render
|
141
|
+
end
|
142
|
+
|
143
|
+
def command_renders?
|
144
|
+
(!!@command.render_options ^ @global_options[:render]) && !Pipe.any_no_render_pipes?(@global_options)
|
145
|
+
end
|
146
|
+
|
147
|
+
def run_pretend_option(args)
|
148
|
+
super
|
149
|
+
@rendered = true if @global_options[:pretend]
|
150
|
+
end
|
151
|
+
|
152
|
+
def help_options
|
153
|
+
super.tap do |opts|
|
154
|
+
if @global_options[:usage_options]
|
155
|
+
opts << "--render_options=#{@global_options[:usage_options]}"
|
156
|
+
end
|
157
|
+
opts
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
extend Render
|
162
|
+
end
|
163
|
+
|
164
|
+
class Command
|
165
|
+
module ScienceClassMethods
|
166
|
+
attr_accessor :all_option_commands
|
167
|
+
|
168
|
+
def create(name, library)
|
169
|
+
super.tap do |obj|
|
170
|
+
if @all_option_commands && !%w{get method_missing}.include?(name)
|
171
|
+
obj.make_option_command(library)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
extend ScienceClassMethods
|
177
|
+
|
178
|
+
module Science
|
179
|
+
# Option parser for command as defined by @render_options.
|
180
|
+
def render_option_parser
|
181
|
+
option_command? ? Boson::Scientist.option_command(self).option_parser : nil
|
182
|
+
end
|
183
|
+
|
184
|
+
def make_option_command(lib=library)
|
185
|
+
@option_command = true
|
186
|
+
@args = [['*args']] unless args(lib) || arg_size
|
187
|
+
end
|
188
|
+
|
189
|
+
def option_command?
|
190
|
+
super || render_options
|
191
|
+
end
|
192
|
+
end
|
193
|
+
include Science
|
194
|
+
end
|
195
|
+
|
196
|
+
# [*:render_options*] Hash of rendering options to pass to OptionParser. If the key :output_class is passed,
|
197
|
+
# that class's Hirb config will serve as defaults for this rendering hash.
|
198
|
+
class Command
|
199
|
+
attr_accessor :render_options
|
200
|
+
|
201
|
+
module Science
|
202
|
+
def after_initialize(hash)
|
203
|
+
if hash[:render_options] && (@render_options = hash.delete(:render_options))[:output_class]
|
204
|
+
@render_options = Util.recursive_hash_merge View.class_config(@render_options[:output_class]), @render_options
|
205
|
+
end
|
206
|
+
super
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
if defined? BinRunner
|
212
|
+
class BinRunner < BareRunner
|
213
|
+
GLOBAL_OPTIONS.update(
|
214
|
+
option_commands: {
|
215
|
+
:type=>:boolean,
|
216
|
+
:desc=>"Toggles on all commands to be defined as option commands"
|
217
|
+
},
|
218
|
+
render: {:type=>:boolean,
|
219
|
+
:desc=>"Renders a Hirb view from result of command without options"}
|
220
|
+
)
|
221
|
+
|
222
|
+
# [:render] Toggles the auto-rendering done for commands that don't have views. Doesn't affect commands that already have views.
|
223
|
+
# Default is false. Also see Auto Rendering section below.
|
224
|
+
#
|
225
|
+
# ==== Auto Rendering
|
226
|
+
# Commands that don't have views (defined via render_options) have their return value auto-rendered as a view as follows:
|
227
|
+
# * nil,false and true aren't rendered
|
228
|
+
# * arrays are rendered with Hirb's tables
|
229
|
+
# * non-arrays are printed with inspect()
|
230
|
+
# * Any of these cases can be toggled to render/not render with the global option :render
|
231
|
+
# To turn off auto-rendering by default, add a :no_auto_render: true entry to the main config.
|
232
|
+
module Science
|
233
|
+
def init
|
234
|
+
Command.all_option_commands = true if @options[:option_commands]
|
235
|
+
super
|
236
|
+
end
|
237
|
+
|
238
|
+
def render_output(output)
|
239
|
+
if (!Scientist.rendered && !View.silent_object?(output)) ^ @options[:render] ^
|
240
|
+
Boson.repo.config[:no_auto_render]
|
241
|
+
opts = output.is_a?(String) ? {:method=>'puts'} :
|
242
|
+
{:inspect=>!output.is_a?(Array) || (Scientist.global_options || {})[:render] }
|
243
|
+
View.render output, opts
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def allowed_argument_error?(err, cmd, args)
|
248
|
+
err.class == OptionCommand::CommandArgumentError || super
|
249
|
+
end
|
250
|
+
|
251
|
+
def execute_command(cmd, args)
|
252
|
+
render_output super
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class <<self
|
257
|
+
include Science
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Additional method attributes:
|
263
|
+
# * render_options: Hash to define an OptionParser object for a command's local/global render options (see View).
|
264
|
+
class MethodInspector
|
265
|
+
METHODS << :render_options
|
266
|
+
METHOD_CLASSES[:render_options] = Hash
|
267
|
+
SCRAPEABLE_METHODS << :render_options
|
268
|
+
end
|
269
|
+
|
270
|
+
module CommentInspector
|
271
|
+
EVAL_ATTRIBUTES << :render_options
|
272
|
+
end
|
273
|
+
end
|
data/lib/boson/view.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'hirb'
|
2
|
+
require 'boson/more_option_parser'
|
3
|
+
|
4
|
+
module Boson
|
5
|
+
# This module generates views for a command by handing it to {Hirb}[http://tagaholic.me/hirb/]. Since Hirb can be customized
|
6
|
+
# to generate any view, commands can have any views associated with them!
|
7
|
+
#
|
8
|
+
# === Views with Render Options
|
9
|
+
# To pass rendering options to a Hirb helper as command options, a command has to define the options with
|
10
|
+
# the render_options method attribute:
|
11
|
+
#
|
12
|
+
# # @render_options :fields=>[:a,:b]
|
13
|
+
# def list(options={})
|
14
|
+
# [{:a=>1, :b=>2}, {:a=>10,:b=>11}]
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # To see that the render_options method attribute actually passes the :fields option by default:
|
18
|
+
# >> list '-p' # or list '--pretend'
|
19
|
+
# Arguments: []
|
20
|
+
# Global options: {:pretend=>true, :fields=>[:a, :b]}
|
21
|
+
#
|
22
|
+
# >> list
|
23
|
+
# +----+----+
|
24
|
+
# | a | b |
|
25
|
+
# +----+----+
|
26
|
+
# | 1 | 2 |
|
27
|
+
# | 10 | 11 |
|
28
|
+
# +----+----+
|
29
|
+
# 2 rows in set
|
30
|
+
#
|
31
|
+
# # To create a vertical table, we can pass --vertical, one of the default global render options.
|
32
|
+
# >> list '-V' # or list '--vertical'
|
33
|
+
# *** 1. row ***
|
34
|
+
# a: 1
|
35
|
+
# b: 2
|
36
|
+
# ...
|
37
|
+
#
|
38
|
+
# # To get the original return value use the global option --render
|
39
|
+
# >> list '-r' # or list '--render'
|
40
|
+
# => [{:a=>1, :b=>2}, {:a=>10,:b=>11}]
|
41
|
+
#
|
42
|
+
# === Boson and Hirb
|
43
|
+
# Since Boson uses {Hirb's auto table helper}[http://tagaholic.me/hirb/doc/classes/Hirb/Helpers/AutoTable.html]
|
44
|
+
# by default, you may want to read up on its many options. To use any of them in commands, define them locally
|
45
|
+
# with render_options or globally by adding them under the :render_options key of the main config.
|
46
|
+
# What if you want to use your own helper class? No problem. Simply pass it with the global :class option.
|
47
|
+
#
|
48
|
+
# When using the default helper, one of the most important options to define is :fields. Aside from controlling what fields
|
49
|
+
# are displayed, it's used to set :values option attributes for related options i.e. :sort and :query. This provides handy option
|
50
|
+
# value aliasing via OptionParser. If you don't set :fields, Boson will try to set its :values with field-related options i.e.
|
51
|
+
# :change_fields, :filters and :headers.
|
52
|
+
module View
|
53
|
+
extend self
|
54
|
+
|
55
|
+
# Enables hirb and reads a config file from the main repo's config/hirb.yml.
|
56
|
+
def enable
|
57
|
+
unless @enabled
|
58
|
+
Hirb::View.enable(:config_file=>File.join(Boson.repo.config_dir, 'hirb.yml'))
|
59
|
+
Hirb::Helpers::Table.filter_any = true
|
60
|
+
end
|
61
|
+
@enabled = true
|
62
|
+
end
|
63
|
+
|
64
|
+
# Renders any object via Hirb. Options are passed directly to
|
65
|
+
# {Hirb::Console.render_output}[http://tagaholic.me/hirb/doc/classes/Hirb/Console.html#M000011].
|
66
|
+
def render(object, options={}, return_obj=false)
|
67
|
+
if options[:inspect]
|
68
|
+
puts(object.inspect)
|
69
|
+
else
|
70
|
+
render_object(object, options, return_obj) unless silent_object?(object)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#:stopdoc:
|
75
|
+
def class_config(klass)
|
76
|
+
opts = (Hirb::View.formatter_config[klass] || {}).dup
|
77
|
+
opts.delete(:ancestor)
|
78
|
+
opts.merge!((opts.delete(:options) || {}).dup)
|
79
|
+
OptionParser.make_mergeable!(opts)
|
80
|
+
opts
|
81
|
+
end
|
82
|
+
|
83
|
+
def toggle_pager
|
84
|
+
Hirb::View.toggle_pager
|
85
|
+
end
|
86
|
+
|
87
|
+
def silent_object?(obj)
|
88
|
+
[nil,false,true].include?(obj)
|
89
|
+
end
|
90
|
+
|
91
|
+
def render_object(object, options={}, return_obj=false)
|
92
|
+
options[:class] ||= :auto_table
|
93
|
+
render_result = Hirb::Console.render_output(object, options)
|
94
|
+
return_obj ? object : render_result
|
95
|
+
end
|
96
|
+
#:startdoc:
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'boson/view'
|
2
|
+
|
3
|
+
module Boson
|
4
|
+
if defined? BinRunner
|
5
|
+
class BinRunner
|
6
|
+
module Viewable
|
7
|
+
def print_usage_header
|
8
|
+
super
|
9
|
+
puts "GLOBAL OPTIONS"
|
10
|
+
View.enable
|
11
|
+
end
|
12
|
+
end
|
13
|
+
extend Viewable
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class BareRunner
|
18
|
+
module Viewable
|
19
|
+
def init
|
20
|
+
View.enable
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
extend Viewable
|
25
|
+
end
|
26
|
+
|
27
|
+
class OptionParser
|
28
|
+
module Viewable
|
29
|
+
def get_fields_and_options(fields, options)
|
30
|
+
(fields << :default).uniq! if options.delete(:local) || options[:fields] == '*'
|
31
|
+
fields, opts = super(fields, options)
|
32
|
+
fields.delete(:default) if fields.include?(:default) && opts.all? {|e| e[:default].nil? }
|
33
|
+
[fields, opts]
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_render_options #:nodoc:
|
37
|
+
{:header_filter=>:capitalize, :description=>false, :filter_any=>true,
|
38
|
+
:filter_classes=>{Array=>[:join, ',']}, :hide_empty=>true}
|
39
|
+
end
|
40
|
+
|
41
|
+
def render_table(fields, arr, options)
|
42
|
+
options = default_render_options.merge(:fields=>fields).merge(options)
|
43
|
+
View.render arr, options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
include Viewable
|
47
|
+
end
|
48
|
+
end
|
data/test/alias_test.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Add library_loaded? and with_config
|
2
|
+
describe "Manager" do
|
3
|
+
def load_library(hash)
|
4
|
+
new_attributes = {:name=>hash[:name], :commands=>[], :created_dependencies=>[], :loaded=>true}
|
5
|
+
[:module, :commands].each {|e| new_attributes[e] = hash.delete(e) if hash[e] }
|
6
|
+
Manager.expects(:call_load_action).returns(Library.new(new_attributes))
|
7
|
+
Manager.load([hash[:name]])
|
8
|
+
end
|
9
|
+
|
10
|
+
before { reset_boson }
|
11
|
+
|
12
|
+
describe "command aliases" do
|
13
|
+
before { eval %[module ::Aquateen; def frylock; end; end] }
|
14
|
+
after { Object.send(:remove_const, "Aquateen") }
|
15
|
+
|
16
|
+
it "created with command specific config" do
|
17
|
+
with_config(:command_aliases=>{'frylock'=>'fr'}) do
|
18
|
+
Manager.expects(:create_instance_aliases).with({"Aquateen"=>{"frylock"=>"fr"}})
|
19
|
+
load_library :name=>'aquateen', :commands=>['frylock'], :module=>Aquateen
|
20
|
+
library_loaded? 'aquateen'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "created with config command_aliases" do
|
25
|
+
with_config(:command_aliases=>{"frylock"=>"fr"}) do
|
26
|
+
Manager.expects(:create_instance_aliases).with({"Aquateen"=>{"frylock"=>"fr"}})
|
27
|
+
load_library :name=>'aquateen', :commands=>['frylock'], :module=>Aquateen
|
28
|
+
library_loaded? 'aquateen'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "not created and warns for commands with no module" do
|
33
|
+
with_config(:command_aliases=>{'frylock'=>'fr'}) do
|
34
|
+
capture_stderr {
|
35
|
+
load_library(:name=>'aquateen', :commands=>['frylock'])
|
36
|
+
}.should =~ /No aliases/
|
37
|
+
library_loaded? 'aquateen'
|
38
|
+
Aquateen.method_defined?(:fr).should == false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "Loader" do
|
45
|
+
describe "load" do
|
46
|
+
before { reset }
|
47
|
+
it "loads a library and creates its class commands" do
|
48
|
+
with_config(:libraries=>{"blah"=>{:class_commands=>{"bling"=>"Blah.bling", "Blah"=>['hmm']}}}) do
|
49
|
+
load :blah, :file_string=>"module Blah; def self.bling; end; def self.hmm; end; end"
|
50
|
+
command_exists? 'bling'
|
51
|
+
command_exists? 'hmm'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|