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.
@@ -0,0 +1,59 @@
1
+ require "set"
2
+
3
+ module ORI
4
+ # ri lookup library.
5
+ class Library #:nodoc:
6
+ # Mask of ri command to fetch content. Example:
7
+ #
8
+ # ri -T -f ansi %s
9
+ attr_accessor :frontend
10
+
11
+ # Shell escape mode. <tt>:unix</tt> or <tt>:windows</tt>.
12
+ attr_accessor :shell_escape
13
+
14
+ def initialize(attrs = {})
15
+ @cache = {}
16
+ attrs.each {|k, v| send("#{k}=", v)}
17
+ end
18
+
19
+ # Lookup an article.
20
+ #
21
+ # lookup("Kernel#puts") # => content or nil.
22
+ def lookup(topic)
23
+ if @cache.has_key? topic
24
+ @cache[topic]
25
+ else
26
+ require_frontend
27
+
28
+ etopic = case @shell_escape
29
+ when :unix
30
+ Tools.shell_escape(topic)
31
+ when :windows
32
+ Tools.win_shell_escape(topic)
33
+ else
34
+ topic
35
+ end
36
+
37
+ cmd = @frontend % etopic
38
+ ##p "cmd", cmd
39
+ content = `#{cmd} 2>&1`
40
+ ##p "content", content
41
+
42
+ # NOTES:
43
+ # * Windows' ri always returns 0 even if article is not found. Work around it with a hack.
44
+ # * Unix's ri sometimes returns 0 when it offers suggestions. Try `ri Object#is_ax?`.
45
+ @cache[topic] = if $?.exitstatus != 0 or content.lines.count < 4
46
+ nil
47
+ else
48
+ content
49
+ end
50
+ end
51
+ end
52
+
53
+ protected
54
+
55
+ def require_frontend
56
+ raise "`frontend` is not set" if not @frontend
57
+ end
58
+ end # Library
59
+ end # ORI
@@ -0,0 +1,247 @@
1
+ module ORI
2
+ # Our method representation suitable for listing.
3
+ class ListMethod #:nodoc:
4
+ OWN_MARKER = ["~", " "]
5
+
6
+ # Object. Can be anything, including <em>nil</em>.
7
+ attr_reader :obj
8
+
9
+ attr_reader :method_name
10
+ attr_reader :inspector
11
+
12
+ def initialize(attrs = {})
13
+ attrs.each {|k, v| send("#{k}=", v)}
14
+ clear_cache
15
+ end
16
+
17
+ #--------------------------------------- Accessors and pseudo accessors
18
+
19
+ # Return method access substring: "::" or "#".
20
+ def access
21
+ # NOTE: It is *WRONG* to rely on Ruby's `inspect` to handle things because
22
+ # it doesn't work for cases when singleton methods are included from modules.
23
+ @cache[:access] ||= (module? and not inspector.match /instance/) ? "::" : "#"
24
+ end
25
+
26
+ def inspector=(s)
27
+ @inspector = s.to_s
28
+ clear_cache
29
+ end
30
+
31
+ def instance?
32
+ access == "#"
33
+ end
34
+
35
+ def method_name=(s)
36
+ @method_name = s.to_s
37
+ clear_cache
38
+ end
39
+
40
+ # Fetch method object.
41
+ def method_object
42
+ require_valid
43
+
44
+ @cache[:method_object] ||= if @inspector.match /instance/
45
+ @obj._ori_instance_method(@method_name)
46
+ else
47
+ @obj._ori_method(@method_name)
48
+ end
49
+ end
50
+
51
+ def module?
52
+ @cache[:is_module] ||= begin
53
+ require_obj
54
+ @obj.is_a? Module
55
+ end
56
+ end
57
+
58
+ def obj=(obj)
59
+ @obj = obj
60
+ @obj_present = true
61
+ clear_cache
62
+ end
63
+
64
+ def obj_module
65
+ @cache[:obj_module] ||= obj.is_a?(Module) ? obj : obj.class
66
+ end
67
+
68
+ def obj_module_name
69
+ @cache[:obj_module_name] ||= Tools.get_module_name(obj_module)
70
+ end
71
+
72
+ def owner
73
+ @cache[:owner] ||= method_object.owner
74
+ end
75
+
76
+ # Get, if possible, <tt>obj</tt> singleton class.
77
+ # Some objects, e.g. <tt>Fixnum</tt> instances, don't have a singleton class.
78
+ def obj_singleton_class
79
+ @cache[:obj_singleton] ||= begin
80
+ class << obj #:nodoc:
81
+ self
82
+ end
83
+ rescue
84
+ nil
85
+ end
86
+ end
87
+
88
+ # Return <tt>true</tt> if method is natively owned by <tt>obj</tt> class.
89
+ def own?
90
+ @cache[:is_own] ||= begin
91
+ require_valid
92
+ owner == obj_module || owner == obj_singleton_class
93
+ end
94
+ end
95
+
96
+ def owner_name
97
+ @cache[:owner_name] ||= Tools.get_module_name(owner)
98
+ end
99
+
100
+ def private?
101
+ visibility == :private
102
+ end
103
+
104
+ def protected?
105
+ visibility == :protected
106
+ end
107
+
108
+ def public?
109
+ visibility == :public
110
+ end
111
+
112
+ def singleton?
113
+ access == "::"
114
+ end
115
+
116
+ # Return visibility: <tt>:public</tt>, <tt>:protected</tt>, <tt>:private</tt>.
117
+ def visibility
118
+ @cache[:visibility] ||= begin
119
+ require_valid
120
+
121
+ if @inspector.match /private/
122
+ :private
123
+ elsif @inspector.match /protected/
124
+ :protected
125
+ else
126
+ :public
127
+ end
128
+ end
129
+ end
130
+
131
+ #---------------------------------------
132
+
133
+ # Format self into a string.
134
+ # Options:
135
+ #
136
+ # :color => true|false
137
+ def format(options = {})
138
+ options = options.dup
139
+ o = {}
140
+ o[k = :color] = (v = options.delete(k)).nil?? true : v
141
+ raise ArgumentError, "Unknown option(s): #{options.inspect}" if not options.empty?
142
+
143
+ require_valid
144
+
145
+ Colorize.colorize *[
146
+ (own?? [[:list_method, :own_marker], OWN_MARKER[0]] : [[:list_method, :not_own_marker], OWN_MARKER[1]]),
147
+ [[:list_method, :obj_module_name], obj_module_name],
148
+ ([[:list_method, :owner_name], "(#{owner_name})"] if not own?),
149
+ [[:list_method, :access], access],
150
+ [[:list_method, :name], method_name],
151
+ ([[:list_method, :visibility], " [#{visibility}]"] if not public?),
152
+ [[:reset]],
153
+ ].compact.flatten(1).reject {|v| v.is_a? Array and not o[:color]}
154
+ end
155
+
156
+ # Match entire formatted record against RE.
157
+ def fullmatch(re)
158
+ format(:color => false).match(re)
159
+ end
160
+
161
+ # Match method name against RE.
162
+ def match(re)
163
+ @method_name.match(re)
164
+ end
165
+
166
+ # Quick format. No options, no hashes, no checks.
167
+ def qformat
168
+ #"#{owner_name}#{access}#{@method_name} [#{visibility}]" # Before multi-obj support.
169
+ "#{obj_module_name}#{access}#{@method_name} [#{visibility}]"
170
+ end
171
+
172
+ def ri_topics
173
+ @cache[:ri_topics] ||= begin
174
+ require_valid
175
+
176
+ # Build "hierarchy methods". Single record is:
177
+ #
178
+ # ["Kernel", "#", "dup"]
179
+ hmethods = []
180
+
181
+ # Always stuff self in front of the line regardless of if we have method or not.
182
+ hmethods << [obj_module_name, access, method_name]
183
+
184
+ ancestors = []
185
+ ancestors += obj_module.ancestors
186
+ ancestors += obj_singleton_class.ancestors if obj_singleton_class # E.g. when module extends class.
187
+
188
+ ancestors.each do |mod|
189
+ mav = Tools.get_methods(mod, :inspector_arg => false, :to_mav => true)
190
+ ##p "mav", mav
191
+ found = mav.select {|method_name,| method_name == self.method_name}
192
+ ##p "found", found
193
+ found.each do |method_name, access|
194
+ hmethods << [Tools.get_module_name(mod), access, method_name]
195
+ end
196
+ end
197
+
198
+ # Misdoc hack -- stuff Object#meth lookup if Kernel#meth is present. For methods like Kernel#is_a?.
199
+ if (found = hmethods.find {|mod, access| [mod, access] == ["Kernel", "#"]}) and not hmethods.find {|mod,| mod == "Object"}
200
+ hmethods << ["Object", "#", found.last]
201
+ end
202
+
203
+ hmethods.uniq
204
+ end
205
+ end
206
+
207
+ def valid?
208
+ [
209
+ @obj_present,
210
+ @method_name,
211
+ @inspector,
212
+ ].all?
213
+ end
214
+
215
+ #---------------------------------------
216
+
217
+ # Support <tt>Enumerable#sort</tt>.
218
+ def <=>(other)
219
+ [@method_name, access, obj_module_name] <=> [other.method_name, other.access, obj_module_name]
220
+ end
221
+
222
+ # Support <tt>Array#uniq</tt>.
223
+ def hash
224
+ @cache[:hash] ||= qformat.hash
225
+ end
226
+
227
+ # Support <tt>Array#uniq</tt>.
228
+ def eql?(other)
229
+ hash == other.hash
230
+ end
231
+
232
+ #---------------------------------------
233
+ private
234
+
235
+ def clear_cache
236
+ @cache = {}
237
+ end
238
+
239
+ def require_obj
240
+ raise "`obj` is not set" if not @obj_present
241
+ end
242
+
243
+ def require_valid
244
+ raise "Object is not valid" if not valid?
245
+ end
246
+ end # ListMethod
247
+ end
@@ -0,0 +1,118 @@
1
+ module ORI
2
+ # <tt>something.ri [something]</tt> request logic.
3
+ #
4
+ # NOTE: This class DOES NOT validate particular options to be passed to <tt>get_list_methods</tt>.
5
+ class Request #:nodoc:
6
+ class ParseError < Exception #:nodoc:
7
+ end
8
+
9
+ # Options for <tt>Internals::get_list_methods</tt>.
10
+ attr_accessor :glm_options
11
+
12
+ # <tt>:self</tt>, <tt>:list</tt>, <tt>:method</tt> or <tt>:error</tt>.
13
+ attr_accessor :kind
14
+
15
+ # Message. E.g. for <tt>:error</tt> kind this is the message for the user.
16
+ attr_accessor :message
17
+
18
+ def initialize(attrs = {})
19
+ @glm_options = {}
20
+ attrs.each {|k, v| send("#{k}=", v)}
21
+ end
22
+
23
+ def error?
24
+ @kind == :error
25
+ end
26
+
27
+ def list?
28
+ @kind == :list
29
+ end
30
+
31
+ def method?
32
+ @kind == :method
33
+ end
34
+
35
+ def self?
36
+ @kind == :self
37
+ end
38
+
39
+ #---------------------------------------
40
+
41
+ # Parse arguments into a new <tt>Request</tt> object.
42
+ #
43
+ # parse()
44
+ # parse(//)
45
+ # parse(//, :all)
46
+ # parse(//, :all => true, :access => "#")
47
+ # parse(:puts)
48
+ # parse("#puts")
49
+ # parse("::puts")
50
+ def self.parse(*args)
51
+ r = new(:glm_options => {:objs => []})
52
+
53
+ begin
54
+ if args.size < 1
55
+ # Fixnum.ri
56
+ # 5.ri
57
+ r.kind = :self
58
+ else
59
+ # At least 1 argument is present.
60
+ arg1 = args.shift
61
+
62
+ case arg1
63
+ when Symbol, String
64
+ # ri :meth
65
+ # ri "#meth"
66
+ # ri "::meth"
67
+ r.kind = :method
68
+ if args.size > 0
69
+ raise ParseError, "Unexpected arguments after #{arg1.inspect}"
70
+ end
71
+
72
+ # This is important -- look through all available methods.
73
+ r.glm_options[:all] = true
74
+
75
+ method_name = if arg1.to_s.match /\A(::|#)(.+)\z/
76
+ r.glm_options[:access] = $1
77
+ $2
78
+ else
79
+ arg1.to_s
80
+ end
81
+
82
+ r.glm_options[:re] = /\A#{Regexp.escape(method_name)}\z/
83
+
84
+ when Regexp
85
+ # ri //
86
+ # ri //, :all
87
+ # ri /kk/, :option => value etc.
88
+ r.kind = :list
89
+ r.glm_options[:re] = arg1
90
+ args.each do |arg|
91
+ if arg.is_a? Hash
92
+ if arg.has_key?(k = :join)
93
+ r.glm_options[:objs] += [arg.delete(k)].flatten(1)
94
+ end
95
+
96
+ r.glm_options.merge! arg
97
+ elsif [String, Symbol].include? arg.class
98
+ r.glm_options.merge! arg.to_sym => true
99
+ else
100
+ raise ParseError, "Unsupported argument #{arg.inspect}"
101
+ end
102
+ end
103
+
104
+ # Don't bother making `objs` unique, we're just the request parser.
105
+
106
+ else
107
+ raise ParseError, "Unsupported argument #{arg1.inspect}"
108
+ end # case arg1
109
+ end # if args.size < 1
110
+ rescue ParseError => e
111
+ r.kind = :error
112
+ r.message = e.message
113
+ end
114
+
115
+ r
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,142 @@
1
+ module ORI
2
+ # Generic tools.
3
+ module Tools #:nodoc:
4
+ ANSI_ATTRS = {
5
+ :reset => 0,
6
+ :bold => 1,
7
+ :underscore => 4,
8
+ :underline => 4,
9
+ :blink => 5,
10
+ :reverse => 7,
11
+ :concealed => 8,
12
+ :black => 30,
13
+ :red => 31,
14
+ :green => 32,
15
+ :yellow => 33,
16
+ :blue => 34,
17
+ :magenta => 35,
18
+ :cyan => 36,
19
+ :white => 37,
20
+ :on_black => 40,
21
+ :on_red => 41,
22
+ :on_green => 42,
23
+ :on_yellow => 43,
24
+ :on_blue => 44,
25
+ :on_magenta => 45,
26
+ :on_cyan => 46,
27
+ :on_white => 47,
28
+ }
29
+
30
+ # Default inspectors for <tt>get_methods</tt>.
31
+ GET_METHODS_INSPECTORS = [
32
+ :private_instance_methods,
33
+ :protected_instance_methods,
34
+ :public_instance_methods,
35
+ :private_methods,
36
+ :protected_methods,
37
+ :public_methods,
38
+ ]
39
+
40
+ # Build an ANSI sequence.
41
+ #
42
+ # ansi() # => ""
43
+ # ansi(:red) # => "\e[31m"
44
+ # ansi(:red, :bold) # => "\e[31;1m"
45
+ # puts "Hello, #{ansi(:bold)}user#{ansi(:reset)}"
46
+ def self.ansi(*attrs)
47
+ codes = attrs.map {|attr| ANSI_ATTRS[attr.to_sym] or raise ArgumentError, "Unknown attribute #{attr.inspect}"}
48
+ codes.empty?? "" : "\e[#{codes.join(';')}m"
49
+ end
50
+
51
+ # Inspect an object with various inspectors.
52
+ # Options:
53
+ #
54
+ # :inspectors => [] # Array of inspectors, e.g. [:public_instance_methods].
55
+ # :inspector_arg => T|F # Arg to pass to inspector. Default is <tt>true</tt>
56
+ # :to_mav => T|F # Post-transform list into [method_name, access, visibility] ("MAV"). Default is <tt>false</tt>.
57
+ #
58
+ # Examples:
59
+ #
60
+ # get_methods(obj)
61
+ # # => [[inspector, [methods]], [inspector, [methods]], ...]
62
+ # get_methods(obj, :to_mav => true)
63
+ # # => [[method_name, access, visibility], [method_name, access, visibility], ...]
64
+ def self.get_methods(obj, options = {})
65
+ options = options.dup
66
+ o = {}
67
+ o[k = :inspectors] = (v = options.delete(k)).nil?? GET_METHODS_INSPECTORS : v
68
+ o[k = :inspector_arg] = (v = options.delete(k)).nil?? true : v
69
+ o[k = :to_mav] = (v = options.delete(k)).nil?? false : v
70
+ raise ArgumentError, "Unknown option(s): #{options.inspect}" if not options.empty?
71
+
72
+ out = []
73
+
74
+ o[:inspectors].each do |inspector|
75
+ next if not obj.respond_to? inspector
76
+ out << [inspector.to_s, obj.send(inspector, o[:inspector_arg]).sort.map(&:to_s)]
77
+ end
78
+
79
+ if o[:to_mav]
80
+ mav = []
81
+
82
+ is_module = obj.is_a? Module
83
+
84
+ out.each do |inspector, methods|
85
+ ##puts "-- inspector-#{inspector.inspect}"
86
+ access = (is_module and not inspector.match /instance/) ? "::" : "#"
87
+
88
+ visibility = if inspector.match /private/
89
+ :private
90
+ elsif inspector.match /protected/
91
+ :protected
92
+ else
93
+ :public
94
+ end
95
+
96
+ methods.each do |method_name|
97
+ mav << [method_name, access, visibility]
98
+ end
99
+ end
100
+
101
+ out = mav.uniq # NOTE: Dupes are possible, e.g. when custom inspectors are given.
102
+ end
103
+
104
+ out
105
+ end
106
+
107
+ # Return name of a module, even a "nameless" one.
108
+ def self.get_module_name(mod)
109
+ if mod.name.to_s.empty?
110
+ if mat = mod.inspect.match(/#<Class:.*?\b(.+?)(?:>|:[0#])/)
111
+ mat[1]
112
+ end
113
+ else
114
+ mod.name
115
+ end
116
+ end
117
+
118
+ # Escape string for use in Unix shell command.
119
+ # Credits http://stackoverflow.com/questions/1306680/shellwords-shellescape-implementation-for-ruby-1-8.
120
+ def self.shell_escape(s)
121
+ # An empty argument will be skipped, so return empty quotes.
122
+ return "''" if s.empty?
123
+
124
+ s = s.dup
125
+
126
+ # Process as a single byte sequence because not all shell
127
+ # implementations are multibyte aware.
128
+ s.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
129
+
130
+ # A LF cannot be escaped with a backslash because a backslash + LF
131
+ # combo is regarded as line continuation and simply ignored.
132
+ s.gsub!(/\n/, "'\n'")
133
+
134
+ s
135
+ end
136
+
137
+ # Escape string for use in Windows command. Word "shell" is used for similarity.
138
+ def self.win_shell_escape(s)
139
+ s
140
+ end
141
+ end # Tools
142
+ end # ORI