BMorearty-looksee 0.1.1
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/.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)
|