Issues (393)

lib/nose/util.rb (1 issue)

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 }
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
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)
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]
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]
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|
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|
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)
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
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
Replace class var :@@registry with a class instance var.
Loading history...
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)
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