sleeping_king_studios-tools 0.7.0 → 1.0.2
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +54 -3
- data/CODE_OF_CONDUCT.md +132 -0
- data/DEVELOPMENT.md +7 -16
- data/LICENSE +1 -1
- data/README.md +71 -145
- data/lib/sleeping_king_studios/tools.rb +12 -6
- data/lib/sleeping_king_studios/tools/array_tools.rb +86 -58
- data/lib/sleeping_king_studios/tools/base.rb +20 -0
- data/lib/sleeping_king_studios/tools/core_tools.rb +78 -19
- data/lib/sleeping_king_studios/tools/hash_tools.rb +69 -42
- data/lib/sleeping_king_studios/tools/integer_tools.rb +97 -55
- data/lib/sleeping_king_studios/tools/object_tools.rb +75 -52
- data/lib/sleeping_king_studios/tools/string_tools.rb +69 -96
- data/lib/sleeping_king_studios/tools/toolbelt.rb +44 -23
- data/lib/sleeping_king_studios/tools/toolbox.rb +2 -2
- data/lib/sleeping_king_studios/tools/toolbox/constant_map.rb +75 -74
- data/lib/sleeping_king_studios/tools/toolbox/inflector.rb +124 -0
- data/lib/sleeping_king_studios/tools/toolbox/inflector/rules.rb +173 -0
- data/lib/sleeping_king_studios/tools/toolbox/mixin.rb +11 -11
- data/lib/sleeping_king_studios/tools/toolbox/semantic_version.rb +15 -14
- data/lib/sleeping_king_studios/tools/version.rb +14 -10
- metadata +106 -35
- data/lib/sleeping_king_studios/tools/all.rb +0 -5
- data/lib/sleeping_king_studios/tools/enumerable_tools.rb +0 -8
- data/lib/sleeping_king_studios/tools/semantic_version.rb +0 -15
- data/lib/sleeping_king_studios/tools/string_tools/plural_inflector.rb +0 -185
- data/lib/sleeping_king_studios/tools/toolbox/configuration.rb +0 -207
- data/lib/sleeping_king_studios/tools/toolbox/delegator.rb +0 -175
| @@ -1,12 +1,18 @@ | |
| 1 | 
            -
            #  | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            # Hic iacet Arthurus, rex quondam, rexque futurus.
         | 
| 4 4 | 
             
            module SleepingKingStudios
         | 
| 5 5 | 
             
              # A library of utility services and concerns to expand the functionality of
         | 
| 6 6 | 
             
              # core classes without polluting the global namespace.
         | 
| 7 7 | 
             
              module Tools
         | 
| 8 | 
            -
                autoload : | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 8 | 
            +
                autoload :Base,         'sleeping_king_studios/tools/base'
         | 
| 9 | 
            +
                autoload :ArrayTools,   'sleeping_king_studios/tools/array_tools'
         | 
| 10 | 
            +
                autoload :CoreTools,    'sleeping_king_studios/tools/core_tools'
         | 
| 11 | 
            +
                autoload :HashTools,    'sleeping_king_studios/tools/hash_tools'
         | 
| 12 | 
            +
                autoload :IntegerTools, 'sleeping_king_studios/tools/integer_tools'
         | 
| 13 | 
            +
                autoload :ObjectTools,  'sleeping_king_studios/tools/object_tools'
         | 
| 14 | 
            +
                autoload :StringTools,  'sleeping_king_studios/tools/string_tools'
         | 
| 15 | 
            +
                autoload :Toolbelt,     'sleeping_king_studios/tools/toolbelt'
         | 
| 16 | 
            +
                autoload :Version,      'sleeping_king_studios/tools/version'
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -1,34 +1,48 @@ | |
| 1 | 
            -
            #  | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'sleeping_king_studios/tools'
         | 
| 4 | 
            -
            require 'sleeping_king_studios/tools/object_tools'
         | 
| 5 4 |  | 
| 6 5 | 
             
            module SleepingKingStudios::Tools
         | 
| 7 6 | 
             
              # Tools for working with array-like enumerable objects.
         | 
| 8 | 
            -
               | 
| 9 | 
            -
                 | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
                 | 
| 7 | 
            +
              class ArrayTools < SleepingKingStudios::Tools::Base
         | 
| 8 | 
            +
                # Expected methods that an Array-like object should implement.
         | 
| 9 | 
            +
                ARRAY_METHODS = %i[[] count each].freeze
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                # Methods that an Array-like object should *not* implement.
         | 
| 12 | 
            +
                OTHER_METHODS = %i[each_key each_pair].freeze
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                class << self
         | 
| 15 | 
            +
                  def_delegators :instance,
         | 
| 16 | 
            +
                    :array?,
         | 
| 17 | 
            +
                    :bisect,
         | 
| 18 | 
            +
                    :count_values,
         | 
| 19 | 
            +
                    :deep_dup,
         | 
| 20 | 
            +
                    :deep_freeze,
         | 
| 21 | 
            +
                    :humanize_list,
         | 
| 22 | 
            +
                    :immutable?,
         | 
| 23 | 
            +
                    :mutable?,
         | 
| 24 | 
            +
                    :splice,
         | 
| 25 | 
            +
                    :tally
         | 
| 26 | 
            +
                end
         | 
| 13 27 |  | 
| 14 28 | 
             
                # Returns true if the object is or appears to be an Array.
         | 
| 15 29 | 
             
                #
         | 
| 16 30 | 
             
                # @param ary [Object] The object to test.
         | 
| 17 31 | 
             
                #
         | 
| 18 32 | 
             
                # @return [Boolean] True if the object is an Array, otherwise false.
         | 
| 19 | 
            -
                def array? | 
| 20 | 
            -
                  return true if Array | 
| 33 | 
            +
                def array?(ary)
         | 
| 34 | 
            +
                  return true if ary.is_a?(Array)
         | 
| 21 35 |  | 
| 22 36 | 
             
                  ARRAY_METHODS.each do |method_name|
         | 
| 23 37 | 
             
                    return false unless ary.respond_to?(method_name)
         | 
| 24 | 
            -
                  end | 
| 38 | 
            +
                  end
         | 
| 25 39 |  | 
| 26 40 | 
             
                  OTHER_METHODS.each do |method_name|
         | 
| 27 41 | 
             
                    return false if ary.respond_to?(method_name)
         | 
| 28 | 
            -
                  end | 
| 42 | 
            +
                  end
         | 
| 29 43 |  | 
| 30 44 | 
             
                  true
         | 
| 31 | 
            -
                end | 
| 45 | 
            +
                end
         | 
| 32 46 |  | 
| 33 47 | 
             
                # Separates the array into two arrays, the first containing all items in the
         | 
| 34 48 | 
             
                # original array that matches the provided block, and the second containing
         | 
| @@ -52,19 +66,20 @@ module SleepingKingStudios::Tools | |
| 52 66 | 
             
                #   if no block is given.
         | 
| 53 67 | 
             
                #
         | 
| 54 68 | 
             
                # @return [Array<Array<Object>>] An array containing two arrays.
         | 
| 55 | 
            -
                def bisect | 
| 69 | 
            +
                def bisect(ary)
         | 
| 56 70 | 
             
                  require_array! ary
         | 
| 57 71 |  | 
| 58 | 
            -
                  raise ArgumentError | 
| 72 | 
            +
                  raise ArgumentError, 'no block given' unless block_given?
         | 
| 59 73 |  | 
| 60 | 
            -
                  selected | 
| 74 | 
            +
                  selected = []
         | 
| 75 | 
            +
                  rejected = []
         | 
| 61 76 |  | 
| 62 77 | 
             
                  ary.each do |item|
         | 
| 63 78 | 
             
                    (yield(item) ? selected : rejected) << item
         | 
| 64 | 
            -
                  end | 
| 79 | 
            +
                  end
         | 
| 65 80 |  | 
| 66 81 | 
             
                  [selected, rejected]
         | 
| 67 | 
            -
                end | 
| 82 | 
            +
                end
         | 
| 68 83 |  | 
| 69 84 | 
             
                # @overload count_values(ary)
         | 
| 70 85 | 
             
                #   Counts the number of times each value appears in the enumerable object.
         | 
| @@ -96,15 +111,16 @@ module SleepingKingStudios::Tools | |
| 96 111 | 
             
                #
         | 
| 97 112 | 
             
                #   @return [Hash{Object, Integer}] The number of times each result
         | 
| 98 113 | 
             
                #     appears.
         | 
| 99 | 
            -
                def count_values | 
| 114 | 
            +
                def count_values(ary, &block)
         | 
| 100 115 | 
             
                  require_array! ary
         | 
| 101 116 |  | 
| 102 117 | 
             
                  ary.each.with_object({}) do |item, hsh|
         | 
| 103 118 | 
             
                    value = block_given? ? block.call(item) : item
         | 
| 104 119 |  | 
| 105 120 | 
             
                    hsh[value] = hsh.fetch(value, 0) + 1
         | 
| 106 | 
            -
                  end | 
| 107 | 
            -
                end | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
                alias tally count_values
         | 
| 108 124 |  | 
| 109 125 | 
             
                # Creates a deep copy of the object by returning a new Array with deep
         | 
| 110 126 | 
             
                # copies of each array item.
         | 
| @@ -112,22 +128,22 @@ module SleepingKingStudios::Tools | |
| 112 128 | 
             
                # @param [Array<Object>] ary The array to copy.
         | 
| 113 129 | 
             
                #
         | 
| 114 130 | 
             
                # @return [Array] The copy of the array.
         | 
| 115 | 
            -
                def deep_dup | 
| 131 | 
            +
                def deep_dup(ary)
         | 
| 116 132 | 
             
                  require_array! ary
         | 
| 117 133 |  | 
| 118 134 | 
             
                  ary.map { |obj| ObjectTools.deep_dup obj }
         | 
| 119 | 
            -
                end | 
| 135 | 
            +
                end
         | 
| 120 136 |  | 
| 121 137 | 
             
                # Freezes the array and performs a deep freeze on each array item.
         | 
| 122 138 | 
             
                #
         | 
| 123 139 | 
             
                # @param [Array] ary The object to freeze.
         | 
| 124 | 
            -
                def deep_freeze | 
| 140 | 
            +
                def deep_freeze(ary)
         | 
| 125 141 | 
             
                  require_array! ary
         | 
| 126 142 |  | 
| 127 143 | 
             
                  ary.freeze
         | 
| 128 144 |  | 
| 129 145 | 
             
                  ary.each { |obj| ObjectTools.deep_freeze obj }
         | 
| 130 | 
            -
                end | 
| 146 | 
            +
                end
         | 
| 131 147 |  | 
| 132 148 | 
             
                # Accepts a list of values and returns a human-readable string of the
         | 
| 133 149 | 
             
                # values, with the format based on the number of items.
         | 
| @@ -149,7 +165,10 @@ module SleepingKingStudios::Tools | |
| 149 165 | 
             
                #   #=> 'spam, eggs, bacon, and spam'
         | 
| 150 166 | 
             
                #
         | 
| 151 167 | 
             
                # @example With Three Or More Items And Options
         | 
| 152 | 
            -
                #   ArrayTools.humanize_list( | 
| 168 | 
            +
                #   ArrayTools.humanize_list(
         | 
| 169 | 
            +
                #     ['spam', 'eggs', 'bacon', 'spam'],
         | 
| 170 | 
            +
                #     :last_separator => ' or '
         | 
| 171 | 
            +
                #   )
         | 
| 153 172 | 
             
                #   #=> 'spam, eggs, bacon, or spam'
         | 
| 154 173 | 
             
                #
         | 
| 155 174 | 
             
                # @param [Array<String>] ary The list of values to format. Will be
         | 
| @@ -166,31 +185,23 @@ module SleepingKingStudios::Tools | |
| 166 185 | 
             
                # @raise ArgumentError If the first argument is not an Array-like object.
         | 
| 167 186 | 
             
                #
         | 
| 168 187 | 
             
                # @return [String] The formatted string.
         | 
| 169 | 
            -
                def humanize_list | 
| 188 | 
            +
                def humanize_list(ary, **options, &block)
         | 
| 170 189 | 
             
                  require_array! ary
         | 
| 171 190 |  | 
| 172 | 
            -
                   | 
| 191 | 
            +
                  return '' if ary.empty?
         | 
| 173 192 |  | 
| 174 | 
            -
                   | 
| 175 | 
            -
                   | 
| 193 | 
            +
                  size = ary.size
         | 
| 194 | 
            +
                  ary  = ary.map(&block) if block_given?
         | 
| 176 195 |  | 
| 177 | 
            -
                   | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
                   | 
| 183 | 
            -
                    "#{ary[0]}#{last_separator}#{ary[1]}"
         | 
| 184 | 
            -
                  else
         | 
| 185 | 
            -
                    if last_separator =~ /\A,?\s*/
         | 
| 186 | 
            -
                      last_separator = last_separator.sub /\A,?\s*/, separator
         | 
| 187 | 
            -
                    else
         | 
| 188 | 
            -
                      last_separator = "#{separator}#{last_separator}"
         | 
| 189 | 
            -
                    end # if-else
         | 
| 196 | 
            +
                  return ary[0].to_s if size == 1
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                  separator, last_separator =
         | 
| 199 | 
            +
                    options_for_humanize_list(size: size, **options)
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                  return "#{ary[0]}#{last_separator}#{ary[1]}" if size == 2
         | 
| 190 202 |  | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 193 | 
            -
                end # method humanize_list
         | 
| 203 | 
            +
                  "#{ary[0...-1].join(separator)}#{last_separator}#{ary.last}"
         | 
| 204 | 
            +
                end
         | 
| 194 205 |  | 
| 195 206 | 
             
                # Returns true if the array is immutable, i.e. the array is frozen and each
         | 
| 196 207 | 
             
                # array item is immutable.
         | 
| @@ -200,7 +211,7 @@ module SleepingKingStudios::Tools | |
| 200 211 | 
             
                # @return [Boolean] True if the array is immutable, otherwise false.
         | 
| 201 212 | 
             
                #
         | 
| 202 213 | 
             
                # @see ObjectTools#immutable?
         | 
| 203 | 
            -
                def immutable? | 
| 214 | 
            +
                def immutable?(ary)
         | 
| 204 215 | 
             
                  require_array! ary
         | 
| 205 216 |  | 
| 206 217 | 
             
                  return false unless ary.frozen?
         | 
| @@ -208,7 +219,7 @@ module SleepingKingStudios::Tools | |
| 208 219 | 
             
                  ary.each { |item| return false unless ObjectTools.immutable?(item) }
         | 
| 209 220 |  | 
| 210 221 | 
             
                  true
         | 
| 211 | 
            -
                end | 
| 222 | 
            +
                end
         | 
| 212 223 |  | 
| 213 224 | 
             
                # Returns true if the array is mutable.
         | 
| 214 225 | 
             
                #
         | 
| @@ -217,9 +228,9 @@ module SleepingKingStudios::Tools | |
| 217 228 | 
             
                # @return [Boolean] True if the array is mutable, otherwise false.
         | 
| 218 229 | 
             
                #
         | 
| 219 230 | 
             
                # @see #immutable?
         | 
| 220 | 
            -
                def mutable? | 
| 231 | 
            +
                def mutable?(ary)
         | 
| 221 232 | 
             
                  !immutable?(ary)
         | 
| 222 | 
            -
                end | 
| 233 | 
            +
                end
         | 
| 223 234 |  | 
| 224 235 | 
             
                # Accepts an array, a start value, a number of items to delete, and zero or
         | 
| 225 236 | 
             
                # more items to insert at that index. Deletes the specified number of items,
         | 
| @@ -235,10 +246,10 @@ module SleepingKingStudios::Tools | |
| 235 246 | 
             
                #
         | 
| 236 247 | 
             
                # @example Inserting items into an Array
         | 
| 237 248 | 
             
                #   values = %w(longsword broadsword claymore)
         | 
| 238 | 
            -
                #   ArrayTools.splice values, 1, 0, ' | 
| 249 | 
            +
                #   ArrayTools.splice values, 1, 0, 'zweihander'
         | 
| 239 250 | 
             
                #   #=> []
         | 
| 240 251 | 
             
                #   values
         | 
| 241 | 
            -
                #   #=> ['longsword', ' | 
| 252 | 
            +
                #   #=> ['longsword', 'zweihander', 'broadsword', 'claymore']
         | 
| 242 253 | 
             
                #
         | 
| 243 254 | 
             
                # @example Inserting and deleting items
         | 
| 244 255 | 
             
                #   values = %w(shortbow longbow crossbow)
         | 
| @@ -257,24 +268,41 @@ module SleepingKingStudios::Tools | |
| 257 268 | 
             
                #
         | 
| 258 269 | 
             
                # @return [Array<Object>] The deleted items, or an empty array if no items
         | 
| 259 270 | 
             
                #   were deleted.
         | 
| 260 | 
            -
                def splice | 
| 271 | 
            +
                def splice(ary, start, delete_count, *insert)
         | 
| 261 272 | 
             
                  require_array! ary
         | 
| 262 273 |  | 
| 263 | 
            -
                  start   = start  | 
| 274 | 
            +
                  start   = start.negative? ? start + ary.count : start
         | 
| 264 275 | 
             
                  range   = start...(start + delete_count)
         | 
| 265 276 | 
             
                  deleted = ary[range]
         | 
| 266 277 |  | 
| 267 278 | 
             
                  ary[range] = insert
         | 
| 268 279 |  | 
| 269 280 | 
             
                  deleted
         | 
| 270 | 
            -
                end | 
| 281 | 
            +
                end
         | 
| 271 282 |  | 
| 272 283 | 
             
                private
         | 
| 273 284 |  | 
| 274 | 
            -
                def  | 
| 285 | 
            +
                def options_for_humanize_list(
         | 
| 286 | 
            +
                  size:,
         | 
| 287 | 
            +
                  last_separator: ' and ',
         | 
| 288 | 
            +
                  separator: ', '
         | 
| 289 | 
            +
                )
         | 
| 290 | 
            +
                  return [separator, last_separator] if size < 3
         | 
| 291 | 
            +
             | 
| 292 | 
            +
                  last_separator =
         | 
| 293 | 
            +
                    if last_separator =~ /\A,?\s*/
         | 
| 294 | 
            +
                      last_separator.sub(/\A,?\s*/, separator)
         | 
| 295 | 
            +
                    else
         | 
| 296 | 
            +
                      "#{separator}#{last_separator}"
         | 
| 297 | 
            +
                    end
         | 
| 298 | 
            +
             | 
| 299 | 
            +
                  [separator, last_separator]
         | 
| 300 | 
            +
                end
         | 
| 301 | 
            +
             | 
| 302 | 
            +
                def require_array!(value)
         | 
| 275 303 | 
             
                  return if array?(value)
         | 
| 276 304 |  | 
| 277 305 | 
             
                  raise ArgumentError, 'argument must be an array', caller[1..-1]
         | 
| 278 | 
            -
                end | 
| 279 | 
            -
              end | 
| 280 | 
            -
            end | 
| 306 | 
            +
                end
         | 
| 307 | 
            +
              end
         | 
| 308 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'forwardable'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require 'sleeping_king_studios/tools'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module SleepingKingStudios::Tools
         | 
| 8 | 
            +
              # Abstract base class for Tools classes.
         | 
| 9 | 
            +
              class Base
         | 
| 10 | 
            +
                class << self
         | 
| 11 | 
            +
                  extend Forwardable
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # @return [SleepingKingStudios::Tools::Base] a memoized instance of the
         | 
| 15 | 
            +
                #   tools class.
         | 
| 16 | 
            +
                def self.instance
         | 
| 17 | 
            +
                  @instance ||= new
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -1,11 +1,32 @@ | |
| 1 | 
            -
            #  | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'sleeping_king_studios/tools'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module SleepingKingStudios::Tools
         | 
| 6 6 | 
             
              # Tools for working with an application or working environment.
         | 
| 7 | 
            -
               | 
| 8 | 
            -
                 | 
| 7 | 
            +
              class CoreTools < Base
         | 
| 8 | 
            +
                # Exception class used when deprecated code is called and the deprecation
         | 
| 9 | 
            +
                # strategy is 'raise'.
         | 
| 10 | 
            +
                class DeprecationError < StandardError; end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                class << self
         | 
| 13 | 
            +
                  def_delegators :instance,
         | 
| 14 | 
            +
                    :deprecate,
         | 
| 15 | 
            +
                    :empty_binding,
         | 
| 16 | 
            +
                    :require_each
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                # @param deprecation_strategy [String] The name of the strategy used when
         | 
| 20 | 
            +
                #   deprecated code is called. Must be 'ignore', 'raise', or 'warn'.
         | 
| 21 | 
            +
                def initialize(deprecation_strategy: nil)
         | 
| 22 | 
            +
                  super()
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  @deprecation_strategy =
         | 
| 25 | 
            +
                    deprecation_strategy || ENV.fetch('DEPRECATION_STRATEGY', 'warn')
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                # @return [String] The current deprecation strategy.
         | 
| 29 | 
            +
                attr_reader :deprecation_strategy
         | 
| 9 30 |  | 
| 10 31 | 
             
                # @overload deprecate(name, message: nil)
         | 
| 11 32 | 
             
                #   Prints a deprecation warning.
         | 
| @@ -22,30 +43,68 @@ module SleepingKingStudios::Tools | |
| 22 43 | 
             
                #   @param format [String] The format string.
         | 
| 23 44 | 
             
                #   @param message [String] An optional message to print after the formatted
         | 
| 24 45 | 
             
                #     string. Defaults to nil.
         | 
| 25 | 
            -
                def deprecate | 
| 26 | 
            -
                   | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 46 | 
            +
                def deprecate(*args, format: nil, message: nil)
         | 
| 47 | 
            +
                  send(
         | 
| 48 | 
            +
                    :"deprecate_as_#{deprecation_strategy}",
         | 
| 49 | 
            +
                    *args,
         | 
| 50 | 
            +
                    format:  format,
         | 
| 51 | 
            +
                    message: message
         | 
| 52 | 
            +
                  )
         | 
| 53 | 
            +
                end
         | 
| 30 54 |  | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
                 | 
| 55 | 
            +
                # Generates an empty Binding object with an Object as the receiver.
         | 
| 56 | 
            +
                #
         | 
| 57 | 
            +
                # @return [Binding] The empty binding object.
         | 
| 58 | 
            +
                def empty_binding
         | 
| 59 | 
            +
                  Object.new.instance_exec { binding }
         | 
| 60 | 
            +
                end
         | 
| 35 61 |  | 
| 36 62 | 
             
                # Expands each file pattern and requires each file.
         | 
| 37 63 | 
             
                #
         | 
| 38 64 | 
             
                # @param file_patterns [Array] The files to require.
         | 
| 39 | 
            -
                def require_each | 
| 65 | 
            +
                def require_each(*file_patterns)
         | 
| 40 66 | 
             
                  file_patterns.each do |file_pattern|
         | 
| 41 67 | 
             
                    if file_pattern.include?('*')
         | 
| 42 68 | 
             
                      Dir[file_pattern].each do |file_name|
         | 
| 43 69 | 
             
                        Kernel.require file_name
         | 
| 44 | 
            -
                      end | 
| 70 | 
            +
                      end
         | 
| 45 71 | 
             
                    else
         | 
| 46 72 | 
             
                      Kernel.require file_pattern
         | 
| 47 | 
            -
                    end | 
| 48 | 
            -
                  end | 
| 49 | 
            -
                end | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                private
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def deprecate_as_ignore(*_args, **_kwargs); end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def deprecate_as_raise(*args, format: nil, message: nil)
         | 
| 82 | 
            +
                  format ||= '%s has been deprecated.'
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  str = format % args
         | 
| 85 | 
            +
                  str << ' ' << message if message
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  raise DeprecationError, str, caller(2..-1)
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def deprecate_as_warn(*args, format: nil, message: nil)
         | 
| 91 | 
            +
                  format ||= '[WARNING] %s has been deprecated.'
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  str = format % args
         | 
| 94 | 
            +
                  str << ' ' << message if message
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  str << "\n  called from #{external_caller}"
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  Kernel.warn str
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                def external_caller
         | 
| 102 | 
            +
                  caller.find do |line|
         | 
| 103 | 
            +
                    !(
         | 
| 104 | 
            +
                      line.include?('forwardable.rb') ||
         | 
| 105 | 
            +
                      line.include?('sleeping_king_studios-tools')
         | 
| 106 | 
            +
                    )
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
              end
         | 
| 110 | 
            +
            end
         | 
| @@ -1,47 +1,58 @@ | |
| 1 | 
            -
            #  | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'sleeping_king_studios/tools'
         | 
| 4 | 
            -
            require 'sleeping_king_studios/tools/array_tools'
         | 
| 5 | 
            -
            require 'sleeping_king_studios/tools/object_tools'
         | 
| 6 4 |  | 
| 7 5 | 
             
            module SleepingKingStudios::Tools
         | 
| 8 6 | 
             
              # Tools for working with hash-like enumerable objects.
         | 
| 9 | 
            -
               | 
| 10 | 
            -
                 | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 7 | 
            +
              class HashTools < SleepingKingStudios::Tools::Base
         | 
| 8 | 
            +
                # Expected methods that a Hash-like object should implement.
         | 
| 9 | 
            +
                HASH_METHODS = %i[[] count each each_key each_pair].freeze
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                class << self
         | 
| 12 | 
            +
                  def_delegators :instance,
         | 
| 13 | 
            +
                    :convert_keys_to_strings,
         | 
| 14 | 
            +
                    :convert_keys_to_symbols,
         | 
| 15 | 
            +
                    :deep_dup,
         | 
| 16 | 
            +
                    :deep_freeze,
         | 
| 17 | 
            +
                    :generate_binding,
         | 
| 18 | 
            +
                    :hash?,
         | 
| 19 | 
            +
                    :immutable?,
         | 
| 20 | 
            +
                    :mutable?,
         | 
| 21 | 
            +
                    :stringify_keys,
         | 
| 22 | 
            +
                    :symbolize_keys
         | 
| 23 | 
            +
                end
         | 
| 13 24 |  | 
| 14 25 | 
             
                # Returns a copy of the hash with the keys converted to strings.
         | 
| 15 26 | 
             
                #
         | 
| 16 27 | 
             
                # @param [Hash] hsh The hash to convert.
         | 
| 17 28 | 
             
                #
         | 
| 18 29 | 
             
                # @return [Hash] The converted copy of the hash.
         | 
| 19 | 
            -
                def convert_keys_to_strings | 
| 30 | 
            +
                def convert_keys_to_strings(hsh)
         | 
| 20 31 | 
             
                  require_hash! hsh
         | 
| 21 32 |  | 
| 22 33 | 
             
                  hsh.each.with_object({}) do |(key, value), cpy|
         | 
| 23 34 | 
             
                    sym = key.to_s
         | 
| 24 35 |  | 
| 25 36 | 
             
                    cpy[sym] = convert_value_to_stringified_hash(value)
         | 
| 26 | 
            -
                  end | 
| 27 | 
            -
                end | 
| 28 | 
            -
                 | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
                alias stringify_keys convert_keys_to_strings
         | 
| 29 40 |  | 
| 30 41 | 
             
                # Returns a copy of the hash with the keys converted to symbols.
         | 
| 31 42 | 
             
                #
         | 
| 32 43 | 
             
                # @param [Hash] hsh The hash to convert.
         | 
| 33 44 | 
             
                #
         | 
| 34 45 | 
             
                # @return [Hash] The converted copy of the hash.
         | 
| 35 | 
            -
                def convert_keys_to_symbols | 
| 46 | 
            +
                def convert_keys_to_symbols(hsh)
         | 
| 36 47 | 
             
                  require_hash! hsh
         | 
| 37 48 |  | 
| 38 49 | 
             
                  hsh.each.with_object({}) do |(key, value), cpy|
         | 
| 39 50 | 
             
                    sym = key.to_s.intern
         | 
| 40 51 |  | 
| 41 52 | 
             
                    cpy[sym] = convert_value_to_symbolic_hash(value)
         | 
| 42 | 
            -
                  end | 
| 43 | 
            -
                end | 
| 44 | 
            -
                 | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
                alias symbolize_keys convert_keys_to_symbols
         | 
| 45 56 |  | 
| 46 57 | 
             
                # Creates a deep copy of the object by returning a new Hash with deep
         | 
| 47 58 | 
             
                # copies of each key and value.
         | 
| @@ -49,19 +60,19 @@ module SleepingKingStudios::Tools | |
| 49 60 | 
             
                # @param [Hash<Object>] hsh The hash to copy.
         | 
| 50 61 | 
             
                #
         | 
| 51 62 | 
             
                # @return [Hash] The copy of the hash.
         | 
| 52 | 
            -
                def deep_dup | 
| 63 | 
            +
                def deep_dup(hsh)
         | 
| 53 64 | 
             
                  require_hash! hsh
         | 
| 54 65 |  | 
| 55 | 
            -
                  hsh.each.with_object( | 
| 66 | 
            +
                  hsh.each.with_object({}) do |(key, value), copy|
         | 
| 56 67 | 
             
                    copy[ObjectTools.deep_dup key] = ObjectTools.deep_dup(value)
         | 
| 57 | 
            -
                  end | 
| 58 | 
            -
                end | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
                end
         | 
| 59 70 |  | 
| 60 71 | 
             
                # Freezes the hash and performs a deep freeze on each hash key and
         | 
| 61 72 | 
             
                # value.
         | 
| 62 73 | 
             
                #
         | 
| 63 74 | 
             
                # @param [Hash] hsh The object to freeze.
         | 
| 64 | 
            -
                def deep_freeze | 
| 75 | 
            +
                def deep_freeze(hsh)
         | 
| 65 76 | 
             
                  require_hash! hsh
         | 
| 66 77 |  | 
| 67 78 | 
             
                  hsh.freeze
         | 
| @@ -69,23 +80,37 @@ module SleepingKingStudios::Tools | |
| 69 80 | 
             
                  hsh.each do |key, value|
         | 
| 70 81 | 
             
                    ObjectTools.deep_freeze key
         | 
| 71 82 | 
             
                    ObjectTools.deep_freeze value
         | 
| 72 | 
            -
                  end | 
| 73 | 
            -
                end | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                # Generates a Binding object with an Object as the receiver and the hash
         | 
| 87 | 
            +
                # key-value pairs set as local variables.
         | 
| 88 | 
            +
                #
         | 
| 89 | 
            +
                # @return [Binding] The binding object.
         | 
| 90 | 
            +
                def generate_binding(hsh)
         | 
| 91 | 
            +
                  require_hash! hsh
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  CoreTools.empty_binding.tap do |binding|
         | 
| 94 | 
            +
                    hsh.each do |key, value|
         | 
| 95 | 
            +
                      binding.local_variable_set key, value
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
                end
         | 
| 74 99 |  | 
| 75 100 | 
             
                # Returns true if the object is or appears to be a Hash.
         | 
| 76 101 | 
             
                #
         | 
| 77 102 | 
             
                # @param hsh [Object] The object to test.
         | 
| 78 103 | 
             
                #
         | 
| 79 104 | 
             
                # @return [Boolean] True if the object is a Hash, otherwise false.
         | 
| 80 | 
            -
                def hash? | 
| 81 | 
            -
                  return true if Hash | 
| 105 | 
            +
                def hash?(hsh)
         | 
| 106 | 
            +
                  return true if hsh.is_a?(Hash)
         | 
| 82 107 |  | 
| 83 108 | 
             
                  HASH_METHODS.each do |method_name|
         | 
| 84 109 | 
             
                    return false unless hsh.respond_to?(method_name)
         | 
| 85 | 
            -
                  end | 
| 110 | 
            +
                  end
         | 
| 86 111 |  | 
| 87 112 | 
             
                  true
         | 
| 88 | 
            -
                end | 
| 113 | 
            +
                end
         | 
| 89 114 |  | 
| 90 115 | 
             
                # Returns true if the hash is immutable, i.e. if the hash is frozen and each
         | 
| 91 116 | 
             
                # hash key and hash value are immutable.
         | 
| @@ -93,17 +118,19 @@ module SleepingKingStudios::Tools | |
| 93 118 | 
             
                # @param hsh [Hash] The hash to test.
         | 
| 94 119 | 
             
                #
         | 
| 95 120 | 
             
                # @return [Boolean] True if the hash is immutable, otherwise false.
         | 
| 96 | 
            -
                def immutable? | 
| 121 | 
            +
                def immutable?(hsh)
         | 
| 97 122 | 
             
                  require_hash! hsh
         | 
| 98 123 |  | 
| 99 124 | 
             
                  return false unless hsh.frozen?
         | 
| 100 125 |  | 
| 101 126 | 
             
                  hsh.each do |key, value|
         | 
| 102 | 
            -
                     | 
| 103 | 
            -
             | 
| 127 | 
            +
                    unless ObjectTools.immutable?(key) && ObjectTools.immutable?(value)
         | 
| 128 | 
            +
                      return false
         | 
| 129 | 
            +
                    end
         | 
| 130 | 
            +
                  end
         | 
| 104 131 |  | 
| 105 132 | 
             
                  true
         | 
| 106 | 
            -
                end | 
| 133 | 
            +
                end
         | 
| 107 134 |  | 
| 108 135 | 
             
                # Returns true if the hash is mutable.
         | 
| 109 136 | 
             
                #
         | 
| @@ -112,36 +139,36 @@ module SleepingKingStudios::Tools | |
| 112 139 | 
             
                # @return [Boolean] True if the hash is mutable, otherwise false.
         | 
| 113 140 | 
             
                #
         | 
| 114 141 | 
             
                # @see #immutable?
         | 
| 115 | 
            -
                def mutable? | 
| 142 | 
            +
                def mutable?(hsh)
         | 
| 116 143 | 
             
                  !immutable?(hsh)
         | 
| 117 | 
            -
                end | 
| 144 | 
            +
                end
         | 
| 118 145 |  | 
| 119 146 | 
             
                private
         | 
| 120 147 |  | 
| 121 | 
            -
                def convert_value_to_stringified_hash | 
| 148 | 
            +
                def convert_value_to_stringified_hash(value)
         | 
| 122 149 | 
             
                  if hash?(value)
         | 
| 123 150 | 
             
                    convert_keys_to_strings(value)
         | 
| 124 151 | 
             
                  elsif ArrayTools.array?(value)
         | 
| 125 152 | 
             
                    value.map { |item| convert_value_to_stringified_hash(item) }
         | 
| 126 153 | 
             
                  else
         | 
| 127 154 | 
             
                    value
         | 
| 128 | 
            -
                  end | 
| 129 | 
            -
                end | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
                end
         | 
| 130 157 |  | 
| 131 | 
            -
                def convert_value_to_symbolic_hash | 
| 158 | 
            +
                def convert_value_to_symbolic_hash(value)
         | 
| 132 159 | 
             
                  if hash?(value)
         | 
| 133 160 | 
             
                    convert_keys_to_symbols(value)
         | 
| 134 161 | 
             
                  elsif ArrayTools.array?(value)
         | 
| 135 162 | 
             
                    value.map { |item| convert_value_to_symbolic_hash(item) }
         | 
| 136 163 | 
             
                  else
         | 
| 137 164 | 
             
                    value
         | 
| 138 | 
            -
                  end | 
| 139 | 
            -
                end | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
                end
         | 
| 140 167 |  | 
| 141 | 
            -
                def require_hash! | 
| 168 | 
            +
                def require_hash!(value)
         | 
| 142 169 | 
             
                  return if hash?(value)
         | 
| 143 170 |  | 
| 144 171 | 
             
                  raise ArgumentError, 'argument must be a hash', caller[1..-1]
         | 
| 145 | 
            -
                end | 
| 146 | 
            -
              end | 
| 147 | 
            -
            end | 
| 172 | 
            +
                end
         | 
| 173 | 
            +
              end
         | 
| 174 | 
            +
            end
         |