ori 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|