reflexive 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/reflexive/application.rb +171 -0
- data/lib/reflexive/coderay_html_encoder.rb +46 -0
- data/lib/reflexive/coderay_ruby_scanner.rb +234 -0
- data/lib/reflexive/columnizer.rb +73 -0
- data/lib/reflexive/constantize.rb +71 -0
- data/lib/reflexive/descendants.rb +39 -0
- data/lib/reflexive/faster_open_struct.rb +116 -0
- data/lib/reflexive/helpers.rb +189 -0
- data/lib/reflexive/methods.rb +140 -0
- data/lib/reflexive/parse_tree_top_down_walker.rb +392 -0
- data/lib/reflexive/reflexive_ripper.rb +274 -0
- data/lib/reflexive/routing_helpers.rb +70 -0
- data/lib/reflexive/variables_scope.rb +16 -0
- data/lib/reflexive.rb +1 -0
- data/public/javascripts/reflexive/jquery-1.4.2.js +6240 -0
- data/public/javascripts/reflexive/reflexive.js +12 -0
- data/public/stylesheets/reflexive/coderay.css +130 -0
- data/public/stylesheets/reflexive/reflexive.css +111 -0
- data/reflexive.gemspec +25 -0
- data/views/constants_methods.html.erb +0 -0
- data/views/constants_show.erb +96 -0
- data/views/dashboard.erb +50 -0
- data/views/directories_show.erb +16 -0
- data/views/error.erb +5 -0
- data/views/files_show.erb +27 -0
- data/views/layout.erb +20 -0
- data/views/methods_apidock.erb +17 -0
- data/views/methods_definition.erb +37 -0
- data/views/methods_rubydoc.erb +16 -0
- metadata +231 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
require "sinatra/base"
|
2
|
+
require "sinatra/reloader" if ENV["SINATRA_RELOADER"]
|
3
|
+
require "sinatra_more/markup_plugin"
|
4
|
+
|
5
|
+
require "coderay"
|
6
|
+
require "andand"
|
7
|
+
require "ostruct"
|
8
|
+
require "open-uri"
|
9
|
+
|
10
|
+
require "looksee"
|
11
|
+
Looksee.styles.each { |k, _| Looksee.styles[k] = "%s" }
|
12
|
+
|
13
|
+
require "reflexive/faster_open_struct"
|
14
|
+
require "reflexive/helpers"
|
15
|
+
require "reflexive/columnizer"
|
16
|
+
require "reflexive/constantize"
|
17
|
+
require "reflexive/descendants"
|
18
|
+
require "reflexive/methods"
|
19
|
+
|
20
|
+
if ENV["SINATRA_RELOADER"]
|
21
|
+
require "rails/all"
|
22
|
+
require "arel"
|
23
|
+
|
24
|
+
module ::Kernel
|
25
|
+
def r(*args)
|
26
|
+
raise((args.size == 1 ? args[0] : args).inspect)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Reflexive
|
32
|
+
class Application < Sinatra::Base
|
33
|
+
register SinatraMore::MarkupPlugin
|
34
|
+
include Reflexive::Helpers
|
35
|
+
|
36
|
+
configure(:development) do
|
37
|
+
if ENV["SINATRA_RELOADER"]
|
38
|
+
register Sinatra::Reloader
|
39
|
+
also_reload "lib/**/*.rb"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
def root
|
45
|
+
require "pathname"
|
46
|
+
Pathname("../../../").expand_path(__FILE__)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
set :public, self.root + "public"
|
51
|
+
set :views, self.root + "views"
|
52
|
+
|
53
|
+
def self.action(path, &block)
|
54
|
+
get("/reflexive/#{ path }", &block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def e(message)
|
58
|
+
@message = message
|
59
|
+
erb :error
|
60
|
+
end
|
61
|
+
|
62
|
+
action "dashboard" do
|
63
|
+
erb :dashboard
|
64
|
+
end
|
65
|
+
|
66
|
+
action "constant_lookup" do
|
67
|
+
if klass = Reflexive.constant_lookup(*params.values_at(:name, :scope))
|
68
|
+
redirect(constant_path(klass.to_s))
|
69
|
+
else
|
70
|
+
e "failed to lookup constant `#{ params[:name] }' in scope #{ params[:scope] }"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
action "files/*" do |path|
|
75
|
+
@path = "/" + path
|
76
|
+
if File.stat(@path).directory?
|
77
|
+
erb :directories_show
|
78
|
+
else
|
79
|
+
@source = highlight_file(@path)
|
80
|
+
erb :files_show
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
get "/reflexive/load_path_lookup" do
|
85
|
+
path = params[:path]
|
86
|
+
feature = Reflexive.loaded_features_lookup(path) || Reflexive.load_path_lookup(path)
|
87
|
+
if feature
|
88
|
+
redirect(file_path(feature))
|
89
|
+
else
|
90
|
+
e "failed to find feature: #{ path }"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
get %r</reflexive/constants/([^/&#]+)/class_methods/([^/&#]+)/definition> do |klass, method|
|
95
|
+
find_klass(klass)
|
96
|
+
@method_name = method
|
97
|
+
@path, @line = @klass.method(@method_name).source_location
|
98
|
+
@source = highlight_file(@path, :highlight_lines => [@line])
|
99
|
+
erb :methods_definition
|
100
|
+
end
|
101
|
+
|
102
|
+
get %r</reflexive/constants/([^/&#]+)/instance_methods/([^/&#]+)/definition> do |klass, method|
|
103
|
+
find_klass(klass)
|
104
|
+
@method_name = method
|
105
|
+
@path, @line = @klass.instance_method(@method_name).source_location
|
106
|
+
@source = highlight_file(@path, :highlight_lines => [@line])
|
107
|
+
erb :methods_definition
|
108
|
+
end
|
109
|
+
|
110
|
+
get %r</reflexive/constants/([^/&#]+)/methods/([^/&#]+)/apidock> do |klass, method|
|
111
|
+
find_klass(klass)
|
112
|
+
@method_name = method
|
113
|
+
erb :methods_apidock
|
114
|
+
end
|
115
|
+
|
116
|
+
get %r</reflexive/constants/([^/&#]+)/class_methods/([^/&#]+)> do |klass, method|
|
117
|
+
find_klass(klass)
|
118
|
+
begin
|
119
|
+
if @klass.method(method).source_location
|
120
|
+
redirect(class_method_definition_path(klass, method) +
|
121
|
+
"#highlighted")
|
122
|
+
else
|
123
|
+
redirect(method_documentation_path(klass, method))
|
124
|
+
end
|
125
|
+
rescue NameError
|
126
|
+
e "failed to find `#{ method }' class method for #{ klass }"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
get %r</reflexive/constants/([^/&#]+)/instance_methods/([^/&#]+)> do |klass, method|
|
131
|
+
find_klass(klass)
|
132
|
+
begin
|
133
|
+
if @klass.instance_method(method).source_location
|
134
|
+
redirect(instance_method_definition_path(klass, method) +
|
135
|
+
"#highlighted")
|
136
|
+
else
|
137
|
+
redirect(method_documentation_path(klass, method))
|
138
|
+
end
|
139
|
+
rescue NameError
|
140
|
+
e "failed to find `#{ method }' instance method for #{ klass }"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
get %r</reflexive/constants/([^/&#]+)> do |klass|
|
145
|
+
find_klass(klass)
|
146
|
+
|
147
|
+
exclude_trite = ![ BasicObject, Object ].include?(@klass)
|
148
|
+
@methods = Reflexive::Methods.new(@klass, :exclude_trite => exclude_trite)
|
149
|
+
|
150
|
+
ancestors_without_self_and_super = @klass.ancestors[2..-1] || []
|
151
|
+
class_ancestors = ancestors_without_self_and_super.select { |ancestor| ancestor.class == Class }
|
152
|
+
@class_ancestors = class_ancestors if class_ancestors.size > 0
|
153
|
+
|
154
|
+
if @klass.respond_to?(:superclass) &&
|
155
|
+
@klass.superclass != Object &&
|
156
|
+
@klass.superclass != nil
|
157
|
+
@superclass = @klass.superclass
|
158
|
+
end
|
159
|
+
|
160
|
+
erb :constants_show
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
error(404) { @app.call(env) if @app }
|
166
|
+
|
167
|
+
def find_klass(klass = params[:klass])
|
168
|
+
@klass = Reflexive.constantize(klass) if klass
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "reflexive/routing_helpers"
|
2
|
+
|
3
|
+
require "coderay"
|
4
|
+
require "coderay/encoder"
|
5
|
+
require "coderay/encoders/html"
|
6
|
+
|
7
|
+
module Reflexive
|
8
|
+
class CodeRayHtmlEncoder < ::CodeRay::Encoders::HTML
|
9
|
+
include RoutingHelpers
|
10
|
+
|
11
|
+
def token(text, type = :plain, tags = {})
|
12
|
+
if constant_access = tags[:constant_access]
|
13
|
+
name, scope = constant_access.values_at(:name, :scope)
|
14
|
+
@out << "<a href='#{ constant_lookup_path(name, scope) }'>"
|
15
|
+
super(text, type)
|
16
|
+
@out << "</a>"
|
17
|
+
elsif type == :meta_scope
|
18
|
+
# pass
|
19
|
+
elsif type == :content && tags[:load_path]
|
20
|
+
@out << "<a href='#{ load_path_lookup_path(text) }'>"
|
21
|
+
super(text, type)
|
22
|
+
@out << "</a>"
|
23
|
+
elsif method_call = tags[:method_call]
|
24
|
+
@out << "<a href='#{ method_call_path(method_call) }'>"
|
25
|
+
super(text, type)
|
26
|
+
@out << "</a>"
|
27
|
+
elsif local_variable_assignment = tags[:local_variable_assignment]
|
28
|
+
@out << "<span id='lv:#{ local_variable_assignment }'>"
|
29
|
+
super(text, type)
|
30
|
+
@out << "</span>"
|
31
|
+
elsif local_variable_access = tags[:local_variable_access]
|
32
|
+
@out << "<a href='#lv:#{ local_variable_access }' class='lva'>"
|
33
|
+
super(text, type)
|
34
|
+
@out << "</a>"
|
35
|
+
else
|
36
|
+
super(text, type) rescue raise([text, type].inspect)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def compile(tokens, options)
|
41
|
+
for token in tokens
|
42
|
+
token(*token)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require "coderay"
|
2
|
+
|
3
|
+
module Reflexive
|
4
|
+
require "reflexive/reflexive_ripper"
|
5
|
+
|
6
|
+
# TODO Heredocs in args are royally screwed
|
7
|
+
class CodeRayRubyScanner < ::CodeRay::Scanners::Scanner
|
8
|
+
SCANNER_EVENT_TO_CODERAY_TOKEN =
|
9
|
+
{
|
10
|
+
:kw => :reserved,
|
11
|
+
:nl => :space,
|
12
|
+
:sp => :space,
|
13
|
+
:ignored_nl => :space,
|
14
|
+
# :tstring_beg => :delimiter,
|
15
|
+
# :tstring_end => :delimiter,
|
16
|
+
:tstring_content => :content,
|
17
|
+
# Кipper reports rbrace always, CodeRay differentiates
|
18
|
+
# between blocks, and rbraces in string interpol
|
19
|
+
# :embexpr_beg => :inline_delimiter,
|
20
|
+
:lbrace => :operator,
|
21
|
+
:rbrace => :operator,
|
22
|
+
:lparen => :operator,
|
23
|
+
:rparen => :operator,
|
24
|
+
:lbracket => :operator,
|
25
|
+
:rbracket => :operator,
|
26
|
+
:comma => :operator,
|
27
|
+
:op => :operator,
|
28
|
+
:int => :integer,
|
29
|
+
:period => :operator,
|
30
|
+
:const => :constant,
|
31
|
+
:cvar => :class_variable,
|
32
|
+
:ivar => :instance_variable,
|
33
|
+
:gvar => :global_variable,
|
34
|
+
:embvar => :escape, # ?
|
35
|
+
:embdoc_beg => :comment,
|
36
|
+
:embdoc => :comment,
|
37
|
+
:embdoc_end => :comment,
|
38
|
+
:qwords_beg => :content,
|
39
|
+
:words_sep => :content,
|
40
|
+
:CHAR => :integer,
|
41
|
+
# * :constant => :class, CodeRay reports `class` token for
|
42
|
+
# class def, we report just `const` always
|
43
|
+
# * Ripper doesn't have `char` event
|
44
|
+
# * `42 if true` - CodeRay reports `pre_constant`
|
45
|
+
}
|
46
|
+
|
47
|
+
def coderay_tokens(scanner_events)
|
48
|
+
@coderay_tokens = []
|
49
|
+
in_backtick = false
|
50
|
+
in_symbol = false
|
51
|
+
in_embexpr_nesting = 0
|
52
|
+
scanner_events.each do |scanner_event|
|
53
|
+
token_val, event, tags =
|
54
|
+
ReflexiveRipper.destruct_scanner_event(scanner_event)
|
55
|
+
|
56
|
+
# if event == :meta_scope
|
57
|
+
# @coderay_tokens << [token_val, event]
|
58
|
+
# next
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# if token_val == :method_call
|
62
|
+
# @coderay_tokens << [token_val, event, tags]
|
63
|
+
# next
|
64
|
+
# end
|
65
|
+
|
66
|
+
ripper_token = SCANNER_EVENT_TO_CODERAY_TOKEN[event.to_sym] || event.to_sym
|
67
|
+
if in_backtick && event == :lparen
|
68
|
+
@coderay_tokens.pop # remove [:open, :shell], [token_val, :delimiter]
|
69
|
+
@coderay_tokens.pop # and replace with method declaration
|
70
|
+
@coderay_tokens << ["`", :method]
|
71
|
+
@coderay_tokens << ["(", :operator]
|
72
|
+
elsif in_embexpr_nesting > 0 && event == :rbrace
|
73
|
+
@coderay_tokens << [token_val, :inline_delimiter]
|
74
|
+
@coderay_tokens << [:close, :inline]
|
75
|
+
in_embexpr_nesting -= 1
|
76
|
+
elsif event == :embexpr_beg
|
77
|
+
@coderay_tokens << [:open, :inline]
|
78
|
+
@coderay_tokens << [token_val, :inline_delimiter]
|
79
|
+
in_embexpr_nesting += 1
|
80
|
+
elsif in_symbol && [:ident, :const, :ivar, :cvar, :gvar, :op, :kw].include?(event)
|
81
|
+
# parse.y
|
82
|
+
#
|
83
|
+
# symbol : tSYMBEG sym
|
84
|
+
#
|
85
|
+
# sym : fname
|
86
|
+
# | tIVAR
|
87
|
+
# | tGVAR
|
88
|
+
# | tCVAR
|
89
|
+
# ;
|
90
|
+
#
|
91
|
+
# fname : tIDENTIFIER
|
92
|
+
# | tCONSTANT
|
93
|
+
# | tFID
|
94
|
+
# | op
|
95
|
+
# {
|
96
|
+
# lex_state = EXPR_ENDFN;
|
97
|
+
# $$ = $1;
|
98
|
+
# }
|
99
|
+
# | reswords
|
100
|
+
# {
|
101
|
+
# lex_state = EXPR_ENDFN;
|
102
|
+
# /*%%%*/
|
103
|
+
# $$ = $<id>1;
|
104
|
+
# /*%
|
105
|
+
# $$ = $1;
|
106
|
+
# %*/
|
107
|
+
# }
|
108
|
+
# ;
|
109
|
+
@coderay_tokens << [token_val, :content]
|
110
|
+
@coderay_tokens << [:close, :symbol]
|
111
|
+
in_symbol = false
|
112
|
+
elsif ripper_token == :regexp_beg
|
113
|
+
@coderay_tokens << [:open, :regexp]
|
114
|
+
@coderay_tokens << [token_val, :delimiter]
|
115
|
+
elsif ripper_token == :regexp_end
|
116
|
+
@coderay_tokens << [token_val, :delimiter]
|
117
|
+
@coderay_tokens << [:close, :regexp]
|
118
|
+
elsif ripper_token == :tstring_beg
|
119
|
+
@coderay_tokens << [:open, :string]
|
120
|
+
@coderay_tokens << [token_val, :delimiter]
|
121
|
+
elsif ripper_token == :tstring_end
|
122
|
+
@coderay_tokens << [token_val, :delimiter]
|
123
|
+
if in_backtick
|
124
|
+
@coderay_tokens << [:close, :shell]
|
125
|
+
in_backtick = false
|
126
|
+
elsif in_symbol
|
127
|
+
@coderay_tokens << [:close, :symbol]
|
128
|
+
in_symbol = false
|
129
|
+
else
|
130
|
+
@coderay_tokens << [:close, :string]
|
131
|
+
end
|
132
|
+
elsif ripper_token == :symbeg
|
133
|
+
if in_symbol # nesting not supported
|
134
|
+
@coderay_tokens << [token_val, :symbol]
|
135
|
+
else
|
136
|
+
@coderay_tokens << [:open, :symbol]
|
137
|
+
@coderay_tokens << [token_val, :delimiter]
|
138
|
+
in_symbol = true
|
139
|
+
end
|
140
|
+
elsif ripper_token == :backtick
|
141
|
+
if in_backtick # nesting not supported
|
142
|
+
@coderay_tokens << [token_val, :operator]
|
143
|
+
else
|
144
|
+
@coderay_tokens << [:open, :shell]
|
145
|
+
@coderay_tokens << [token_val, :delimiter]
|
146
|
+
in_backtick = true
|
147
|
+
end
|
148
|
+
else
|
149
|
+
if @coderay_tokens.size > 0 && @coderay_tokens[-1][1] == ripper_token
|
150
|
+
@coderay_tokens.last[0] << token_val # same consequent tokens get squeezed
|
151
|
+
else
|
152
|
+
@coderay_tokens << [token_val, ripper_token]
|
153
|
+
@coderay_tokens.last << tags if tags
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
tokens = @coderay_tokens
|
159
|
+
# tokens = squeeze_constants(tokens)
|
160
|
+
# tokens = inject_constant_tags(tokens)
|
161
|
+
tokens = inject_load_path_tags(tokens)
|
162
|
+
tokens
|
163
|
+
end
|
164
|
+
|
165
|
+
def squeeze_constants(coderay_tokens)
|
166
|
+
tokens = []
|
167
|
+
coderay_tokens.reverse_each do |token|
|
168
|
+
if tokens.size > 0
|
169
|
+
if token[1] == :constant &&
|
170
|
+
tokens[-1][1] == :constant
|
171
|
+
tokens[-1][0] = "#{ token[0] }#{ tokens[-1][0] }"
|
172
|
+
elsif token == [ "::", :operator ] &&
|
173
|
+
tokens[-1][1] == :constant
|
174
|
+
tokens[-1][0] = "#{ token[0] }#{ tokens[-1][0] }"
|
175
|
+
else
|
176
|
+
tokens << token
|
177
|
+
end
|
178
|
+
else
|
179
|
+
tokens << token
|
180
|
+
end
|
181
|
+
end
|
182
|
+
tokens.reverse
|
183
|
+
end
|
184
|
+
|
185
|
+
def inject_constant_tags(coderay_tokens)
|
186
|
+
scope = nil
|
187
|
+
coderay_tokens.each do |token|
|
188
|
+
if token[1] == :constant
|
189
|
+
token << { :scope => scope }
|
190
|
+
elsif token[0] == :method_call &&
|
191
|
+
token[1] == :open
|
192
|
+
begin
|
193
|
+
token[2][:scope] = scope
|
194
|
+
rescue Exception
|
195
|
+
r(token)
|
196
|
+
end
|
197
|
+
|
198
|
+
elsif token[1] == :meta_scope
|
199
|
+
scope = token[0]
|
200
|
+
scope = nil if scope.empty?
|
201
|
+
end
|
202
|
+
end
|
203
|
+
coderay_tokens
|
204
|
+
end
|
205
|
+
|
206
|
+
def inject_load_path_tags(coderay_tokens)
|
207
|
+
state = nil
|
208
|
+
coderay_tokens.each do |token|
|
209
|
+
value, type = token
|
210
|
+
if state == nil && type == :ident && value =~ /\A(require|load)\z/
|
211
|
+
state = :ident
|
212
|
+
elsif state == :ident && [ :operator, :space ].include?(type)
|
213
|
+
# skip
|
214
|
+
elsif state == :ident && value == :open && type == :string
|
215
|
+
state = :open_string
|
216
|
+
elsif state == :open_string && type == :delimiter
|
217
|
+
# skip
|
218
|
+
elsif state == :open_string && type == :content
|
219
|
+
token << { :load_path => true }
|
220
|
+
else
|
221
|
+
state = nil
|
222
|
+
end
|
223
|
+
end
|
224
|
+
coderay_tokens
|
225
|
+
end
|
226
|
+
|
227
|
+
def scan_tokens(tokens, options)
|
228
|
+
parser = ReflexiveRipper.new(code)
|
229
|
+
parser.parse
|
230
|
+
tokens.replace(coderay_tokens(parser.scanner_events))
|
231
|
+
tokens
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "cgi"
|
2
|
+
|
3
|
+
module Reflexive
|
4
|
+
module Columnizer
|
5
|
+
class << self
|
6
|
+
#
|
7
|
+
# Arrange the given strings in columns, restricted to the given
|
8
|
+
# width. Smart enough to ignore content in terminal control
|
9
|
+
# sequences.
|
10
|
+
#
|
11
|
+
def columnize(strings, width)
|
12
|
+
return '' if strings.empty?
|
13
|
+
num_columns = 1
|
14
|
+
layout = [strings]
|
15
|
+
loop do
|
16
|
+
break if layout.first.length <= 1
|
17
|
+
next_layout = layout_in_columns(strings, num_columns + 1)
|
18
|
+
break if layout_width(next_layout) > width
|
19
|
+
layout = next_layout
|
20
|
+
num_columns += 1
|
21
|
+
end
|
22
|
+
|
23
|
+
pad_strings(layout)
|
24
|
+
rectangularize_layout(layout)
|
25
|
+
layout.transpose.map do |row|
|
26
|
+
' ' + row.compact.join(' ')
|
27
|
+
end.join("\n") << "\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
private # -----------------------------------------------------
|
31
|
+
|
32
|
+
def layout_in_columns(strings, num_columns)
|
33
|
+
strings_per_column = (strings.length / num_columns.to_f).ceil
|
34
|
+
(0...num_columns).map { |i| strings[i*strings_per_column...(i+1)*strings_per_column] || [] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def layout_width(layout)
|
38
|
+
widths = layout_column_widths(layout)
|
39
|
+
widths.inject(0) { |sum, w| sum + w } + 2*layout.length
|
40
|
+
end
|
41
|
+
|
42
|
+
def layout_column_widths(layout)
|
43
|
+
layout.map do |column|
|
44
|
+
column.map { |string| display_width(string) }.max || 0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def display_width(string)
|
49
|
+
CGI.unescapeHTML(string.gsub(/<\/?.+?>/, '')).size
|
50
|
+
end
|
51
|
+
|
52
|
+
def pad_strings(layout)
|
53
|
+
widths = layout_column_widths(layout)
|
54
|
+
layout.each_with_index do |column, i|
|
55
|
+
column_width = widths[i]
|
56
|
+
column.each do |string|
|
57
|
+
padding = column_width - display_width(string)
|
58
|
+
string << ' '*padding
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def rectangularize_layout(layout)
|
64
|
+
return if layout.length == 1
|
65
|
+
height = layout[0].length
|
66
|
+
layout[1..-1].each do |column|
|
67
|
+
column.length == height or
|
68
|
+
column[height - 1] = nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Reflexive
|
2
|
+
module_function
|
3
|
+
|
4
|
+
def constant_lookup(name, scope)
|
5
|
+
if name =~ /^::/
|
6
|
+
begin
|
7
|
+
return Reflexive.constantize(name)
|
8
|
+
rescue NameError, ArgumentError
|
9
|
+
return nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
scope_parts = scope.split("::")
|
14
|
+
|
15
|
+
begin
|
16
|
+
name_with_scope = "#{ scope_parts.join("::") }::#{ name }"
|
17
|
+
return Reflexive.constantize(name_with_scope)
|
18
|
+
rescue NameError, ArgumentError
|
19
|
+
# For defined top-level module, when looked up from another class:
|
20
|
+
# ArgumentError: Object is not missing constant TopLevelConst!
|
21
|
+
# from .../activesupport-2.3.5/lib/active_support/dependencies.rb:417:in `load_missing_constant'
|
22
|
+
retry if scope_parts.pop
|
23
|
+
end
|
24
|
+
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# from C:\Users\work\Documents\ubuntu_shared\edge\vendor\rails\activesupport\lib\active_support\inflector\methods.rb
|
29
|
+
# Ruby 1.9 introduces an inherit argument for Module#const_get and
|
30
|
+
# #const_defined? and changes their default behavior.
|
31
|
+
if Module.method(:const_get).arity == 1
|
32
|
+
# Tries to find a constant with the name specified in the argument string:
|
33
|
+
#
|
34
|
+
# "Module".constantize # => Module
|
35
|
+
# "Test::Unit".constantize # => Test::Unit
|
36
|
+
#
|
37
|
+
# The name is assumed to be the one of a top-level constant, no matter whether
|
38
|
+
# it starts with "::" or not. No lexical context is taken into account:
|
39
|
+
#
|
40
|
+
# C = 'outside'
|
41
|
+
# module M
|
42
|
+
# C = 'inside'
|
43
|
+
# C # => 'inside'
|
44
|
+
# "C".constantize # => 'outside', same as ::C
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# NameError is raised when the name is not in CamelCase or the constant is
|
48
|
+
# unknown.
|
49
|
+
def constantize(camel_cased_word)
|
50
|
+
names = camel_cased_word.split('::')
|
51
|
+
names.shift if names.empty? || names.first.empty?
|
52
|
+
|
53
|
+
constant = Object
|
54
|
+
names.each do |name|
|
55
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
56
|
+
end
|
57
|
+
constant
|
58
|
+
end
|
59
|
+
else
|
60
|
+
def constantize(camel_cased_word) #:nodoc:
|
61
|
+
names = camel_cased_word.split('::')
|
62
|
+
names.shift if names.empty? || names.first.empty?
|
63
|
+
|
64
|
+
constant = Object
|
65
|
+
names.each do |name|
|
66
|
+
constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
|
67
|
+
end
|
68
|
+
constant
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Reflexive
|
2
|
+
module_function
|
3
|
+
|
4
|
+
# List all descedents of this class.
|
5
|
+
#
|
6
|
+
# class X ; end
|
7
|
+
# class A < X; end
|
8
|
+
# class B < X; end
|
9
|
+
# X.descendents #=> [A,B]
|
10
|
+
#
|
11
|
+
# You may also limit the generational distance
|
12
|
+
# the subclass may be from the parent class.
|
13
|
+
#
|
14
|
+
# class X ; end
|
15
|
+
# class A < X; end
|
16
|
+
# class B < A; end
|
17
|
+
# X.descendents #=> [A, B]
|
18
|
+
# X.descendents(1) #=> [A]
|
19
|
+
#
|
20
|
+
# NOTE: This is a intensive operation. Do not
|
21
|
+
# expect it to be super fast.
|
22
|
+
|
23
|
+
def descendants(klass, generations=nil)
|
24
|
+
subclass = []
|
25
|
+
|
26
|
+
ObjectSpace.each_object(Class) do |c|
|
27
|
+
next if c == klass
|
28
|
+
|
29
|
+
ancestors = c.ancestors[0 .. (generations || -1)]
|
30
|
+
subclass << c if ancestors.include?(klass)
|
31
|
+
|
32
|
+
ancestors = c.singleton_class.ancestors[0 .. (generations || -1)]
|
33
|
+
subclass << c if ancestors.include?(klass)
|
34
|
+
end
|
35
|
+
|
36
|
+
subclass
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|