ori 0.1.0

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