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/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)