looksee 0.0.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 +3 -0
- data/Manifest.txt +18 -0
- data/README.rdoc +118 -0
- data/Rakefile +38 -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 +192 -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,38 @@
|
|
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.description_sections = %w'description synopsis'
|
15
|
+
# self.extra_deps = [['activesupport','>= 2.0.2']]
|
16
|
+
self.extra_dev_deps = [
|
17
|
+
['newgem', ">= #{::Newgem::VERSION}"],
|
18
|
+
['rspec', '>= 1.2.7'],
|
19
|
+
['mocha', '>= 0.9.5'],
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Configure the clean and clobber tasks.
|
24
|
+
require 'rake/clean'
|
25
|
+
require 'rbconfig'
|
26
|
+
CLEAN.include('**/*.o')
|
27
|
+
CLOBBER.include("ext/looksee/looksee.#{Config::CONFIG['DLEXT']}")
|
28
|
+
|
29
|
+
require 'newgem/tasks' # loads /tasks/*.rake
|
30
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
31
|
+
|
32
|
+
desc "Rebuild the gem from scratch."
|
33
|
+
task :regem => [:clobber, :gem]
|
34
|
+
|
35
|
+
# Force build before running specs.
|
36
|
+
Rake::Task['spec'].prerequisites << 'extconf:compile'
|
37
|
+
|
38
|
+
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
|