BMorearty-looksee 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +9 -0
- data/History.txt +18 -0
- data/Manifest.txt +21 -0
- data/README.rdoc +129 -0
- data/Rakefile +37 -0
- data/ext/looksee/extconf.rb +6 -0
- data/ext/looksee/looksee.c +144 -0
- data/ext/looksee/node-1.9.h +35 -0
- data/lib/looksee/shortcuts.rb +63 -0
- data/lib/looksee/version.rb +3 -0
- data/lib/looksee/wirble_compatibility.rb +86 -0
- data/lib/looksee.rb +403 -0
- data/looksee.gemspec +44 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/looksee_spec.rb +425 -0
- data/spec/spec_helper.rb +139 -0
- data/spec/wirble_compatibility_spec.rb +119 -0
- data/tasks/extconf/looksee.rake +43 -0
- data/tasks/extconf.rake +13 -0
- metadata +116 -0
data/lib/looksee.rb
ADDED
@@ -0,0 +1,403 @@
|
|
1
|
+
require "rbconfig"
|
2
|
+
require File.dirname(__FILE__) + "/../ext/looksee/looksee.#{Config::CONFIG['DLEXT']}"
|
3
|
+
require "looksee/version"
|
4
|
+
|
5
|
+
#
|
6
|
+
# Looksee lets you inspect the method lookup path of an object. There
|
7
|
+
# are two ways to use it:
|
8
|
+
#
|
9
|
+
# 1. Keep all methods contained in the Looksee namespace:
|
10
|
+
#
|
11
|
+
# require 'looksee'
|
12
|
+
#
|
13
|
+
# 2. Let it all hang out:
|
14
|
+
#
|
15
|
+
# require 'looksee/shortcuts'
|
16
|
+
#
|
17
|
+
# The latter adds the following shortcuts to the built-in classes:
|
18
|
+
#
|
19
|
+
# Object#lookup_path
|
20
|
+
# Object#dump_lookup_path
|
21
|
+
# Object#lp
|
22
|
+
# Object#lpi
|
23
|
+
# Object#lc
|
24
|
+
#
|
25
|
+
# See their docs.
|
26
|
+
#
|
27
|
+
# == Usage
|
28
|
+
#
|
29
|
+
# In irb:
|
30
|
+
#
|
31
|
+
# require 'looksee/shortcuts'
|
32
|
+
# lp some_object
|
33
|
+
#
|
34
|
+
# +lp+ returns a LookupPath object, which has +inspect+ defined to
|
35
|
+
# print things out pretty. By default, it shows public, protected,
|
36
|
+
# undefined, and overridden methods. They're all colored, which makes
|
37
|
+
# showing overridden methods not such a strange idea.
|
38
|
+
#
|
39
|
+
# Some examples of the other shortcuts:
|
40
|
+
#
|
41
|
+
# lpi Array
|
42
|
+
# some_object.lookup_path
|
43
|
+
# foo.bar.baz.dump_lookup_path.and.more
|
44
|
+
#
|
45
|
+
# If you're being namespace-clean, you'll need to do:
|
46
|
+
#
|
47
|
+
# require 'looksee'
|
48
|
+
# Looksee.lookup_path(thing) # like "lp thing"
|
49
|
+
#
|
50
|
+
# If you want to remember what each color means:
|
51
|
+
#
|
52
|
+
# lc # shortcut mapping for Looksee.colors
|
53
|
+
#
|
54
|
+
# == Configuration
|
55
|
+
#
|
56
|
+
# Set these:
|
57
|
+
#
|
58
|
+
# Looksee.default_lookup_path_options
|
59
|
+
# Looksee.default_width
|
60
|
+
# Looksee.styles
|
61
|
+
#
|
62
|
+
# See their docs.
|
63
|
+
#
|
64
|
+
module Looksee
|
65
|
+
class << self
|
66
|
+
#
|
67
|
+
# Return a collection of methods that +object+ responds to,
|
68
|
+
# according to the options given. The following options are
|
69
|
+
# recognized:
|
70
|
+
#
|
71
|
+
# * +:public+ - include public methods
|
72
|
+
# * +:protected+ - include protected methods
|
73
|
+
# * +:private+ - include private methods
|
74
|
+
# * +:undefined+ - include undefined methods (see Module#undef_method)
|
75
|
+
# * +:overridden+ - include methods overridden by subclasses
|
76
|
+
#
|
77
|
+
# The default (if options is nil or omitted) is given by
|
78
|
+
# #default_lookup_path_options.
|
79
|
+
#
|
80
|
+
def lookup_path(object, *options)
|
81
|
+
normalized_options = Looksee.default_lookup_path_options.dup
|
82
|
+
hash_options = options.last.is_a?(Hash) ? options.pop : {}
|
83
|
+
options.each do |option|
|
84
|
+
normalized_options[option] = true
|
85
|
+
end
|
86
|
+
normalized_options.update(hash_options)
|
87
|
+
LookupPath.for(object, normalized_options)
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# The default options passed to lookup_path.
|
92
|
+
#
|
93
|
+
# Default: <tt>{:public => true, :protected => true, :undefined =>
|
94
|
+
# true, :overridden => true}</tt>
|
95
|
+
#
|
96
|
+
attr_accessor :default_lookup_path_options
|
97
|
+
|
98
|
+
#
|
99
|
+
# The width to use for displaying output, when not available in
|
100
|
+
# the COLUMNS environment variable.
|
101
|
+
#
|
102
|
+
# Default: 80
|
103
|
+
#
|
104
|
+
attr_accessor :default_width
|
105
|
+
|
106
|
+
#
|
107
|
+
# The default styles to use for the +inspect+ strings.
|
108
|
+
#
|
109
|
+
# This is a hash with keys:
|
110
|
+
#
|
111
|
+
# * :module
|
112
|
+
# * :public
|
113
|
+
# * :protected
|
114
|
+
# * :private
|
115
|
+
# * :undefined
|
116
|
+
# * :overridden
|
117
|
+
#
|
118
|
+
# The values are format strings. They should all contain a single
|
119
|
+
# "%s", which is where the name is inserted.
|
120
|
+
#
|
121
|
+
# Default:
|
122
|
+
#
|
123
|
+
# {
|
124
|
+
# :module => "\e[1;37m%s\e[0m", # white
|
125
|
+
# :public => "\e[1;32m%s\e[0m", # green
|
126
|
+
# :protected => "\e[1;33m%s\e[0m", # yellow
|
127
|
+
# :private => "\e[1;31m%s\e[0m", # red
|
128
|
+
# :undefined => "\e[1;34m%s\e[0m", # blue
|
129
|
+
# :overridden => "\e[1;30m%s\e[0m", # black
|
130
|
+
# }
|
131
|
+
#
|
132
|
+
attr_accessor :styles
|
133
|
+
|
134
|
+
#
|
135
|
+
# Return the chain of classes and modules which comprise the
|
136
|
+
# object's method lookup path.
|
137
|
+
#
|
138
|
+
def lookup_modules(object)
|
139
|
+
modules = []
|
140
|
+
klass = Looksee.internal_class(object)
|
141
|
+
while klass
|
142
|
+
modules << Looksee.internal_class_to_module(klass)
|
143
|
+
klass = Looksee.internal_superclass(klass)
|
144
|
+
end
|
145
|
+
modules
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Return the color mappings.
|
150
|
+
#
|
151
|
+
def colors
|
152
|
+
Colors.new
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
self.default_lookup_path_options = {:public => true, :protected => true, :undefined => true, :overridden => true}
|
157
|
+
self.default_width = 80
|
158
|
+
self.styles = {
|
159
|
+
:module => "\e[1;37m%s\e[0m", # white
|
160
|
+
:public => "\e[1;32m%s\e[0m", # green
|
161
|
+
:protected => "\e[1;33m%s\e[0m", # yellow
|
162
|
+
:private => "\e[1;31m%s\e[0m", # red
|
163
|
+
:undefined => "\e[1;34m%s\e[0m", # blue
|
164
|
+
:overridden => "\e[1;30m%s\e[0m", # black
|
165
|
+
}
|
166
|
+
|
167
|
+
class LookupPath
|
168
|
+
attr_reader :entries
|
169
|
+
|
170
|
+
def initialize(entries)
|
171
|
+
@entries = entries
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# Create a LookupPath for the given object.
|
176
|
+
#
|
177
|
+
# Options may be given to restrict which visibilities are
|
178
|
+
# included.
|
179
|
+
#
|
180
|
+
# :public
|
181
|
+
# :protected
|
182
|
+
# :private
|
183
|
+
# :undefined
|
184
|
+
# :overridden
|
185
|
+
#
|
186
|
+
def self.for(object, options={})
|
187
|
+
entries = entries_for(object, options)
|
188
|
+
new(entries)
|
189
|
+
end
|
190
|
+
|
191
|
+
#
|
192
|
+
# Return a new LookupPath which only contains names matching the
|
193
|
+
# given pattern.
|
194
|
+
#
|
195
|
+
def grep(pattern)
|
196
|
+
entries = self.entries.map do |entry|
|
197
|
+
entry.grep(pattern)
|
198
|
+
end
|
199
|
+
self.class.new(entries)
|
200
|
+
end
|
201
|
+
|
202
|
+
def inspect(options={})
|
203
|
+
options = normalize_inspect_options(options)
|
204
|
+
entries.map{|e| e.inspect(options)}.join("\n")
|
205
|
+
end
|
206
|
+
|
207
|
+
private # -------------------------------------------------------
|
208
|
+
|
209
|
+
def self.entries_for(object, options)
|
210
|
+
seen = {}
|
211
|
+
Looksee.lookup_modules(object).map do |mod|
|
212
|
+
entry = Entry.for(mod, seen, options)
|
213
|
+
entry.methods.each{|m| seen[m] = true}
|
214
|
+
entry
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def normalize_inspect_options(options)
|
219
|
+
options[:width] ||= ENV['COLUMNS'].to_i.nonzero? || Looksee.default_width
|
220
|
+
options
|
221
|
+
end
|
222
|
+
|
223
|
+
#
|
224
|
+
# An entry in the LookupPath.
|
225
|
+
#
|
226
|
+
# Contains a module and its methods, along with visibility
|
227
|
+
# information (public, private, etc.).
|
228
|
+
#
|
229
|
+
class Entry
|
230
|
+
def initialize(mod, methods=[], visibilities={})
|
231
|
+
@module = mod
|
232
|
+
@methods = methods
|
233
|
+
@visibilities = visibilities
|
234
|
+
end
|
235
|
+
|
236
|
+
def self.for(mod, seen, options)
|
237
|
+
entry = new(mod)
|
238
|
+
entry.initialize_for(seen, options)
|
239
|
+
entry
|
240
|
+
end
|
241
|
+
|
242
|
+
attr_reader :module, :methods
|
243
|
+
|
244
|
+
def initialize_for(seen, options)
|
245
|
+
add_methods(Looksee.internal_public_instance_methods(@module).map{|sym| sym.to_s} , :public , seen) if options[:public ]
|
246
|
+
add_methods(Looksee.internal_protected_instance_methods(@module).map{|sym| sym.to_s}, :protected, seen) if options[:protected]
|
247
|
+
add_methods(Looksee.internal_private_instance_methods(@module).map{|sym| sym.to_s} , :private , seen) if options[:private ]
|
248
|
+
add_methods(Looksee.internal_undefined_instance_methods(@module).map{|sym| sym.to_s}, :undefined, seen) if options[:undefined]
|
249
|
+
@methods.sort!
|
250
|
+
end
|
251
|
+
|
252
|
+
def grep(pattern)
|
253
|
+
methods = []
|
254
|
+
visibilities = {}
|
255
|
+
@methods.each do |name|
|
256
|
+
if name[pattern]
|
257
|
+
methods << name
|
258
|
+
visibilities[name] = @visibilities[name]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
self.class.new(@module, methods, visibilities)
|
262
|
+
end
|
263
|
+
|
264
|
+
#
|
265
|
+
# Return the name of the class or module.
|
266
|
+
#
|
267
|
+
# Singleton classes are displayed in brackets. Singleton class
|
268
|
+
# of singleton classes are displayed in double brackets. But
|
269
|
+
# you'd never need that, would you?
|
270
|
+
#
|
271
|
+
def module_name
|
272
|
+
name = @module.to_s # #name doesn't do singleton classes right
|
273
|
+
nil while name.sub!(/#<Class:(.*)>/, '[\\1]')
|
274
|
+
name
|
275
|
+
end
|
276
|
+
|
277
|
+
#
|
278
|
+
# Yield each method along with its visibility (:public,
|
279
|
+
# :private, :protected, :undefined, or :overridden).
|
280
|
+
#
|
281
|
+
def each
|
282
|
+
@methods.each do |name|
|
283
|
+
yield name, @visibilities[name]
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
include Enumerable
|
288
|
+
|
289
|
+
#
|
290
|
+
# Return a nice, pretty string for inspection.
|
291
|
+
#
|
292
|
+
# Contains the module name, plus the method names laid out in
|
293
|
+
# columns. Pass a :width option to control the output width.
|
294
|
+
#
|
295
|
+
def inspect(options={})
|
296
|
+
string = styled_module_name << "\n" << Columnizer.columnize(styled_methods, options[:width])
|
297
|
+
string.chomp
|
298
|
+
end
|
299
|
+
|
300
|
+
private # -----------------------------------------------------
|
301
|
+
|
302
|
+
def add_methods(methods, visibility, seen)
|
303
|
+
methods.each do |method|
|
304
|
+
@methods << method
|
305
|
+
@visibilities[method] = seen[method] ? :overridden : visibility
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def styled_module_name
|
310
|
+
Looksee.styles[:module] % module_name
|
311
|
+
end
|
312
|
+
|
313
|
+
def styled_methods
|
314
|
+
map do |name, visibility|
|
315
|
+
Looksee.styles[visibility] % name
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
module Columnizer
|
322
|
+
class << self
|
323
|
+
#
|
324
|
+
# Arrange the given strings in columns, restricted to the given
|
325
|
+
# width. Smart enough to ignore content in terminal control
|
326
|
+
# sequences.
|
327
|
+
#
|
328
|
+
def columnize(strings, width)
|
329
|
+
return '' if strings.empty?
|
330
|
+
|
331
|
+
num_columns = 1
|
332
|
+
layout = [strings]
|
333
|
+
loop do
|
334
|
+
break if layout.first.length <= 1
|
335
|
+
next_layout = layout_in_columns(strings, num_columns + 1)
|
336
|
+
break if layout_width(next_layout) > width
|
337
|
+
layout = next_layout
|
338
|
+
num_columns += 1
|
339
|
+
end
|
340
|
+
|
341
|
+
pad_strings(layout)
|
342
|
+
rectangularize_layout(layout)
|
343
|
+
layout.transpose.map do |row|
|
344
|
+
' ' + row.compact.join(' ')
|
345
|
+
end.join("\n") << "\n"
|
346
|
+
end
|
347
|
+
|
348
|
+
private # -----------------------------------------------------
|
349
|
+
|
350
|
+
def layout_in_columns(strings, num_columns)
|
351
|
+
strings_per_column = (strings.length / num_columns.to_f).ceil
|
352
|
+
(0...num_columns).map{|i| strings[i*strings_per_column...(i+1)*strings_per_column] || []}
|
353
|
+
end
|
354
|
+
|
355
|
+
def layout_width(layout)
|
356
|
+
widths = layout_column_widths(layout)
|
357
|
+
widths.inject(0){|sum, w| sum + w} + 2*layout.length
|
358
|
+
end
|
359
|
+
|
360
|
+
def layout_column_widths(layout)
|
361
|
+
layout.map do |column|
|
362
|
+
column.map{|string| display_width(string)}.max || 0
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def display_width(string)
|
367
|
+
# remove terminal control sequences
|
368
|
+
string.gsub(/\e\[.*?m/, '').length
|
369
|
+
end
|
370
|
+
|
371
|
+
def pad_strings(layout)
|
372
|
+
widths = layout_column_widths(layout)
|
373
|
+
layout.each_with_index do |column, i|
|
374
|
+
column_width = widths[i]
|
375
|
+
column.each do |string|
|
376
|
+
padding = column_width - display_width(string)
|
377
|
+
string << ' '*padding
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def rectangularize_layout(layout)
|
383
|
+
return if layout.length == 1
|
384
|
+
height = layout[0].length
|
385
|
+
layout[1..-1].each do |column|
|
386
|
+
column.length == height or
|
387
|
+
column[height - 1] = nil
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
class Colors
|
394
|
+
#
|
395
|
+
# Show the meaning of each color.
|
396
|
+
#
|
397
|
+
def inspect
|
398
|
+
"Looksee colors:\n " + Looksee.styles.map {|style,value| value % "#{style.to_s}"}.join(" ")
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
require 'looksee/wirble_compatibility'
|
data/looksee.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{looksee}
|
5
|
+
s.version = "0.1.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["George Ogata"]
|
9
|
+
s.date = %q{2009-08-20}
|
10
|
+
s.description = %q{Looksee lets you examine the method lookup path of objects in ways not
|
11
|
+
possible in plain ruby.}
|
12
|
+
s.email = ["george.ogata@gmail.com"]
|
13
|
+
s.extensions = ["ext/looksee/extconf.rb"]
|
14
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
|
15
|
+
s.files = [".autotest", "History.txt", "Manifest.txt", "README.rdoc", "Rakefile", "ext/looksee/extconf.rb", "ext/looksee/looksee.c", "ext/looksee/node-1.9.h", "lib/looksee.rb", "lib/looksee/shortcuts.rb", "lib/looksee/version.rb", "lib/looksee/wirble_compatibility.rb", "looksee.gemspec", "script/console", "script/destroy", "script/generate", "spec/looksee_spec.rb", "spec/spec_helper.rb", "spec/wirble_compatibility_spec.rb", "tasks/extconf.rake", "tasks/extconf/looksee.rake"]
|
16
|
+
s.homepage = %q{http://github.com/oggy/looksee}
|
17
|
+
s.rdoc_options = ["--main", "README.rdoc"]
|
18
|
+
s.require_paths = ["lib", "ext/looksee"]
|
19
|
+
s.rubyforge_project = %q{looksee}
|
20
|
+
s.rubygems_version = %q{1.3.4}
|
21
|
+
s.summary = %q{Looksee lets you examine the method lookup path of objects in ways not possible in plain ruby.}
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 3
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_development_dependency(%q<newgem>, [">= 1.5.2"])
|
29
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.7"])
|
30
|
+
s.add_development_dependency(%q<mocha>, [">= 0.9.5"])
|
31
|
+
s.add_development_dependency(%q<hoe>, [">= 2.3.2"])
|
32
|
+
else
|
33
|
+
s.add_dependency(%q<newgem>, [">= 1.5.2"])
|
34
|
+
s.add_dependency(%q<rspec>, [">= 1.2.7"])
|
35
|
+
s.add_dependency(%q<mocha>, [">= 0.9.5"])
|
36
|
+
s.add_dependency(%q<hoe>, [">= 2.3.2"])
|
37
|
+
end
|
38
|
+
else
|
39
|
+
s.add_dependency(%q<newgem>, [">= 1.5.2"])
|
40
|
+
s.add_dependency(%q<rspec>, [">= 1.2.7"])
|
41
|
+
s.add_dependency(%q<mocha>, [">= 0.9.5"])
|
42
|
+
s.add_dependency(%q<hoe>, [">= 2.3.2"])
|
43
|
+
end
|
44
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/looksee.rb'}"
|
9
|
+
puts "Loading looksee gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|