BMorearty-looksee 0.1.1

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