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