Completed
Push — master ( f86927...27dfdc )
by Michael
03:07
created

lib/nose/util.rb (2 issues)

1
# frozen_string_literal: true
2
# rubocop:disable Documentation
0 ignored issues
show
Unnecessary disabling of Style/Documentation.
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
Don't use parentheses around a method call.
Loading history...
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