reflexive 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.
@@ -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