1 | # frozen_string_literal: true |
||
2 | # rubocop:disable Documentation |
||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
3 | |||
4 | 1 | require 'date' |
|
5 | 1 | require 'formatador' |
|
6 | 1 | require 'parallel' |
|
7 | 1 | require 'pp' |
|
8 | 1 | require 'stringio' |
|
9 | |||
10 | # Reopen to add utility methods |
||
11 | 1 | module Enumerable |
|
12 | # Enumerate all non-empty prefixes of the enumerable |
||
13 | # @return [Enumerator] |
||
14 | 1 | def prefixes |
|
15 | 4674 | Enumerator.new do |enum| |
|
16 | 4676 | prefix = [] |
|
17 | 4676 | each do |elem| |
|
18 | 3982 | prefix = prefix.dup << elem |
|
19 | 3982 | enum.yield prefix |
|
20 | end |
||
21 | end |
||
22 | end |
||
23 | |||
24 | # Enumerate all partitionings of an enumerable |
||
25 | # @return [Enumerator] |
||
26 | 1 | def partitions(max_length = nil) |
|
27 | 9243 | max_length = length if max_length.nil? |
|
28 | 9243 | Enumerator.new do |enum| |
|
29 | 9244 | 1.upto(max_length).map do |length| |
|
30 | 69796 | enum.yield partition.with_index { |_, i| i < length } |
|
31 | end |
||
32 | end |
||
33 | end |
||
34 | |||
35 | # Take the sum of the result of calling the block on each item |
||
36 | # @return [Object] |
||
37 | 1 | def sum_by(initial = 0) |
|
38 | 98217 | reduce(initial) { |sum, item| sum + yield(item) } |
|
39 | end |
||
40 | |||
41 | # Take the product of the result of calling the block on each item |
||
42 | # @return [Object] |
||
43 | 1 | def product_by(initial = 1) |
|
44 | 37551 | reduce(initial) { |product, item| product * yield(item) } |
|
45 | end |
||
46 | end |
||
47 | |||
48 | # Extend with some convenience methods |
||
49 | 1 | class Array |
|
50 | # Find the longest common prefix of two arrays |
||
51 | # @return [Array<Object>] |
||
52 | 1 | def longest_common_prefix(other) |
|
53 | 1893 | fail TypeError unless other.is_a? Array |
|
54 | 1893 | (prefixes.to_a & other.prefixes.to_a).max_by(&:length) || [] |
|
55 | end |
||
56 | end |
||
57 | |||
58 | # Reopen to present as finite as with Float |
||
59 | 1 | class Integer |
|
60 | # Convenience methods to allow integers to be considered finite |
||
61 | # @return [Boolean] |
||
62 | 1 | def finite? |
|
63 | true |
||
64 | end |
||
65 | end |
||
66 | |||
67 | # Extend Object to print coloured output |
||
68 | 1 | class Object |
|
69 | 1 | def inspect |
|
70 | 100 | Formatador.parse(respond_to?(:to_color) ? to_color : to_s) |
|
71 | end |
||
72 | |||
73 | # Get a colored representation of the object |
||
74 | # @return [String] |
||
75 | 1 | def to_color |
|
76 | 1 | to_s |
|
77 | end |
||
78 | end |
||
79 | |||
80 | # Allow a supertype to look up a class given the |
||
81 | # name of a subtype inheriting from this class |
||
82 | 1 | module Supertype |
|
83 | # Add class methods when this module is included |
||
84 | # @return [void] |
||
85 | 1 | def self.included(base) |
|
86 | 6 | base.extend ClassMethods |
|
87 | end |
||
88 | |||
89 | # Add a single method to get a class given the subtype name |
||
90 | 1 | module ClassMethods |
|
91 | # Get the class given the name of a subtype |
||
92 | # @return [Class] the concrete class with the given subtype name |
||
93 | 1 | def subtype_class(name) |
|
94 | 63 | class_name = self.name.split('::')[0..-2] |
|
95 | class_name << (name.split('_').map do |name_part| |
||
0 ignored issues
–
show
|
|||
96 | 93 | name_part = name_part[0].upcase + name_part[1..-1] |
|
97 | 93 | name_part.sub 'Id', 'ID' |
|
98 | 63 | end.join) |
|
99 | 63 | class_name[-1] = class_name[-1] + self.name.split('::').last |
|
100 | |||
101 | 63 | class_name.reduce(Object) do |mod, name_part| |
|
102 | 194 | mod.const_get(name_part) |
|
103 | end |
||
104 | end |
||
105 | end |
||
106 | end |
||
107 | |||
108 | # Allow subclasses to return a string representing of the |
||
109 | # class, minus a common suffix also used in the superclass |
||
110 | 1 | module Subtype |
|
111 | # Add instance and class methods when this module is included |
||
112 | # @return [void] |
||
113 | 1 | def self.included(base) |
|
114 | 52 | base.send :include, InstanceMethods |
|
115 | 52 | base.extend ClassMethods |
|
116 | end |
||
117 | |||
118 | # Mirror the subtype method on class instances |
||
119 | 1 | module InstanceMethods |
|
120 | # A mirror of {Subtype::ClassMethods#subtype_name} |
||
121 | # @return [String] |
||
122 | 1 | def subtype_name(**args) |
|
123 | 2814 | self.class.subtype_name(**args) |
|
124 | end |
||
125 | end |
||
126 | |||
127 | # Add a single method to retrieve the subtype name |
||
128 | 1 | module ClassMethods |
|
129 | # Get a unique string identify this subclass amongst sibling classes |
||
130 | # @return [String] |
||
131 | 1 | def subtype_name(name_case: :snake) |
|
132 | 2848 | super_name = name_array superclass |
|
133 | 2848 | self_name = name_array self |
|
134 | 2848 | self_name = self_name.reverse.drop_while do |part| |
|
135 | 8423 | super_name.include? part |
|
136 | end.reverse |
||
137 | |||
138 | 2848 | if name_case == :snake |
|
139 | 2847 | name = self_name.join '_' |
|
140 | elsif name_case == :camel |
||
141 | 3 | name = self_name.map { |part| part[0].upcase + part[1..-1] }.join '' |
|
142 | 1 | name.sub! 'Id', 'ID' |
|
143 | end |
||
144 | |||
145 | 2848 | name |
|
146 | end |
||
147 | |||
148 | 1 | private |
|
149 | |||
150 | # Convert camel case class names to an array |
||
151 | # @return [Array<String>] |
||
152 | 1 | def name_array(cls) |
|
153 | cls.name.sub('ID', 'Id').split('::').last.split(/(?=[A-Z]+)/) \ |
||
154 | 5696 | .map(&:downcase) |
|
155 | end |
||
156 | end |
||
157 | end |
||
158 | |||
159 | # Simple helper class to facilitate cardinality estimates |
||
160 | 1 | class Cardinality |
|
161 | # Update the cardinality based on filtering implicit to the index |
||
162 | # @return [Fixnum] |
||
163 | 1 | def self.filter(cardinality, eq_filter, range_filter) |
|
164 | 1909 | filtered = (range_filter.nil? ? 1.0 : 0.1) * cardinality |
|
165 | 1909 | filtered *= eq_filter.map do |field| |
|
166 | 17 | 1.0 / field.cardinality |
|
167 | end.inject(1.0, &:*) |
||
168 | |||
169 | 1909 | filtered |
|
170 | end |
||
171 | end |
||
172 | |||
173 | # Add a simple function for pretty printing strings |
||
174 | 1 | module Kernel |
|
175 | 1 | private |
|
176 | |||
177 | # Pretty print to a string |
||
178 | # @return [String] |
||
179 | 1 | def pp_s(*objs) |
|
180 | s = StringIO.new |
||
181 | objs.each { |obj| PP.pp(obj, s) } |
||
182 | s.rewind |
||
183 | s.read |
||
184 | end |
||
185 | |||
186 | 1 | module_function :pp_s |
|
187 | end |
||
188 | |||
189 | # Add simple convenience methods |
||
190 | 1 | class Object |
|
191 | # Convert all the keys of a hash to symbols |
||
192 | # @return [Object] |
||
193 | 1 | def deep_symbolize_keys |
|
194 | return each_with_object({}) do |(k, v), memo| |
||
195 | 881 | memo[k.to_sym] = v.deep_symbolize_keys |
|
196 | 881 | memo |
|
197 | 1000 | end if is_a? Hash |
|
198 | |||
199 | return each_with_object([]) do |v, memo| |
||
200 | 55 | memo << v.deep_symbolize_keys |
|
201 | 55 | memo |
|
202 | 714 | end if is_a? Array |
|
203 | |||
204 | 659 | self |
|
205 | end |
||
206 | end |
||
207 | |||
208 | # Extend the kernel to allow warning suppression |
||
209 | 1 | module Kernel |
|
210 | # Allow the suppression of warnings for a block of code |
||
211 | # @return [void] |
||
212 | 1 | def suppress_warnings |
|
213 | 15 | original_verbosity = $VERBOSE |
|
214 | 15 | $VERBOSE = nil |
|
215 | 15 | result = yield |
|
216 | 15 | $VERBOSE = original_verbosity |
|
217 | |||
218 | 15 | result |
|
219 | end |
||
220 | end |
||
221 | |||
222 | 1 | module NoSE |
|
223 | # Helper functions for building DSLs |
||
224 | 1 | module DSL |
|
225 | # Add methods to the class which can be used to access entities and fields |
||
226 | # @return [void] |
||
227 | 1 | def mixin_fields(entities, cls) |
|
228 | 2 | entities.each do |entity_name, entity| |
|
229 | # Add fake entity object for the DSL |
||
230 | 14 | fake = Object.new |
|
231 | |||
232 | # Add a method named by the entity to allow field creation |
||
233 | 14 | cls.send :define_method, entity_name.to_sym, (proc do |
|
234 | 16 | metaclass = class << fake; self; end |
|
235 | |||
236 | # Allow fields to be defined using [] access |
||
237 | 8 | metaclass.send :define_method, :[] do |field_name| |
|
238 | 1 | if field_name == '*' |
|
239 | 1 | entity.fields.values |
|
240 | else |
||
241 | entity.fields[field_name] || entity.foreign_keys[field_name] |
||
242 | end |
||
243 | end |
||
244 | |||
245 | # Define methods named for fields so things like 'user.id' work |
||
246 | 8 | entity.fields.merge(entity.foreign_keys).each do |field_name, field| |
|
247 | 105 | metaclass.send :define_method, field_name.to_sym, -> { field } |
|
248 | end |
||
249 | |||
250 | 8 | fake |
|
251 | end) |
||
252 | end |
||
253 | end |
||
254 | |||
255 | 1 | module_function :mixin_fields |
|
256 | end |
||
257 | |||
258 | # Add loading of class instances from the filesystem |
||
259 | 1 | module Loader |
|
260 | 1 | attr_reader :source_code |
|
261 | |||
262 | 1 | def self.included(base) |
|
263 | 3 | base.extend ClassMethods |
|
264 | end |
||
265 | |||
266 | # Add a class method to load class instances from file |
||
267 | 1 | module ClassMethods |
|
268 | # Load a class with the given name from a directory specified |
||
269 | # by the LOAD_PATH class constant |
||
270 | # @return [Object] an instance of the class which included this module |
||
271 | 1 | def load(name) |
|
272 | 24 | path = const_get(:LOAD_PATH) |
|
273 | 24 | filename = File.expand_path "../../../#{path}/#{name}.rb", __FILE__ |
|
274 | 24 | source_code = File.read(filename) |
|
275 | |||
276 | 24 | instance = binding.eval source_code, filename |
|
277 | 24 | instance.instance_variable_set :@source_code, source_code |
|
278 | 24 | instance |
|
279 | end |
||
280 | end |
||
281 | end |
||
282 | end |
||
283 | |||
284 | # Extend Time to allow conversion to DateTime instances |
||
285 | 1 | class Time |
|
286 | # Convert to a DateTime instance |
||
287 | # http://stackoverflow.com/a/279785/123695 |
||
288 | # @return [DateTime] |
||
289 | 1 | def to_datetime |
|
290 | # Convert seconds + microseconds into a fractional number of seconds |
||
291 | seconds = sec + Rational(usec, 10**6) |
||
292 | |||
293 | # Convert a UTC offset measured in minutes to one measured in a |
||
294 | # fraction of a day. |
||
295 | offset = Rational(utc_offset, 60 * 60 * 24) |
||
296 | DateTime.new(year, month, day, hour, min, seconds, offset) |
||
297 | end |
||
298 | end |
||
299 | |||
300 | # rubocop:enable Documentation |
||
301 |