oggy-looksee 0.0.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 +3 -0
- data/Manifest.txt +18 -0
- data/README.rdoc +118 -0
- data/Rakefile +37 -0
- data/ext/looksee/extconf.rb +6 -0
- data/ext/looksee/looksee.c +126 -0
- data/ext/looksee/node-1.9.h +35 -0
- data/lib/looksee.rb +332 -0
- data/lib/looksee/shortcuts.rb +54 -0
- data/lib/looksee/version.rb +3 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/looksee_spec.rb +374 -0
- data/spec/spec_helper.rb +80 -0
- data/tasks/extconf.rake +13 -0
- data/tasks/extconf/looksee.rake +43 -0
- metadata +113 -0
data/.autotest
ADDED
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
.autotest
|
2
|
+
History.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
ext/looksee/extconf.rb
|
7
|
+
ext/looksee/looksee.c
|
8
|
+
ext/looksee/node-1.9.h
|
9
|
+
lib/looksee.rb
|
10
|
+
lib/looksee/shortcuts.rb
|
11
|
+
lib/looksee/version.rb
|
12
|
+
script/console
|
13
|
+
script/destroy
|
14
|
+
script/generate
|
15
|
+
spec/looksee_spec.rb
|
16
|
+
spec/spec_helper.rb
|
17
|
+
tasks/extconf.rake
|
18
|
+
tasks/extconf/looksee.rake
|
data/README.rdoc
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
= Looksee
|
2
|
+
|
3
|
+
* http://github.com/oggy/looksee
|
4
|
+
|
5
|
+
== DESCRIPTION
|
6
|
+
|
7
|
+
Looksee lets you examine the method lookup path of objects in ways not
|
8
|
+
possible in plain ruby.
|
9
|
+
|
10
|
+
== SYNOPSIS
|
11
|
+
|
12
|
+
Pop this in your .irbrc :
|
13
|
+
|
14
|
+
require 'looksee/shortcuts'
|
15
|
+
|
16
|
+
This defines a method +lp+ ("lookup path") which lets you do:
|
17
|
+
|
18
|
+
irb(main):001:0> lp []
|
19
|
+
=> Array
|
20
|
+
& concat frozen? push taguri
|
21
|
+
* count hash rassoc taguri=
|
22
|
+
+ cycle include? reject take
|
23
|
+
- delete index reject! take_while
|
24
|
+
<< delete_at indexes replace to_a
|
25
|
+
<=> delete_if indices reverse to_ary
|
26
|
+
== drop insert reverse! to_s
|
27
|
+
[] drop_while inspect reverse_each to_yaml
|
28
|
+
[]= each join rindex transpose
|
29
|
+
assoc each_index last select uniq
|
30
|
+
at empty? length shift uniq!
|
31
|
+
choice eql? map shuffle unshift
|
32
|
+
clear fetch map! shuffle! values_at
|
33
|
+
collect fill nitems size yaml_initialize
|
34
|
+
collect! find_index pack slice zip
|
35
|
+
combination first permutation slice! |
|
36
|
+
compact flatten pop sort
|
37
|
+
compact! flatten! product sort!
|
38
|
+
Enumerable
|
39
|
+
all? each_slice first min reverse_each
|
40
|
+
any? each_with_index grep min_by select
|
41
|
+
collect entries group_by minmax sort
|
42
|
+
count enum_cons include? minmax_by sort_by
|
43
|
+
cycle enum_slice inject none? take
|
44
|
+
detect enum_with_index map one? take_while
|
45
|
+
drop find max partition to_a
|
46
|
+
drop_while find_all max_by reduce zip
|
47
|
+
each_cons find_index member? reject
|
48
|
+
Object
|
49
|
+
taguri taguri= to_yaml to_yaml_properties to_yaml_style
|
50
|
+
Kernel
|
51
|
+
== hash object_id
|
52
|
+
=== id private_methods
|
53
|
+
=~ inspect protected_methods
|
54
|
+
__id__ instance_eval public_methods
|
55
|
+
__send__ instance_exec respond_to?
|
56
|
+
class instance_of? send
|
57
|
+
clone instance_variable_defined? singleton_methods
|
58
|
+
display instance_variable_get taint
|
59
|
+
dup instance_variable_set tainted?
|
60
|
+
enum_for instance_variables tap
|
61
|
+
eql? is_a? to_a
|
62
|
+
equal? kind_of? to_enum
|
63
|
+
extend method to_s
|
64
|
+
freeze methods type
|
65
|
+
frozen? nil? untaint
|
66
|
+
|
67
|
+
It'll also color the methods according to whether they're public,
|
68
|
+
protected, private, or overridden. So pretty. You gotta try it.
|
69
|
+
|
70
|
+
By default, it shows public and protected methods. Add private ones
|
71
|
+
like so:
|
72
|
+
|
73
|
+
lp [], :private => true
|
74
|
+
lp [], :private # shortcut
|
75
|
+
|
76
|
+
Or if you don't want protected:
|
77
|
+
|
78
|
+
lp [], :protected => false
|
79
|
+
|
80
|
+
There are variations too. And you can configure things. And you can
|
81
|
+
use it as a library without polluting the built-in classes. See:
|
82
|
+
|
83
|
+
$ ri Looksee
|
84
|
+
|
85
|
+
Enjoy!
|
86
|
+
|
87
|
+
== INSTALL
|
88
|
+
|
89
|
+
gem install looksee
|
90
|
+
|
91
|
+
== FEATURES/PROBLEMS
|
92
|
+
|
93
|
+
* Currently only does MRI 1.8, 1.9.
|
94
|
+
|
95
|
+
== LICENSE
|
96
|
+
|
97
|
+
(The MIT License)
|
98
|
+
|
99
|
+
Copyright (c) 2009 George Ogata
|
100
|
+
|
101
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
102
|
+
a copy of this software and associated documentation files (the
|
103
|
+
'Software'), to deal in the Software without restriction, including
|
104
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
105
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
106
|
+
permit persons to whom the Software is furnished to do so, subject to
|
107
|
+
the following conditions:
|
108
|
+
|
109
|
+
The above copyright notice and this permission notice shall be
|
110
|
+
included in all copies or substantial portions of the Software.
|
111
|
+
|
112
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
113
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
114
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
115
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
116
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
117
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
118
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
require './lib/looksee/version'
|
7
|
+
|
8
|
+
Hoe.plugin :newgem
|
9
|
+
Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
$hoe = Hoe.spec 'looksee' do
|
12
|
+
self.developer 'George Ogata', 'george.ogata@gmail.com'
|
13
|
+
self.rubyforge_name = self.name # TODO this is default value
|
14
|
+
# self.extra_deps = [['activesupport','>= 2.0.2']]
|
15
|
+
self.extra_dev_deps = [
|
16
|
+
['newgem', ">= #{::Newgem::VERSION}"],
|
17
|
+
['rspec', '>= 1.2.7'],
|
18
|
+
['mocha', '>= 0.9.5'],
|
19
|
+
]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Configure the clean and clobber tasks.
|
23
|
+
require 'rake/clean'
|
24
|
+
require 'rbconfig'
|
25
|
+
CLEAN.include('**/*.o')
|
26
|
+
CLOBBER.include("ext/looksee/looksee.#{Config::CONFIG['DLEXT']}")
|
27
|
+
|
28
|
+
require 'newgem/tasks' # loads /tasks/*.rake
|
29
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
30
|
+
|
31
|
+
desc "Rebuild the gem from scratch."
|
32
|
+
task :regem => [:clobber, :gem]
|
33
|
+
|
34
|
+
# Force build before running specs.
|
35
|
+
Rake::Task['spec'].prerequisites << 'extconf:compile'
|
36
|
+
|
37
|
+
task :default => :spec
|
@@ -0,0 +1,126 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
3
|
+
#if RUBY_VERSION >= 190
|
4
|
+
# include "node-1.9.h"
|
5
|
+
# include "ruby/st.h"
|
6
|
+
#else
|
7
|
+
# include "node.h"
|
8
|
+
# include "st.h"
|
9
|
+
#endif
|
10
|
+
|
11
|
+
#if RUBY_VERSION < 187
|
12
|
+
# define RCLASS_IV_TBL(c) (RCLASS(c)->iv_tbl)
|
13
|
+
# define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl)
|
14
|
+
# define RCLASS_SUPER(c) (RCLASS(c)->super)
|
15
|
+
#endif
|
16
|
+
|
17
|
+
/*
|
18
|
+
* Return the internal superclass of this class.
|
19
|
+
*
|
20
|
+
* This is either a Class or "IClass." IClasses represent Modules
|
21
|
+
* included in the ancestry, and should be treated as opaque objects
|
22
|
+
* in ruby space. Convert the IClass to a Module using #iclass_to_module
|
23
|
+
* before using it in ruby.
|
24
|
+
*/
|
25
|
+
VALUE Looksee_internal_superclass(VALUE self, VALUE internal_class) {
|
26
|
+
VALUE super = RCLASS_SUPER(internal_class);
|
27
|
+
if (!super)
|
28
|
+
return Qnil;
|
29
|
+
return super;
|
30
|
+
}
|
31
|
+
|
32
|
+
/*
|
33
|
+
* Return the internal class of the given object.
|
34
|
+
*
|
35
|
+
* This is either the object's singleton class, if it exists, or the
|
36
|
+
* object's birth class.
|
37
|
+
*/
|
38
|
+
VALUE Looksee_internal_class(VALUE self, VALUE object) {
|
39
|
+
return RBASIC(object)->klass;
|
40
|
+
}
|
41
|
+
|
42
|
+
/*
|
43
|
+
* Return the class or module that the given internal class
|
44
|
+
* represents.
|
45
|
+
*
|
46
|
+
* If a class is given, this is the class. If an iclass is given,
|
47
|
+
* this is the module it represents in the lookup chain.
|
48
|
+
*/
|
49
|
+
VALUE Looksee_internal_class_to_module(VALUE self, VALUE internal_class) {
|
50
|
+
if (!SPECIAL_CONST_P(internal_class)) {
|
51
|
+
switch (BUILTIN_TYPE(internal_class)) {
|
52
|
+
case T_ICLASS:
|
53
|
+
return RBASIC(internal_class)->klass;
|
54
|
+
case T_CLASS:
|
55
|
+
return internal_class;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
rb_raise(rb_eArgError, "not an internal class: %s", RSTRING_PTR(rb_inspect(internal_class)));
|
59
|
+
}
|
60
|
+
|
61
|
+
typedef struct add_method_if_matching_arg {
|
62
|
+
VALUE names;
|
63
|
+
int visibility;
|
64
|
+
} add_method_if_matching_arg_t;
|
65
|
+
|
66
|
+
#if RUBY_VERSION < 190
|
67
|
+
# define VISIBILITY(node) ((node)->nd_noex & NOEX_MASK)
|
68
|
+
#else
|
69
|
+
# define VISIBILITY(node) ((node)->nd_body->nd_noex & NOEX_MASK)
|
70
|
+
#endif
|
71
|
+
|
72
|
+
static int add_method_if_matching(ID method_name, NODE *body, add_method_if_matching_arg_t *arg) {
|
73
|
+
/* This entry is for the internal allocator function. */
|
74
|
+
if (method_name == ID_ALLOCATOR)
|
75
|
+
return ST_CONTINUE;
|
76
|
+
|
77
|
+
/* Module#undef_method sets body->nd_body to NULL. */
|
78
|
+
if (!body || !body->nd_body)
|
79
|
+
return ST_CONTINUE;
|
80
|
+
|
81
|
+
if (VISIBILITY(body) == arg->visibility)
|
82
|
+
rb_ary_push(arg->names, ID2SYM(method_name));
|
83
|
+
return ST_CONTINUE;
|
84
|
+
}
|
85
|
+
|
86
|
+
static VALUE internal_instance_methods(VALUE klass, long visibility) {
|
87
|
+
add_method_if_matching_arg_t arg;
|
88
|
+
arg.names = rb_ary_new();
|
89
|
+
arg.visibility = visibility;
|
90
|
+
st_foreach(RCLASS_M_TBL(klass), add_method_if_matching, (st_data_t)&arg);
|
91
|
+
return arg.names;
|
92
|
+
}
|
93
|
+
|
94
|
+
/*
|
95
|
+
* Return the list of public instance methods (as Symbols) of the
|
96
|
+
* given internal class.
|
97
|
+
*/
|
98
|
+
VALUE Looksee_internal_public_instance_methods(VALUE self, VALUE klass) {
|
99
|
+
return internal_instance_methods(klass, NOEX_PUBLIC);
|
100
|
+
}
|
101
|
+
|
102
|
+
/*
|
103
|
+
* Return the list of protected instance methods (as Symbols) of the
|
104
|
+
* given internal class.
|
105
|
+
*/
|
106
|
+
VALUE Looksee_internal_protected_instance_methods(VALUE self, VALUE klass) {
|
107
|
+
return internal_instance_methods(klass, NOEX_PROTECTED);
|
108
|
+
}
|
109
|
+
|
110
|
+
/*
|
111
|
+
* Return the list of private instance methods (as Symbols) of the
|
112
|
+
* given internal class.
|
113
|
+
*/
|
114
|
+
VALUE Looksee_internal_private_instance_methods(VALUE self, VALUE klass) {
|
115
|
+
return internal_instance_methods(klass, NOEX_PRIVATE);
|
116
|
+
}
|
117
|
+
|
118
|
+
void Init_looksee(void) {
|
119
|
+
VALUE mLooksee = rb_define_module("Looksee");
|
120
|
+
rb_define_singleton_method(mLooksee, "internal_superclass", Looksee_internal_superclass, 1);
|
121
|
+
rb_define_singleton_method(mLooksee, "internal_class", Looksee_internal_class, 1);
|
122
|
+
rb_define_singleton_method(mLooksee, "internal_class_to_module", Looksee_internal_class_to_module, 1);
|
123
|
+
rb_define_singleton_method(mLooksee, "internal_public_instance_methods", Looksee_internal_public_instance_methods, 1);
|
124
|
+
rb_define_singleton_method(mLooksee, "internal_protected_instance_methods", Looksee_internal_protected_instance_methods, 1);
|
125
|
+
rb_define_singleton_method(mLooksee, "internal_private_instance_methods", Looksee_internal_private_instance_methods, 1);
|
126
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
/* MRI 1.9 does not install node.h. This is the part we need. */
|
2
|
+
|
3
|
+
typedef struct RNode {
|
4
|
+
unsigned long flags;
|
5
|
+
char *nd_file;
|
6
|
+
union {
|
7
|
+
struct RNode *node;
|
8
|
+
ID id;
|
9
|
+
VALUE value;
|
10
|
+
VALUE (*cfunc)(ANYARGS);
|
11
|
+
ID *tbl;
|
12
|
+
} u1;
|
13
|
+
union {
|
14
|
+
struct RNode *node;
|
15
|
+
ID id;
|
16
|
+
long argc;
|
17
|
+
VALUE value;
|
18
|
+
} u2;
|
19
|
+
union {
|
20
|
+
struct RNode *node;
|
21
|
+
ID id;
|
22
|
+
long state;
|
23
|
+
struct global_entry *entry;
|
24
|
+
long cnt;
|
25
|
+
VALUE value;
|
26
|
+
} u3;
|
27
|
+
} NODE;
|
28
|
+
|
29
|
+
#define nd_body u2.node
|
30
|
+
#define nd_noex u3.id
|
31
|
+
|
32
|
+
#define NOEX_PUBLIC 0x00
|
33
|
+
#define NOEX_PRIVATE 0x02
|
34
|
+
#define NOEX_PROTECTED 0x04
|
35
|
+
#define NOEX_MASK 0x06
|
data/lib/looksee.rb
ADDED
@@ -0,0 +1,332 @@
|
|
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
|
+
#
|
24
|
+
# See their docs.
|
25
|
+
#
|
26
|
+
# == Usage
|
27
|
+
#
|
28
|
+
# In irb:
|
29
|
+
#
|
30
|
+
# require 'looksee/shortcuts'
|
31
|
+
# lp some_object
|
32
|
+
#
|
33
|
+
# +lp+ returns a LookupPath object, which has +inspect+ defined to
|
34
|
+
# print things out pretty. By default, it shows public, protected,
|
35
|
+
# and overridden methods. They're all colored, which makes showing
|
36
|
+
# overridden methods not such a strange idea.
|
37
|
+
#
|
38
|
+
# Some examples of the other shortcuts:
|
39
|
+
#
|
40
|
+
# lpi Array
|
41
|
+
# some_object.lookup_path
|
42
|
+
# foo.bar.baz.dump_lookup_path.and.more
|
43
|
+
#
|
44
|
+
# If you're being namespace-clean, you'll need to do:
|
45
|
+
#
|
46
|
+
# require 'looksee'
|
47
|
+
# Looksee.lookup_path(thing) # like "lp thing"
|
48
|
+
#
|
49
|
+
# == Configuration
|
50
|
+
#
|
51
|
+
# Set these:
|
52
|
+
#
|
53
|
+
# Looksee.default_lookup_path_options
|
54
|
+
# Looksee.default_width
|
55
|
+
# Looksee.styles
|
56
|
+
#
|
57
|
+
# See their docs.
|
58
|
+
#
|
59
|
+
module Looksee
|
60
|
+
class << self
|
61
|
+
#
|
62
|
+
# Return a collection of methods that +object+ responds to,
|
63
|
+
# according to the options given. The following options are
|
64
|
+
# recognized:
|
65
|
+
#
|
66
|
+
# * +:public+ - include public methods
|
67
|
+
# * +:protected+ - include protected methods
|
68
|
+
# * +:private+ - include private methods
|
69
|
+
# * +:overridden+ - include methods overridden by subclasses
|
70
|
+
#
|
71
|
+
# The default (if options is nil or omitted) is [:public].
|
72
|
+
#
|
73
|
+
def lookup_path(object, *options)
|
74
|
+
normalized_options = Looksee.default_lookup_path_options.dup
|
75
|
+
hash_options = options.last.is_a?(Hash) ? options.pop : {}
|
76
|
+
options.each do |option|
|
77
|
+
normalized_options[option] = true
|
78
|
+
end
|
79
|
+
normalized_options.update(hash_options)
|
80
|
+
LookupPath.new(object, normalized_options)
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# The default options passed to lookup_path.
|
85
|
+
#
|
86
|
+
# Default: <tt>{:public => true, :protected => true, :overridden => true}</tt>
|
87
|
+
#
|
88
|
+
attr_accessor :default_lookup_path_options
|
89
|
+
|
90
|
+
#
|
91
|
+
# The width to use for displaying output, when not available in
|
92
|
+
# the COLUMNS environment variable.
|
93
|
+
#
|
94
|
+
# Default: 80
|
95
|
+
#
|
96
|
+
attr_accessor :default_width
|
97
|
+
|
98
|
+
#
|
99
|
+
# The default styles to use for the +inspect+ strings.
|
100
|
+
#
|
101
|
+
# This is a hash with keys:
|
102
|
+
#
|
103
|
+
# * :module
|
104
|
+
# * :public
|
105
|
+
# * :protected
|
106
|
+
# * :private
|
107
|
+
# * :overridden
|
108
|
+
#
|
109
|
+
# The values are format strings. They should all contain a single
|
110
|
+
# "%s", which is where the name is inserted.
|
111
|
+
#
|
112
|
+
# Default:
|
113
|
+
#
|
114
|
+
# {
|
115
|
+
# :module => "\e[1;37m%s\e[0m",
|
116
|
+
# :public => "\e[1;32m%s\e[0m",
|
117
|
+
# :protected => "\e[1;33m%s\e[0m",
|
118
|
+
# :private => "\e[1;31m%s\e[0m",
|
119
|
+
# :overridden => "\e[1;30m%s\e[0m",
|
120
|
+
# }
|
121
|
+
#
|
122
|
+
attr_accessor :styles
|
123
|
+
|
124
|
+
#
|
125
|
+
# Return the chain of classes and modules which comprise the
|
126
|
+
# object's method lookup path.
|
127
|
+
#
|
128
|
+
def lookup_modules(object)
|
129
|
+
modules = []
|
130
|
+
klass = Looksee.internal_class(object)
|
131
|
+
while klass
|
132
|
+
modules << Looksee.internal_class_to_module(klass)
|
133
|
+
klass = Looksee.internal_superclass(klass)
|
134
|
+
end
|
135
|
+
modules
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
self.default_lookup_path_options = {:public => true, :protected => true, :overridden => true}
|
140
|
+
self.default_width = 80
|
141
|
+
self.styles = {
|
142
|
+
:module => "\e[1;37m%s\e[0m",
|
143
|
+
:public => "\e[1;32m%s\e[0m",
|
144
|
+
:protected => "\e[1;33m%s\e[0m",
|
145
|
+
:private => "\e[1;31m%s\e[0m",
|
146
|
+
:overridden => "\e[1;30m%s\e[0m",
|
147
|
+
}
|
148
|
+
|
149
|
+
class LookupPath
|
150
|
+
attr_reader :entries
|
151
|
+
|
152
|
+
#
|
153
|
+
# Create a LookupPath for the given object.
|
154
|
+
#
|
155
|
+
# Options may be given to restrict which visibilities are
|
156
|
+
# included.
|
157
|
+
#
|
158
|
+
# :public
|
159
|
+
# :protected
|
160
|
+
# :private
|
161
|
+
# :overridden
|
162
|
+
#
|
163
|
+
def initialize(object, options={})
|
164
|
+
@entries = []
|
165
|
+
seen = {}
|
166
|
+
Looksee.lookup_modules(object).each do |mod|
|
167
|
+
entry = Entry.new(mod, seen, options)
|
168
|
+
entry.methods.each{|m| seen[m] = true}
|
169
|
+
@entries << entry
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def inspect(options={})
|
174
|
+
options = normalize_inspect_options(options)
|
175
|
+
entries.map{|e| e.inspect(options)}.join
|
176
|
+
end
|
177
|
+
|
178
|
+
private # -------------------------------------------------------
|
179
|
+
|
180
|
+
def normalize_inspect_options(options)
|
181
|
+
options[:width] ||= ENV['COLUMNS'].to_i.nonzero? || Looksee.default_width
|
182
|
+
options
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# An entry in the LookupPath.
|
187
|
+
#
|
188
|
+
# Contains a module and its methods, along with visibility
|
189
|
+
# information (public, private, etc.).
|
190
|
+
#
|
191
|
+
class Entry
|
192
|
+
#
|
193
|
+
# Don't call me, silly. I'm just part of a LookupPath.
|
194
|
+
#
|
195
|
+
def initialize(mod, seen, options)
|
196
|
+
@module = mod
|
197
|
+
@methods = []
|
198
|
+
@visibilities = {}
|
199
|
+
add_methods(Looksee.internal_public_instance_methods(mod).map{|sym| sym.to_s} , :public , seen) if options[:public ]
|
200
|
+
add_methods(Looksee.internal_protected_instance_methods(mod).map{|sym| sym.to_s}, :protected, seen) if options[:protected]
|
201
|
+
add_methods(Looksee.internal_private_instance_methods(mod).map{|sym| sym.to_s} , :private , seen) if options[:private ]
|
202
|
+
@methods.sort!
|
203
|
+
end
|
204
|
+
|
205
|
+
attr_reader :module, :methods
|
206
|
+
|
207
|
+
#
|
208
|
+
# Return the name of the class or module.
|
209
|
+
#
|
210
|
+
# Singleton classes are displayed in brackets. Singleton class
|
211
|
+
# of singleton classes are displayed in double brackets. But
|
212
|
+
# you'd never need that, would you?
|
213
|
+
#
|
214
|
+
def module_name
|
215
|
+
name = @module.to_s # #name doesn't do singleton classes right
|
216
|
+
nil while name.sub!(/#<Class:(.*)>/, '[\\1]')
|
217
|
+
name
|
218
|
+
end
|
219
|
+
|
220
|
+
#
|
221
|
+
# Yield each method along with its visibility (:public,
|
222
|
+
# :private, :protected, or :overridden).
|
223
|
+
#
|
224
|
+
def each
|
225
|
+
@methods.each do |name|
|
226
|
+
yield name, @visibilities[name]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
include Enumerable
|
231
|
+
|
232
|
+
#
|
233
|
+
# Return a nice, pretty string for inspection.
|
234
|
+
#
|
235
|
+
# Contains the module name, plus the method names laid out in
|
236
|
+
# columns. Pass a :width option to control the output width.
|
237
|
+
#
|
238
|
+
def inspect(options={})
|
239
|
+
styled_module_name << "\n" << Columnizer.columnize(styled_methods, options[:width])
|
240
|
+
end
|
241
|
+
|
242
|
+
private # -----------------------------------------------------
|
243
|
+
|
244
|
+
def add_methods(methods, visibility, seen)
|
245
|
+
methods.each do |method|
|
246
|
+
@methods << method
|
247
|
+
@visibilities[method] = seen[method] ? :overridden : visibility
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def styled_module_name
|
252
|
+
Looksee.styles[:module] % module_name
|
253
|
+
end
|
254
|
+
|
255
|
+
def styled_methods
|
256
|
+
map do |name, visibility|
|
257
|
+
Looksee.styles[visibility] % name
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
module Columnizer
|
264
|
+
class << self
|
265
|
+
#
|
266
|
+
# Arrange the given strings in columns, restricted to the given
|
267
|
+
# width. Smart enough to ignore content in terminal control
|
268
|
+
# sequences.
|
269
|
+
#
|
270
|
+
def columnize(strings, width)
|
271
|
+
num_columns = 1
|
272
|
+
layout = [strings]
|
273
|
+
loop do
|
274
|
+
break if layout.first.length <= 1
|
275
|
+
next_layout = layout_in_columns(strings, num_columns + 1)
|
276
|
+
break if layout_width(next_layout) > width
|
277
|
+
layout = next_layout
|
278
|
+
num_columns += 1
|
279
|
+
end
|
280
|
+
|
281
|
+
pad_strings(layout)
|
282
|
+
rectangularize_layout(layout)
|
283
|
+
layout.transpose.map do |row|
|
284
|
+
' ' + row.compact.join(' ')
|
285
|
+
end.join("\n") << "\n"
|
286
|
+
end
|
287
|
+
|
288
|
+
private # -----------------------------------------------------
|
289
|
+
|
290
|
+
def layout_in_columns(strings, num_columns)
|
291
|
+
strings_per_column = (strings.length / num_columns.to_f).ceil
|
292
|
+
(0...num_columns).map{|i| strings[i*strings_per_column...(i+1)*strings_per_column] || []}
|
293
|
+
end
|
294
|
+
|
295
|
+
def layout_width(layout)
|
296
|
+
widths = layout_column_widths(layout)
|
297
|
+
widths.inject(0){|sum, w| sum + w} + 2*layout.length
|
298
|
+
end
|
299
|
+
|
300
|
+
def layout_column_widths(layout)
|
301
|
+
layout.map do |column|
|
302
|
+
column.map{|string| display_width(string)}.max || 0
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def display_width(string)
|
307
|
+
# remove terminal control sequences
|
308
|
+
string.gsub(/\e\[.*?m/, '').length
|
309
|
+
end
|
310
|
+
|
311
|
+
def pad_strings(layout)
|
312
|
+
widths = layout_column_widths(layout)
|
313
|
+
layout.each_with_index do |column, i|
|
314
|
+
column_width = widths[i]
|
315
|
+
column.each do |string|
|
316
|
+
padding = column_width - display_width(string)
|
317
|
+
string << ' '*padding
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def rectangularize_layout(layout)
|
323
|
+
return if layout.length == 1
|
324
|
+
height = layout[0].length
|
325
|
+
layout[1..-1].each do |column|
|
326
|
+
column.length == height or
|
327
|
+
column[height - 1] = nil
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|