ori 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/MIT-LICENSE +20 -0
- data/README.html +158 -0
- data/README.md +160 -0
- data/Rakefile +60 -0
- data/VERSION.yml +5 -0
- data/lib/misc/method_aliases.rb +11 -0
- data/lib/ori.rb +47 -0
- data/lib/ori/auto_config.rb +78 -0
- data/lib/ori/colorize.rb +62 -0
- data/lib/ori/config.rb +27 -0
- data/lib/ori/extensions.rb +4 -0
- data/lib/ori/extensions/object/ri.rb +88 -0
- data/lib/ori/internals.rb +411 -0
- data/lib/ori/library.rb +59 -0
- data/lib/ori/list_method.rb +247 -0
- data/lib/ori/request.rb +118 -0
- data/lib/ori/tools.rb +142 -0
- data/ori.gemspec +82 -0
- data/samples/NOTES +3 -0
- data/samples/basic_extension.rb +27 -0
- data/samples/basic_inheritance.rb +99 -0
- data/samples/self_singletons.rb +36 -0
- data/samples/singleton_class_includes_module.rb +33 -0
- data/spec/auto_config_spec.rb +33 -0
- data/spec/colorize_spec.rb +26 -0
- data/spec/inspector_spec.rb +75 -0
- data/spec/internals_spec.rb +219 -0
- data/spec/list_method_spec.rb +118 -0
- data/spec/request_spec.rb +72 -0
- data/spec/site/NOTES +3 -0
- data/spec/site/library_spec.rb +33 -0
- data/spec/site/spec_helper.rb +1 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/tools_spec.rb +109 -0
- metadata +107 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
module ORI
|
2
|
+
# Propose config defaults based on OS and environment.
|
3
|
+
class AutoConfig #:nodoc:
|
4
|
+
# Value of <tt>RbConfig::Config["host_os"]</tt>.
|
5
|
+
#
|
6
|
+
# linux-gnu
|
7
|
+
# mswin32
|
8
|
+
# cygwin
|
9
|
+
attr_reader :host_os
|
10
|
+
|
11
|
+
def initialize(attrs = {})
|
12
|
+
attrs.each {|k, v| send("#{k}=", v)}
|
13
|
+
clear_cache
|
14
|
+
end
|
15
|
+
|
16
|
+
#--------------------------------------- Accessors and pseudo-accessors
|
17
|
+
|
18
|
+
def has_less?
|
19
|
+
@cache[:has_less] ||= begin
|
20
|
+
require_host_os
|
21
|
+
!!@host_os.match(/cygwin|darwin|freebsd|gnu|linux/i)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def host_os=(s)
|
26
|
+
@host_os = s
|
27
|
+
clear_cache
|
28
|
+
end
|
29
|
+
|
30
|
+
def unix?
|
31
|
+
@cache[:is_unix] ||= begin
|
32
|
+
require_host_os
|
33
|
+
!!@host_os.match(/cygwin|darwin|freebsd|gnu|linux|sunos|solaris/i)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def windows?
|
38
|
+
@cache[:is_windows] ||= begin
|
39
|
+
require_host_os
|
40
|
+
!!@host_os.match(/mswin|windows/i)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#--------------------------------------- Defaults
|
45
|
+
|
46
|
+
def color
|
47
|
+
@cache[:color] ||= unix?? true : false
|
48
|
+
end
|
49
|
+
|
50
|
+
def frontend
|
51
|
+
@cache[:frontend] ||= unix?? "ri -T -f ansi %s" : "ri -T %s"
|
52
|
+
end
|
53
|
+
|
54
|
+
def pager
|
55
|
+
@cache[:pager] ||= has_less?? "less -R" : "more"
|
56
|
+
end
|
57
|
+
|
58
|
+
def shell_escape
|
59
|
+
@cache[:shell_escape] ||= if unix?
|
60
|
+
:unix
|
61
|
+
elsif windows?
|
62
|
+
:windows
|
63
|
+
else
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def clear_cache
|
71
|
+
@cache = {}
|
72
|
+
end
|
73
|
+
|
74
|
+
def require_host_os
|
75
|
+
raise "`host_os` is not set" if not @host_os
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/ori/colorize.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module ORI
|
2
|
+
# Simplistic ANSI colorizer.
|
3
|
+
module Colorize #:nodoc:
|
4
|
+
# Issue an ANSI color sequence.
|
5
|
+
#
|
6
|
+
# puts [Colorize.seq(:message, :error), "Error!", Colorize.seq(:reset)].join
|
7
|
+
def self.seq(*spec)
|
8
|
+
Tools.ansi(*case spec
|
9
|
+
when [:choice, :title]
|
10
|
+
[:green]
|
11
|
+
when [:choice, :index]
|
12
|
+
[:yellow, :bold]
|
13
|
+
when [:choice, :label]
|
14
|
+
[:cyan]
|
15
|
+
when [:choice, :prompt]
|
16
|
+
[:yellow, :bold]
|
17
|
+
|
18
|
+
# These go in sequence, each knows who's before. Thus we minimize ANSI.
|
19
|
+
when [:list_method, :own_marker]
|
20
|
+
[:reset, :bold]
|
21
|
+
when [:list_method, :not_own_marker]
|
22
|
+
[:reset]
|
23
|
+
when [:list_method, :obj_module_name]
|
24
|
+
[:cyan, :bold]
|
25
|
+
when [:list_method, :owner_name]
|
26
|
+
[:reset]
|
27
|
+
when [:list_method, :access]
|
28
|
+
[:reset, :cyan]
|
29
|
+
when [:list_method, :name]
|
30
|
+
[:reset, :bold]
|
31
|
+
when [:list_method, :visibility]
|
32
|
+
[:reset, :yellow]
|
33
|
+
|
34
|
+
# These go in sequence.
|
35
|
+
when [:mam, :module_name]
|
36
|
+
[:cyan, :bold]
|
37
|
+
when [:mam, :access]
|
38
|
+
[:reset, :cyan]
|
39
|
+
when [:mam, :method_name]
|
40
|
+
[:reset, :bold]
|
41
|
+
|
42
|
+
when [:message, :action]
|
43
|
+
[:green]
|
44
|
+
when [:message, :error]
|
45
|
+
[:red, :bold]
|
46
|
+
when [:message, :info]
|
47
|
+
[:green]
|
48
|
+
|
49
|
+
when [:reset]
|
50
|
+
[:reset]
|
51
|
+
|
52
|
+
else
|
53
|
+
raise ArgumentError, "Unknown spec: #{spec.inspect}"
|
54
|
+
end
|
55
|
+
) # Tools.ansi
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.colorize(*args)
|
59
|
+
args.map {|v| v.is_a?(Array) ? seq(*v) : v}.join
|
60
|
+
end
|
61
|
+
end # Colorize
|
62
|
+
end
|
data/lib/ori/config.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module ORI
|
2
|
+
# Configuration object.
|
3
|
+
class Config
|
4
|
+
# Enable color. Example:
|
5
|
+
#
|
6
|
+
# true
|
7
|
+
attr_accessor :color
|
8
|
+
|
9
|
+
# RI frontend command to use. <tt>%s</tt> is replaced with sought topic. Example:
|
10
|
+
#
|
11
|
+
# ri -T -f ansi %s
|
12
|
+
attr_accessor :frontend
|
13
|
+
|
14
|
+
# Paging program to use. Examples:
|
15
|
+
#
|
16
|
+
# less -R
|
17
|
+
# more
|
18
|
+
attr_accessor :pager
|
19
|
+
|
20
|
+
# Shell escape mode. <tt>:unix</tt> or <tt>:windows</tt>.
|
21
|
+
attr_accessor :shell_escape
|
22
|
+
|
23
|
+
def initialize(attrs = {})
|
24
|
+
attrs.each {|k, v| send("#{k}=", v)}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module ORI
|
2
|
+
module Extensions
|
3
|
+
module Object
|
4
|
+
# View RI pages on module, class, method. Interactively list receiver's methods.
|
5
|
+
#
|
6
|
+
# == Request RI on a Class
|
7
|
+
#
|
8
|
+
# Array.ri
|
9
|
+
# String.ri
|
10
|
+
# [].ri
|
11
|
+
# "".ri
|
12
|
+
# 5.ri
|
13
|
+
#
|
14
|
+
# So that's fairly straightforward -- grab a class or class instance and call <tt>ri</tt> on it:
|
15
|
+
#
|
16
|
+
# obj = SomeKlass.new
|
17
|
+
# obj.ri
|
18
|
+
#
|
19
|
+
# == Request RI on a Method
|
20
|
+
#
|
21
|
+
# String.ri :upcase
|
22
|
+
# "".ri :upcase
|
23
|
+
# [].ri :sort
|
24
|
+
# Hash.ri :[]
|
25
|
+
# Hash.ri "::[]"
|
26
|
+
# Hash.ri "#[]"
|
27
|
+
#
|
28
|
+
# == Request Interactive Method List
|
29
|
+
#
|
30
|
+
# # Regular expression argument denotes list request.
|
31
|
+
# String.ri //
|
32
|
+
# "".ri //
|
33
|
+
#
|
34
|
+
# # Show method names matching a regular expression.
|
35
|
+
# "".ri /case/
|
36
|
+
# "".ri /^to_/
|
37
|
+
# [].ri /sort/
|
38
|
+
# {}.ri /each/
|
39
|
+
#
|
40
|
+
# # Show ALL methods, including those private of Kernel.
|
41
|
+
# Hash.ri //, :all => true
|
42
|
+
# Hash.ri //, :all
|
43
|
+
#
|
44
|
+
# # Show class methods or instance methods only.
|
45
|
+
# Module.ri //, :access => "::"
|
46
|
+
# Module.ri //, :access => "#"
|
47
|
+
#
|
48
|
+
# # Show own methods only.
|
49
|
+
# Time.ri //, :own => true
|
50
|
+
# Time.ri //, :own
|
51
|
+
#
|
52
|
+
# # Specify visibility: public, protected or private.
|
53
|
+
# Module.ri //, :visibility => :private
|
54
|
+
# Module.ri //, :visibility => [:public, :protected]
|
55
|
+
#
|
56
|
+
# # Filter fully formatted name by given regexp.
|
57
|
+
# Module.ri //, :fullre => /\(Object\)::/
|
58
|
+
#
|
59
|
+
# # Combine options.
|
60
|
+
# Module.ri //, :fullre => /\(Object\)::/, :access => "::", :visibility => :private
|
61
|
+
#
|
62
|
+
# == Request Interactive Method List for More Than 1 Object at Once
|
63
|
+
#
|
64
|
+
# By using the <tt>:join</tt> option it's possible to fetch methods for more
|
65
|
+
# than 1 object at once. Value of <tt>:join</tt> (which can be an object or an array)
|
66
|
+
# is joined with the original receiver, and then a combined set is queried.
|
67
|
+
#
|
68
|
+
# # List all division-related methods from numeric classes.
|
69
|
+
# Fixnum.ri /div/, :join => [Float, Rational]
|
70
|
+
# 5.ri /div/, :join => [5.0, 5.to_r]
|
71
|
+
#
|
72
|
+
# # List all ActiveSupport extensions to numeric classes.
|
73
|
+
# 5.ri //, :join => [5.0, 5.to_r], :fullre => /ActiveSupport/
|
74
|
+
#
|
75
|
+
# # Query entire Rails family for methods having the word "javascript".
|
76
|
+
# rails_modules = ObjectSpace.each_object(Module).select {|mod| mod.to_s.match /Active|Action/}
|
77
|
+
# "".ri /javascript/, :join => rails_modules
|
78
|
+
def ri(*args)
|
79
|
+
::ORI::Internals.do_history
|
80
|
+
::ORI::Internals.ri(self, *args)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class Object #:nodoc:
|
87
|
+
include ::ORI::Extensions::Object
|
88
|
+
end
|
@@ -0,0 +1,411 @@
|
|
1
|
+
module ORI
|
2
|
+
# Tools used internally by ORI.
|
3
|
+
module Internals #:nodoc:
|
4
|
+
GLM_ALL_ACCESSES = ["::", "#"]
|
5
|
+
GLM_ALL_VISIBILITIES = [:public, :protected, :private]
|
6
|
+
|
7
|
+
# Error message for the user. Sometimes it's CAUSED by the user, sometimes it's influenced by him.
|
8
|
+
class UserError < Exception #:nodoc:
|
9
|
+
end
|
10
|
+
|
11
|
+
# Non-destructive break request.
|
12
|
+
class Break < Exception #:nodoc:
|
13
|
+
end
|
14
|
+
|
15
|
+
# Apply smart filters on array of <tt>ListMethod</tt>. Return filtered array.
|
16
|
+
def self.apply_smart_filters(obj, list_methods)
|
17
|
+
# Filters.
|
18
|
+
#
|
19
|
+
# * Filters return false if record is "bad". Any other return result means that record is "good".
|
20
|
+
filters = []
|
21
|
+
|
22
|
+
# Hide all methods starting with "_ori_".
|
23
|
+
filters << proc do |r|
|
24
|
+
if r.method_name.match /\A_ori_/
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Obj is not Kernel.
|
30
|
+
if (obj != Kernel rescue false)
|
31
|
+
filters << proc do |r|
|
32
|
+
# Chop off Kernel's non-public methods.
|
33
|
+
if r.owner == Kernel and not r.public?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Obj is an object.
|
40
|
+
if not obj.is_a? Module
|
41
|
+
filters << proc do |r|
|
42
|
+
# Chop off non-public methods.
|
43
|
+
if not r.public?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Obj is a module or a class.
|
50
|
+
if obj.is_a? Module
|
51
|
+
filters << proc do |r|
|
52
|
+
# Chop off others' private instance methods.
|
53
|
+
# NOTE: We shouldn't chop private singleton methods since they are callable from the context of our class. See Sample::BasicExtension::Klass.
|
54
|
+
if not r.own? and r.private? and r.instance?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Go! If any filter rejects the record, it's rejected.
|
61
|
+
list_methods.reject do |r|
|
62
|
+
filters.any? {|f| f.call(r) == false}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Process interactive choice.
|
67
|
+
# Return chosen item or <tt>nil</tt>.
|
68
|
+
#
|
69
|
+
# choice [
|
70
|
+
# ["wan", 1.0],
|
71
|
+
# ["tew", 2.0],
|
72
|
+
# ["free", 3.0],
|
73
|
+
# ]
|
74
|
+
#
|
75
|
+
# Options:
|
76
|
+
#
|
77
|
+
# :colorize_labels => T|F # Default is true.
|
78
|
+
# :item_indent => " " # Default is " ".
|
79
|
+
# :prompt => ">" # Default is ">>".
|
80
|
+
# :title => "my title" # Dialog title. Default is nil (no title).
|
81
|
+
#
|
82
|
+
# :on_abort => obj # Result to return on abort (Ctrl-C). Default is nil.
|
83
|
+
# :on_skip => obj # Treat empty input as skip action. Default is nil.
|
84
|
+
def self.choice(items, options = {})
|
85
|
+
raise ArgumentError, "At least 1 item required" if items.size < 1
|
86
|
+
|
87
|
+
options = options.dup
|
88
|
+
o = {}
|
89
|
+
|
90
|
+
o[k = :colorize_labels] = (v = options.delete(k)).nil?? true : v
|
91
|
+
o[k = :item_indent] = (v = options.delete(k)).nil?? " " : v
|
92
|
+
o[k = :prompt] = (v = options.delete(k)).nil?? ">>" : v
|
93
|
+
o[k = :title] = options.delete(k)
|
94
|
+
|
95
|
+
o[k = :on_abort] = options.delete(k)
|
96
|
+
o[k = :on_skip] = options.delete(k)
|
97
|
+
|
98
|
+
raise ArgumentError, "Unknown option(s): #{options.inspect}" if not options.empty?
|
99
|
+
|
100
|
+
# Convert `items` into an informative hash.
|
101
|
+
hitems = []
|
102
|
+
items.each_with_index do |item, i|
|
103
|
+
hitems << {
|
104
|
+
:index => (i + 1).to_s, # Convert to string here, which eliminates the need to do String -> Integer after input.
|
105
|
+
:label => item[0],
|
106
|
+
:value => item[1],
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
### Begin dialog. ###
|
111
|
+
|
112
|
+
if not (s = o[:title].to_s).empty?
|
113
|
+
puts colorize([:choice, :title], s, [:reset])
|
114
|
+
puts
|
115
|
+
end
|
116
|
+
|
117
|
+
# Print items.
|
118
|
+
index_nchars = hitems.size.to_s.size
|
119
|
+
hitems.each do |h|
|
120
|
+
puts colorize(*[
|
121
|
+
[
|
122
|
+
o[:item_indent],
|
123
|
+
[:choice, :index], "%*d" % [index_nchars, h[:index]],
|
124
|
+
" ",
|
125
|
+
],
|
126
|
+
(o[:colorize_labels] ? [[:choice, :label], h[:label]] : [h[:label]]),
|
127
|
+
[[:reset]],
|
128
|
+
].flatten(1))
|
129
|
+
end
|
130
|
+
puts
|
131
|
+
|
132
|
+
# Read input.
|
133
|
+
|
134
|
+
# Catch INT for a while.
|
135
|
+
old_sigint = trap("INT") do
|
136
|
+
puts "\nAborted"
|
137
|
+
return o[:on_abort]
|
138
|
+
end
|
139
|
+
|
140
|
+
# WARNING: Return result of `while` is return result of method.
|
141
|
+
while true
|
142
|
+
print colorize([:choice, :prompt], o[:prompt], " ", [:reset])
|
143
|
+
|
144
|
+
input = gets.strip
|
145
|
+
if input.empty?
|
146
|
+
if o[:on_skip]
|
147
|
+
break o[:on_skip]
|
148
|
+
else
|
149
|
+
next
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Something has been input.
|
154
|
+
found = hitems.find {|h| h[:index] == input}
|
155
|
+
break found[:value] if found
|
156
|
+
|
157
|
+
puts colorize([:message, :error], "Invalid input", [:reset])
|
158
|
+
end # while true
|
159
|
+
ensure
|
160
|
+
# NOTE: `old_sigint` is literally declared above, so it always exists here no matter when we gain control.
|
161
|
+
if not old_sigint.nil?
|
162
|
+
trap("INT", &old_sigint)
|
163
|
+
end
|
164
|
+
end # choice
|
165
|
+
|
166
|
+
# Same as <tt>ORI::Colorize.colorize</tt>, but this one produces
|
167
|
+
# plain output if color is turned off in <tt>ORI.conf</tt>.
|
168
|
+
def self.colorize(*args)
|
169
|
+
Colorize.colorize *args.reject {|v| v.is_a? Array and not ::ORI.conf.color}
|
170
|
+
end
|
171
|
+
|
172
|
+
# Colorize a MAM (module-access-method) array.
|
173
|
+
#
|
174
|
+
# colorize_mam(["Kernel", "#", "dup"])
|
175
|
+
def self.colorize_mam(mam)
|
176
|
+
colorize(*[
|
177
|
+
[:mam, :module_name], mam[0],
|
178
|
+
[:mam, :access], mam[1],
|
179
|
+
[:mam, :method_name], mam[2],
|
180
|
+
[:reset],
|
181
|
+
])
|
182
|
+
end
|
183
|
+
|
184
|
+
# Stuff a ready-made "<subject>.ri " command into Readline history if last request had an argument.
|
185
|
+
def self.do_history
|
186
|
+
# `cmd` is actually THIS command being executed.
|
187
|
+
cmd = Readline::HISTORY.to_a.last
|
188
|
+
if prefix = get_ri_arg_prefix(cmd)
|
189
|
+
Readline::HISTORY.pop
|
190
|
+
Readline::HISTORY.push "#{prefix} "
|
191
|
+
Readline::HISTORY.push cmd
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Fetch ListMethods from one or more objects (<tt>:obj => ...</tt>) and optionally filter them.
|
196
|
+
# Options:
|
197
|
+
#
|
198
|
+
# :access => "#" # "#" or "::".
|
199
|
+
# :all => true|false # Show all methods. Default is `false`.
|
200
|
+
# :fullre => Regexp # Full record filter.
|
201
|
+
# :objs => Array # Array of objects to fetch methods of. Must be specified.
|
202
|
+
# :own => true|false # Show own methods only.
|
203
|
+
# :re => Regexp # Method name filter.
|
204
|
+
# :visibility => :protected # Symbol or [Symbol, Symbol, ...].
|
205
|
+
def self.get_list_methods(options = {})
|
206
|
+
options = options.dup
|
207
|
+
o = {}
|
208
|
+
|
209
|
+
o[k = :access] = if v = options.delete(k); v.to_s; end
|
210
|
+
o[k = :all] = (v = options.delete(k)).nil?? false : v
|
211
|
+
o[k = :fullre] = options.delete(k)
|
212
|
+
o[k = :objs] = options.delete(k)
|
213
|
+
o[k = :own] = (v = options.delete(k)).nil?? false : v
|
214
|
+
o[k = :re] = options.delete(k)
|
215
|
+
o[k = :visibility] = options.delete(k)
|
216
|
+
raise ArgumentError, "Unknown option(s): #{options.inspect}" if not options.empty?
|
217
|
+
|
218
|
+
k = :access; raise ArgumentError, "options[#{k.inspect}] must be in #{GLM_ALL_ACCESSES.inspect}, #{o[k].inspect} given" if o[k] and not GLM_ALL_ACCESSES.include? o[k]
|
219
|
+
k = :fullre; raise ArgumentError, "options[#{k.inspect}] must be Regexp, #{o[k].class} given" if o[k] and not o[k].is_a? Regexp
|
220
|
+
|
221
|
+
k = :objs
|
222
|
+
raise ArgumentError, "options[#{k.inspect}] must be set" if not o[k]
|
223
|
+
raise ArgumentError, "options[#{k.inspect}] must be Array, #{o[k].class} given" if o[k] and not o[k].is_a? Array
|
224
|
+
|
225
|
+
k = :re; raise ArgumentError, "options[#{k.inspect}] must be Regexp, #{o[k].class} given" if o[k] and not o[k].is_a? Regexp
|
226
|
+
|
227
|
+
if o[k = :visibility]
|
228
|
+
o[k] = [o[k]].flatten
|
229
|
+
o[k].each do |v|
|
230
|
+
raise ArgumentError, "options[#{k.inspect}] must be in #{GLM_ALL_VISIBILITIES.inspect}, #{v.inspect} given" if not GLM_ALL_VISIBILITIES.include? v
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# NOTE: `:all` and `:own` are NOT mutually exclusive. They are mutually confusive. :)
|
235
|
+
|
236
|
+
# Build per-obj lists.
|
237
|
+
per_obj = o[:objs].uniq.map do |obj|
|
238
|
+
ar = []
|
239
|
+
|
240
|
+
Tools.get_methods(obj).each do |inspector, methods|
|
241
|
+
ar += methods.map {|method_name| ListMethod.new(:obj => obj, :inspector => inspector, :method_name => method_name)}
|
242
|
+
end
|
243
|
+
|
244
|
+
# Filter by access if requested.
|
245
|
+
ar.reject! {|r| r.access != o[:access]} if o[:access]
|
246
|
+
|
247
|
+
# Filter by visibility if requested.
|
248
|
+
ar.reject! {|r| o[:visibility].none? {|vis| r.visibility == vis}} if o[:visibility]
|
249
|
+
|
250
|
+
# Leave only own methods if requested.
|
251
|
+
ar.reject! {|r| not r.own?} if o[:own]
|
252
|
+
|
253
|
+
# Apply RE if requested.
|
254
|
+
ar.reject! {|r| not r.match(o[:re])} if o[:re]
|
255
|
+
|
256
|
+
# Apply full RE if requested
|
257
|
+
ar.reject! {|r| not r.fullmatch(o[:fullre])} if o[:fullre]
|
258
|
+
|
259
|
+
# Apply smart filters if requested.
|
260
|
+
ar = apply_smart_filters(obj, ar) if not o[:all]
|
261
|
+
|
262
|
+
# Important, return `ar` from block.
|
263
|
+
ar
|
264
|
+
end # o[:objs].each
|
265
|
+
|
266
|
+
out = per_obj.flatten(1)
|
267
|
+
##p "out.size", out.size
|
268
|
+
|
269
|
+
# Chop off duplicates.
|
270
|
+
out.uniq!
|
271
|
+
|
272
|
+
# DO NOT sort by default. If required for visual listing, that's caller's responsibility!
|
273
|
+
#out.sort!
|
274
|
+
|
275
|
+
out
|
276
|
+
end
|
277
|
+
|
278
|
+
# Used in <tt>do_history</tt>.
|
279
|
+
# Get prefix of the last "subject.ri args" command.
|
280
|
+
# Return everything before " args" or <tt>nil</tt> if command didn't have arguments.
|
281
|
+
def self.get_ri_arg_prefix(cmd)
|
282
|
+
if (mat = cmd.match /\A(\s*.+?\.ri)\s+\S/)
|
283
|
+
mat[1]
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# Return local library instance.
|
288
|
+
def self.library
|
289
|
+
@lib ||= Library.new
|
290
|
+
|
291
|
+
# Update sensitive attrs on every call.
|
292
|
+
@lib.frontend = ::ORI.conf.frontend
|
293
|
+
@lib.shell_escape = ::ORI.conf.shell_escape
|
294
|
+
|
295
|
+
@lib
|
296
|
+
end
|
297
|
+
|
298
|
+
# Show content in a configured pager.
|
299
|
+
#
|
300
|
+
# pager do |f|
|
301
|
+
# f.puts "Hello, world!"
|
302
|
+
# end
|
303
|
+
def self.pager(&block)
|
304
|
+
IO.popen(::ORI.conf.pager, "w", &block)
|
305
|
+
end
|
306
|
+
|
307
|
+
# Do main job.
|
308
|
+
def self.ri(obj, *args)
|
309
|
+
# Most of the time return nil, for list modes return number of items. Could be useful. Don't return `false` on error, that's confusing.
|
310
|
+
out = nil
|
311
|
+
|
312
|
+
begin
|
313
|
+
# Build request.
|
314
|
+
req = ::ORI::Request.parse(*args)
|
315
|
+
raise UserError, "Bad request: #{req.message}" if req.error?
|
316
|
+
##IrbHacks.break req
|
317
|
+
|
318
|
+
# List request.
|
319
|
+
#
|
320
|
+
# Klass.ri //
|
321
|
+
if req.list?
|
322
|
+
begin
|
323
|
+
req.glm_options[:objs].unshift(obj)
|
324
|
+
list_methods = get_list_methods(req.glm_options).sort
|
325
|
+
rescue ArgumentError => e
|
326
|
+
raise UserError, "Bad request: #{e.message}"
|
327
|
+
end
|
328
|
+
raise UserError, "No methods found" if list_methods.size < 1
|
329
|
+
|
330
|
+
# Display.
|
331
|
+
pager do |f|
|
332
|
+
f.puts list_methods.map {|r| r.format(:color => ::ORI.conf.color)}
|
333
|
+
end
|
334
|
+
|
335
|
+
out = list_methods.size
|
336
|
+
raise Break
|
337
|
+
end # if req.list?
|
338
|
+
|
339
|
+
# Class or method request. Particular ri article should be displayed.
|
340
|
+
#
|
341
|
+
# Klass.ri
|
342
|
+
# Klass.ri :meth
|
343
|
+
mam_topics = if req.self?
|
344
|
+
[[Tools.get_module_name(obj.is_a?(Module) ? obj : obj.class)]]
|
345
|
+
elsif req.method?
|
346
|
+
begin
|
347
|
+
req.glm_options[:objs].unshift(obj)
|
348
|
+
list_methods = get_list_methods(req.glm_options)
|
349
|
+
rescue ArgumentError => e
|
350
|
+
raise UserError, "Bad request: #{e.message}"
|
351
|
+
end
|
352
|
+
raise UserError, "No methods found" if list_methods.size < 1
|
353
|
+
|
354
|
+
# Collect topics.
|
355
|
+
# NOTE: `uniq` is important. Take `Module#public` as an example.
|
356
|
+
list_methods.map {|r| r.ri_topics}.flatten(1).uniq
|
357
|
+
else
|
358
|
+
raise "Unrecognized request kind #{req.kind.inspect}, SE"
|
359
|
+
end # mam_topics =
|
360
|
+
|
361
|
+
# Lookup topics. Display progress -- 1 character per lookup.
|
362
|
+
print colorize([:message, :action], "Looking up topics [", [:reset], mam_topics.map {|ar| colorize_mam(ar)}.join(", "), [:message, :action], "] ", [:reset])
|
363
|
+
|
364
|
+
found = []
|
365
|
+
mam_topics.each do |mam|
|
366
|
+
topic = mam.join
|
367
|
+
content = library.lookup(topic)
|
368
|
+
if content
|
369
|
+
print "o"
|
370
|
+
found << {
|
371
|
+
:topic => colorize_mam(mam),
|
372
|
+
:content => content,
|
373
|
+
}
|
374
|
+
else
|
375
|
+
print "."
|
376
|
+
end
|
377
|
+
end
|
378
|
+
puts
|
379
|
+
|
380
|
+
raise UserError, "No articles found" if found.size < 1
|
381
|
+
|
382
|
+
# Decide which article to show.
|
383
|
+
content = if found.size == 1
|
384
|
+
found.first[:content]
|
385
|
+
else
|
386
|
+
items = found.map {|h| ["#{h[:topic]} (#{h[:content].size}b)", h[:content]]}
|
387
|
+
choice(items, {
|
388
|
+
:colorize_labels => false,
|
389
|
+
:title => "More than 1 article found",
|
390
|
+
:on_skip => items.first[1],
|
391
|
+
})
|
392
|
+
end
|
393
|
+
|
394
|
+
# Handle abort.
|
395
|
+
raise Break if not content
|
396
|
+
|
397
|
+
# Display.
|
398
|
+
pager do |f|
|
399
|
+
f.puts content
|
400
|
+
end
|
401
|
+
rescue UserError => e
|
402
|
+
puts colorize([:message, :error], e.message, [:reset])
|
403
|
+
|
404
|
+
out = nil
|
405
|
+
rescue Break
|
406
|
+
end
|
407
|
+
|
408
|
+
out
|
409
|
+
end
|
410
|
+
end # Internals
|
411
|
+
end # ORI
|