reflexive 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,116 @@
1
+ module Faster
2
+ # ## Faster::OpenStruct
3
+ #
4
+ # Up to 40 (!) times more memory efficient version of OpenStruct
5
+ #
6
+ # Differences from Ruby MRI OpenStruct:
7
+ #
8
+ # 1. Doesn't `dup` passed initialization hash (NOTE: only reference to hash is stored)
9
+ #
10
+ # 2. Doesn't convert hash keys to symbols (by default string keys are used,
11
+ # with fallback to symbol keys)
12
+ #
13
+ # 3. Creates methods on the fly on `OpenStruct` class, instead of singleton class.
14
+ # Uses `module_eval` with string to avoid holding scope references for every method.
15
+ #
16
+ # 4. Refactored, crud clean, spec covered :)
17
+ #
18
+ class OpenStruct
19
+ # Undefine particularly nasty interfering methods on Ruby 1.8
20
+ undef :type if method_defined?(:type)
21
+ undef :id if method_defined?(:id)
22
+
23
+ def initialize(hash = nil)
24
+ @hash = hash || {}
25
+ @initialized_empty = hash == nil
26
+ end
27
+
28
+ def method_missing(method_name_sym, *args)
29
+ if method_name_sym.to_s[-1] == ?=
30
+ if args.size != 1
31
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 1)", caller(1)
32
+ end
33
+
34
+ if self.frozen?
35
+ raise TypeError, "can't modify frozen #{self.class}", caller(1)
36
+ end
37
+
38
+ __new_ostruct_member__(method_name_sym.to_s.chomp("="))
39
+ send(method_name_sym, args[0])
40
+ elsif args.size == 0
41
+ __new_ostruct_member__(method_name_sym)
42
+ send(method_name_sym)
43
+ else
44
+ raise NoMethodError, "undefined method `#{method_name_sym}' for #{self}", caller(1)
45
+ end
46
+ end
47
+
48
+ def __new_ostruct_member__(method_name_sym)
49
+ self.class.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
50
+ def #{ method_name_sym }
51
+ @hash.fetch("#{ method_name_sym }", @hash[:#{ method_name_sym }]) # read by default from string key, then try symbol
52
+ # if string key doesn't exist
53
+ end
54
+ END_EVAL
55
+
56
+ unless method_name_sym.to_s[-1] == ?? # can't define writer for predicate method
57
+ self.class.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
58
+ def #{ method_name_sym }=(val)
59
+ if @hash.key?("#{ method_name_sym }") || @initialized_empty # write by default to string key (when it is present
60
+ # in initialization hash or initialization hash
61
+ # wasn't provided)
62
+ @hash["#{ method_name_sym }"] = val # if it doesn't exist - write to symbol key
63
+ else
64
+ @hash[:#{ method_name_sym }] = val
65
+ end
66
+ end
67
+ END_EVAL
68
+ end
69
+ end
70
+
71
+ def empty?
72
+ @hash.empty?
73
+ end
74
+
75
+ #
76
+ # Compare this object and +other+ for equality.
77
+ #
78
+ def ==(other)
79
+ return false unless other.is_a?(self.class)
80
+ @hash == other.instance_variable_get(:@hash)
81
+ end
82
+
83
+ InspectKey = :__inspect_key__ # :nodoc:
84
+
85
+ #
86
+ # Returns a string containing a detailed summary of the keys and values.
87
+ #
88
+ def inspect
89
+ str = "#<#{ self.class }"
90
+ str << " #{ @hash.map { |k, v| "#{ k }=#{ v.inspect }" }.join(", ") }" unless @hash.empty?
91
+ str << ">"
92
+ end
93
+
94
+ def inspect_with_reentrant_guard(default = "...")
95
+ Thread.current[InspectKey] ||= []
96
+
97
+ if Thread.current[InspectKey].include?(self)
98
+ return default # reenter detected
99
+ end
100
+
101
+ Thread.current[InspectKey] << self
102
+
103
+ begin
104
+ inspect_without_reentrant_guard
105
+ ensure
106
+ Thread.current[InspectKey].pop
107
+ end
108
+ end
109
+
110
+ alias_method :inspect_without_reentrant_guard, :inspect
111
+ alias_method :inspect, :inspect_with_reentrant_guard
112
+
113
+ alias :to_s :inspect
114
+ end
115
+ end
116
+
@@ -0,0 +1,189 @@
1
+ require "reflexive/routing_helpers"
2
+ require "reflexive/coderay_ruby_scanner"
3
+ require "reflexive/coderay_html_encoder"
4
+
5
+ module Reflexive
6
+ FILE_EXT = /\.\w+\z/
7
+ DL_EXT = /\.s?o\z/
8
+ def self.load_path_lookup(path)
9
+ path_with_rb_ext = path.sub(FILE_EXT, "") + ".rb"
10
+ feature = nil
11
+ $LOAD_PATH.detect do |load_path|
12
+ File.exists?(feature = File.join(load_path, path_with_rb_ext))
13
+ end
14
+ feature
15
+ end
16
+
17
+ def self.loaded_features_lookup(path)
18
+ path_without_ext = path.sub(FILE_EXT, "")
19
+
20
+ $LOADED_FEATURES.reverse.reject do |feature|
21
+ feature =~ DL_EXT
22
+ end.detect do |feature|
23
+ feature_without_ext = feature.sub(FILE_EXT, "")
24
+
25
+ feature_without_ext =~ /\/#{ Regexp.escape(path_without_ext) }\z/
26
+ end
27
+ end
28
+
29
+ module Helpers
30
+ include RoutingHelpers
31
+
32
+ def filter_existing_constants(constants)
33
+ constants.
34
+ select { |c| Kernel.const_defined?(c) }.
35
+ map { |c| Kernel.const_get(c) }
36
+ end
37
+
38
+ CODERAY_ENCODER_OPTIONS = {
39
+ :wrap => :div,
40
+ :css => :class,
41
+ :line_numbers => :inline
42
+ }.freeze
43
+
44
+ def highlight_file(path, options = {})
45
+ options = CODERAY_ENCODER_OPTIONS.merge(options)
46
+ if path =~ /\.rb$/
47
+ src = IO.read(path)
48
+ tokens = CodeRayRubyScanner.new(src).tokenize
49
+ encoder = CodeRayHtmlEncoder.new(options)
50
+ encoder.encode_tokens(tokens)
51
+ elsif path =~ /\.(markdown|md)$/
52
+ require "rdiscount"
53
+ src = IO.read(path)
54
+ markdown = RDiscount.new(src)
55
+ markdown.to_html
56
+ else
57
+ CodeRay.scan_file(path).html(options).div
58
+ end
59
+ end
60
+
61
+ def constant_name(klass)
62
+ klass.name || klass.to_s
63
+ end
64
+
65
+ def link_to_file(path, options = {})
66
+ link_text = if options[:file_name_only]
67
+ File.basename(path) + (path[-1] == ?/ ? "/" : "")
68
+ else
69
+ shorten_file_path(path)
70
+ end
71
+
72
+ link_to(link_text,
73
+ file_path(path),
74
+ :title => path,
75
+ :class => "path")
76
+ end
77
+
78
+ def shorten_file_path(path)
79
+ require "rbconfig"
80
+ path.
81
+ gsub(/\A#{ Regexp.escape(Gem.dir) }\/gems/, '#{gems}').
82
+ gsub(/\A#{ Config::CONFIG["rubylibdir"] }/, '#{rubylib}')
83
+ end
84
+
85
+ def load_and_highlight(location)
86
+ tokens = CodeRay.scan(IO.read(location), :ruby)
87
+
88
+ tokens.html(:line_numbers => :inline, :wrap => :page)
89
+ end
90
+
91
+ def methods_table(constant, lookup_path)
92
+ linked_methods = lookup_path.map do |name, visibility|
93
+ link_to_method(lookup_path.module_name.gsub(/\[|\]/, ""),
94
+ name,
95
+ visibility)
96
+ end
97
+
98
+ Reflexive::Columnizer.columnize(linked_methods, 120)
99
+ end
100
+
101
+ def new_methods_table(constant, level, methods)
102
+ linked_methods = methods.sort.map do |name|
103
+ new_link_to_method(constant, level, name)
104
+ end
105
+ Reflexive::Columnizer.columnize(linked_methods, 120)
106
+ end
107
+
108
+ def constants_table(base_constant, constants, width = 120)
109
+ Reflexive::Columnizer.columnize(constants_links(base_constant, constants), width)
110
+ end
111
+
112
+ def constants_list(base_constant, constants)
113
+ constants_links(base_constant, constants).join("<br/>")
114
+ end
115
+
116
+ def constants_links(base_constant, constants)
117
+ constants.map do |constant|
118
+ full_name = constant_name(constant)
119
+ [ full_name, constant ]
120
+ end.sort_by(&:first).map do |full_name, constant|
121
+ link_text = full_name.gsub("#{ base_constant }::", "")
122
+ link_to(link_text, constant_path(constant), :title => full_name)
123
+ end
124
+ end
125
+
126
+ def instance_methods_table(lookup_path)
127
+ linked_methods = []
128
+
129
+ %w(public protected private).each do |visibility|
130
+ methods = lookup_path.module.send("#{ visibility }_instance_methods", false)
131
+
132
+ linked_methods += methods.map do |name|
133
+ link_to_method(lookup_path.module_name.gsub(/\[|\]/, ""),
134
+ name,
135
+ visibility)
136
+ end
137
+ end
138
+
139
+ Reflexive::Columnizer.columnize(linked_methods, 120)
140
+ end
141
+
142
+ def just_methods_table(klass)
143
+ linked_methods = []
144
+
145
+ ancestors_with_methods = ActiveRecord::Base.ancestors.map do |a|
146
+ [a, a.methods(false).sort] unless a.methods(false).empty?
147
+ end.compact
148
+
149
+ ancestors_with_methods.each do |ancestor, ancestor_methods|
150
+ linked_methods += ancestor_methods.map do |name|
151
+ link_to_method(ancestor.name, name)
152
+ end
153
+ end
154
+
155
+ Reflexive::Columnizer.columnize(linked_methods, 120)
156
+ end
157
+
158
+ def link_to_method(constant, method_name, visibility = nil)
159
+ link_text = truncate(method_name)
160
+ link_to(Rack::Utils.escape_html(link_text),
161
+ method_path(constant, method_name),
162
+ :title => (method_name if link_text.include?("...")))
163
+ end
164
+
165
+ def new_link_to_method(constant, level, method_name)
166
+ link_text = truncate(method_name)
167
+ link_to(Rack::Utils.escape_html(link_text),
168
+ new_method_path(constant, level, method_name),
169
+ :title => (method_name if link_text.include?("...")))
170
+ end
171
+
172
+ ##
173
+ # Truncates a given text after a given :length if text is longer than :length (defaults to 30).
174
+ # The last characters will be replaced with the :omission (defaults to "в_│") for a total length not exceeding :length.
175
+ #
176
+ # ==== Examples
177
+ #
178
+ # truncate("Once upon a time in a world far far away", :length => 8) => "Once upon..."
179
+ #
180
+ def truncate(text, options={})
181
+ options.reverse_merge!(:length => 30, :omission => "...")
182
+ if text
183
+ len = options[:length] - options[:omission].length
184
+ chars = text
185
+ (chars.length > options[:length] ? chars[0...len] + options[:omission] : text).to_s
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,140 @@
1
+ module Reflexive
2
+ class Methods
3
+ def initialize(klass_or_module, options = {})
4
+ @klass_or_module = klass_or_module
5
+ @ancestor_name_formatter = options.fetch(:ancestor_name_formatter,
6
+ default_ancestor_name_formatter)
7
+ @exclude_trite = options.fetch(:exclude_trite, true)
8
+ end
9
+
10
+ def all
11
+ @all ||= find_all
12
+ end
13
+
14
+ def files
15
+ @files ||= find_all_files
16
+ end
17
+
18
+ def constants
19
+ @constants ||= @klass_or_module.constants(true).map do |c|
20
+ @klass_or_module.const_get(c) rescue nil
21
+ end.compact.select do |c|
22
+ c.instance_of?(Class) || c.instance_of?(Module)
23
+ end # rescue r(@klass_or_module.constants)
24
+ end
25
+
26
+ def descendants
27
+ @descendants ||= Reflexive.descendants(@klass_or_module)
28
+ end
29
+
30
+ VISIBILITIES = [ :public, :protected, :private ].freeze
31
+
32
+ protected
33
+
34
+ def each_immediate_class_and_instance_method(&block)
35
+ VISIBILITIES.each do |visibility|
36
+ [ @klass_or_module, @klass_or_module.singleton_class ].each do |klass|
37
+ methods = klass.send("#{ visibility }_instance_methods", false)
38
+ methods.each { |m| block.call(klass.instance_method(m)) }
39
+ end
40
+ end
41
+ end
42
+
43
+ def find_all_files
44
+ source_locations = []
45
+ each_immediate_class_and_instance_method do |meth|
46
+ if location = meth.source_location
47
+ source_locations << location[0] unless source_locations.include?(location[0])
48
+ end
49
+ end
50
+ source_locations
51
+ end
52
+
53
+ def default_ancestor_name_formatter
54
+ proc do |ancestor, singleton|
55
+ ancestor_name(ancestor, singleton)
56
+ end
57
+ end
58
+
59
+ def find_all
60
+ ancestors = [] # flattened ancestors (both normal and singleton)
61
+
62
+ (@klass_or_module.ancestors - trite_ancestors).each do |ancestor|
63
+ ancestor_singleton = ancestor.singleton_class
64
+
65
+ # Modules don't inherit class methods from included modules
66
+ unless @klass_or_module.instance_of?(Module) && ancestor != @klass_or_module
67
+ class_methods = collect_instance_methods(ancestor_singleton)
68
+ end
69
+
70
+ instance_methods = collect_instance_methods(ancestor)
71
+
72
+ append_ancestor_entry(ancestors, @ancestor_name_formatter[ancestor, false],
73
+ class_methods, instance_methods)
74
+
75
+ (singleton_ancestors(ancestor) || []).each do |singleton_ancestor|
76
+ class_methods = collect_instance_methods(singleton_ancestor)
77
+ append_ancestor_entry(ancestors, @ancestor_name_formatter[singleton_ancestor, true],
78
+ class_methods)
79
+ end
80
+ end
81
+
82
+ ancestors
83
+ end
84
+
85
+ # singleton ancestors with ancestor introduced
86
+ def singleton_ancestors(ancestor)
87
+ @singleton_ancestors ||= all_singleton_ancestors
88
+ @singleton_ancestors[ancestor]
89
+ end
90
+
91
+ def all_singleton_ancestors
92
+ all = {}
93
+ seen = []
94
+ (@klass_or_module.ancestors - trite_ancestors).reverse.each do |ancestor|
95
+ singleton_ancestors = ancestor.singleton_class.ancestors - trite_singleton_ancestors
96
+ introduces = singleton_ancestors - seen
97
+ all[ancestor] = introduces unless introduces.empty?
98
+ seen.concat singleton_ancestors
99
+ end
100
+ all
101
+ end
102
+
103
+ def ancestor_name(ancestor, singleton)
104
+ "#{ singleton ? "S" : ""}[#{ ancestor.is_a?(Class) ? "C" : "M" }] #{ ancestor.name || ancestor.to_s }"
105
+ end
106
+
107
+ # ancestor is included only when contributes some methods
108
+ def append_ancestor_entry(ancestors, ancestor, class_methods, instance_methods = nil)
109
+ if class_methods || instance_methods
110
+ ancestor_entry = {}
111
+ ancestor_entry[:class] = class_methods if class_methods
112
+ ancestor_entry[:instance] = instance_methods if instance_methods
113
+ ancestors << {ancestor => ancestor_entry}
114
+ end
115
+ end
116
+
117
+ # Returns hash { :public => [...public methods...],
118
+ # :protected => [...private methods...],
119
+ # :private => [...private methods...] }
120
+ # keys with empty values are excluded,
121
+ # when no methods are found - returns nil
122
+ def collect_instance_methods(klass)
123
+ methods_with_visibility = VISIBILITIES.map do |visibility|
124
+ methods = klass.send("#{ visibility }_instance_methods", false)
125
+ [visibility, methods] unless methods.empty?
126
+ end.compact
127
+ Hash[methods_with_visibility] unless methods_with_visibility.empty?
128
+ end
129
+
130
+ def trite_singleton_ancestors
131
+ return [] unless @exclude_trite
132
+ @trite_singleton_ancestors ||= Class.new.singleton_class.ancestors
133
+ end
134
+
135
+ def trite_ancestors
136
+ return [] unless @exclude_trite
137
+ @trite_ancestors ||= Class.new.ancestors
138
+ end
139
+ end
140
+ end