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.
Files changed (29) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +54 -3
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/DEVELOPMENT.md +7 -16
  5. data/LICENSE +1 -1
  6. data/README.md +71 -145
  7. data/lib/sleeping_king_studios/tools.rb +12 -6
  8. data/lib/sleeping_king_studios/tools/array_tools.rb +86 -58
  9. data/lib/sleeping_king_studios/tools/base.rb +20 -0
  10. data/lib/sleeping_king_studios/tools/core_tools.rb +78 -19
  11. data/lib/sleeping_king_studios/tools/hash_tools.rb +69 -42
  12. data/lib/sleeping_king_studios/tools/integer_tools.rb +97 -55
  13. data/lib/sleeping_king_studios/tools/object_tools.rb +75 -52
  14. data/lib/sleeping_king_studios/tools/string_tools.rb +69 -96
  15. data/lib/sleeping_king_studios/tools/toolbelt.rb +44 -23
  16. data/lib/sleeping_king_studios/tools/toolbox.rb +2 -2
  17. data/lib/sleeping_king_studios/tools/toolbox/constant_map.rb +75 -74
  18. data/lib/sleeping_king_studios/tools/toolbox/inflector.rb +124 -0
  19. data/lib/sleeping_king_studios/tools/toolbox/inflector/rules.rb +173 -0
  20. data/lib/sleeping_king_studios/tools/toolbox/mixin.rb +11 -11
  21. data/lib/sleeping_king_studios/tools/toolbox/semantic_version.rb +15 -14
  22. data/lib/sleeping_king_studios/tools/version.rb +14 -10
  23. metadata +106 -35
  24. data/lib/sleeping_king_studios/tools/all.rb +0 -5
  25. data/lib/sleeping_king_studios/tools/enumerable_tools.rb +0 -8
  26. data/lib/sleeping_king_studios/tools/semantic_version.rb +0 -15
  27. data/lib/sleeping_king_studios/tools/string_tools/plural_inflector.rb +0 -185
  28. data/lib/sleeping_king_studios/tools/toolbox/configuration.rb +0 -207
  29. data/lib/sleeping_king_studios/tools/toolbox/delegator.rb +0 -175
@@ -1,34 +1,55 @@
1
- # lib/sleeping_king_studios/tools/toolbelt.rb
1
+ # frozen_string_literal: true
2
2
 
3
- require 'sleeping_king_studios/tools/all'
3
+ require 'sleeping_king_studios/tools'
4
4
 
5
5
  module SleepingKingStudios::Tools
6
6
  # Helper object for quick access to all available tools.
7
7
  class Toolbelt < BasicObject
8
+ # @return [SleepingKingStudios::Tools::Toolbelt] a memoized instance of the
9
+ # toolbelt class.
8
10
  def self.instance
9
11
  @instance ||= new
10
- end # class method instance
12
+ end
11
13
 
12
- namespace = ::SleepingKingStudios::Tools
14
+ # @param deprecation_strategy [String] The name of the strategy used when
15
+ # deprecated code is called. Must be 'ignore', 'raise', or 'warn'.
16
+ # @param inflector [Object] An object that conforms to the interface used
17
+ # by SleepingKingStudios::Tools::Toolbox::Inflector, such as
18
+ # ActiveSupport::Inflector .
19
+ def initialize(deprecation_strategy: nil, inflector: nil)
20
+ @array_tools = ::SleepingKingStudios::Tools::ArrayTools.new
21
+ @core_tools = ::SleepingKingStudios::Tools::CoreTools.new(
22
+ deprecation_strategy: deprecation_strategy
23
+ )
24
+ @hash_tools = ::SleepingKingStudios::Tools::HashTools.new
25
+ @integer_tools = ::SleepingKingStudios::Tools::IntegerTools.new
26
+ @object_tools = ::SleepingKingStudios::Tools::ObjectTools.new
27
+ @string_tools =
28
+ ::SleepingKingStudios::Tools::StringTools.new(inflector: inflector)
29
+ end
13
30
 
14
- %w(array core hash integer object string).each do |name|
15
- define_method(name) do
16
- begin
17
- namespace.const_get("#{name.capitalize}Tools")
18
- rescue NameError => exception
19
- nil
20
- end # begin-rescue
21
- end # each
22
- end # each
31
+ attr_reader :array_tools
23
32
 
33
+ attr_reader :core_tools
34
+
35
+ attr_reader :hash_tools
36
+
37
+ attr_reader :integer_tools
38
+
39
+ attr_reader :object_tools
40
+
41
+ attr_reader :string_tools
42
+
43
+ alias ary array_tools
44
+ alias hsh hash_tools
45
+ alias int integer_tools
46
+ alias obj object_tools
47
+ alias str string_tools
48
+
49
+ # @return [String] a human-readable representation of the object.
24
50
  def inspect
25
- @to_s ||=
26
- begin
27
- object_class = class << self; self; end.superclass
28
-
29
- "#<#{object_class.name}>"
30
- end # string
31
- end # method inspect
32
- alias_method :to_s, :inspect
33
- end # module
34
- end # module
51
+ "#<#{::Object.instance_method(:class).bind(self).call.name}>"
52
+ end
53
+ alias to_s inspect
54
+ end
55
+ end
@@ -1,4 +1,4 @@
1
- # lib/sleeping_king_studios/tools/toolbox.rb
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'sleeping_king_studios/tools'
4
4
 
@@ -7,4 +7,4 @@ module SleepingKingStudios::Tools
7
7
  # are larger than or do not fit the functional paradigm of the tools.*
8
8
  # pattern.
9
9
  module Toolbox; end
10
- end # module
10
+ end
@@ -1,94 +1,95 @@
1
- # lib/sleeping_king_studios/tools/toolbox/constant_map.rb
1
+ # frozen_string_literal: true
2
2
 
3
- require 'sleeping_king_studios/tools/string_tools'
3
+ require 'forwardable'
4
+
5
+ require 'sleeping_king_studios/tools/toolbelt'
4
6
  require 'sleeping_king_studios/tools/toolbox'
5
7
 
6
8
  module SleepingKingStudios::Tools::Toolbox
7
9
  # Provides an enumerable interface for defining a group of constants.
8
- module ConstantMap
9
- class << self
10
- # Creates a new ConstantMap.
11
- #
12
- # @param constants [Hash] The constants to define.
13
- def new constants
14
- mod = Module.new
15
- mod.extend self
16
-
17
- constants.each do |const_name, const_value|
18
- mod.const_set const_name, const_value
19
- end # each
20
-
21
- mod
22
- end # class method new
23
- end # eigenclass
24
-
25
- # Returns a hash with the names and values of the defined constants.
26
- #
27
- # @return [Hash] The defined constants.
28
- def all
29
- constants.each.with_object({}) do |const_name, hsh|
30
- hsh[const_name] = const_get(const_name)
31
- end # each
32
- end # method all
33
-
34
- # Iterates through the defined constants, yielding the name and value of
35
- # each constant to the block.
10
+ class ConstantMap < Module
11
+ extend Forwardable
12
+ include Enumerable
13
+
14
+ # @param constants [Hash] The constants to define.
15
+ def initialize(constants)
16
+ super()
17
+
18
+ @to_h = constants.dup
19
+
20
+ constants.each do |const_name, const_value|
21
+ const_set(const_name, const_value)
22
+
23
+ define_reader(const_name)
24
+ end
25
+ end
26
+
27
+ def_delegators :@to_h,
28
+ :each,
29
+ :each_key,
30
+ :each_pair,
31
+ :each_value,
32
+ :keys,
33
+ :values
34
+
35
+ # @!method each
36
+ # Iterates through the defined constants, yielding the name and value of
37
+ # each constant to the block.
36
38
  #
37
- # @yieldparam key [Symbol] The name of the symbol.
38
- # @yieldparam value [Object] The value of the symbol.
39
- def each &block
40
- all.each(&block)
41
- end # method each
39
+ # @yieldparam key [Symbol] The name of the constant.
40
+ # @yieldparam value [Object] The value of the constant.
42
41
 
43
- # Freezes the constant map and recursively freezes every constant value
44
- # using ObjectTools#deep_freeze. Also pre-emptively defines any reader
45
- # methods that are not already undefined.
42
+ # @!method each_key
43
+ # Iterates through the defined constants, yielding the name of each
44
+ # constant to the block.
46
45
  #
47
- # @see ObjectTools#deep_freeze
48
- def freeze
49
- constants.each do |const_name|
50
- reader_name = const_name.downcase
51
-
52
- define_reader(const_name, reader_name) unless methods.include?(reader_name)
53
-
54
- object_tools.deep_freeze const_get(const_name)
55
- end # each
46
+ # @yieldparam key [Symbol] The name of the constant.
56
47
 
57
- super
58
- end # method freeze
48
+ # @!method each_pair
49
+ # Iterates through the defined constants, yielding the name and value of
50
+ # each constant to the block.
51
+ #
52
+ # @yieldparam key [Symbol] The name of the constant.
53
+ # @yieldparam value [Object] The value of the constant.
59
54
 
60
- private
55
+ # @!method each_value
56
+ # Iterates through the defined constants, yielding the value of each
57
+ # constant to the block.
58
+ #
59
+ # @yieldparam value [Object] The value of the constant.
61
60
 
62
- def define_reader const_name, reader_name = nil
63
- reader_name ||= const_name.downcase
61
+ # @!method keys
62
+ # @return [Array] the names of the defined constants.
64
63
 
65
- define_singleton_method reader_name, ->() { const_get const_name }
66
- end # method define_reader
64
+ # @!method values
65
+ # @return [Array] the values of the defined constants.
67
66
 
68
- def method_missing symbol, *args, &block
69
- const_name = string_tools.underscore(symbol.to_s).upcase.intern
67
+ # @return [Hash] The defined constants.
68
+ attr_reader :to_h
69
+ alias all to_h
70
70
 
71
- if constants.include?(const_name)
72
- define_reader(const_name, symbol)
71
+ # Freezes the constant map and recursively freezes every constant value
72
+ # using ObjectTools#deep_freeze.
73
+ #
74
+ # @see ObjectTools#deep_freeze
75
+ def freeze
76
+ super
73
77
 
74
- return send(symbol, *args, &block)
75
- end # if
78
+ tools.hsh.deep_freeze(@to_h)
76
79
 
77
- super
78
- end # method method_missing
80
+ self
81
+ end
79
82
 
80
- def object_tools
81
- ::SleepingKingStudios::Tools::ObjectTools
82
- end # method object_tools
83
+ private
83
84
 
84
- def respond_to_missing? symbol, include_all = false
85
- const_name = string_tools.underscore(symbol.to_s).upcase.intern
85
+ def define_reader(const_name)
86
+ reader_name ||= tools.str.underscore(const_name.to_s).intern
86
87
 
87
- constants.include?(const_name) || super
88
- end # method respond_to_missing?
88
+ define_singleton_method(reader_name) { const_get const_name }
89
+ end
89
90
 
90
- def string_tools
91
- ::SleepingKingStudios::Tools::StringTools
92
- end # method string_tools
93
- end # module
94
- end # module
91
+ def tools
92
+ ::SleepingKingStudios::Tools::Toolbelt.instance
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require 'sleeping_king_studios/tools/toolbox'
6
+
7
+ module SleepingKingStudios::Tools::Toolbox
8
+ # Transforms words (e.g. from singular to plural).
9
+ #
10
+ # Should maintain the same interface as ActiveSupport::Inflector.
11
+ class Inflector
12
+ extend Forwardable
13
+
14
+ autoload :Rules, 'sleeping_king_studios/tools/toolbox/inflector/rules'
15
+
16
+ def_delegators :@rules,
17
+ :irregular_words,
18
+ :irregular_words_reversed,
19
+ :plural_rules,
20
+ :singular_rules,
21
+ :uncountable_words
22
+
23
+ private \
24
+ :irregular_words,
25
+ :irregular_words_reversed,
26
+ :plural_rules,
27
+ :singular_rules,
28
+ :uncountable_words
29
+
30
+ # @return [Rules] An object defining the transformation rules.
31
+ def initialize(rules: nil)
32
+ @rules = rules || Rules.new
33
+ end
34
+
35
+ # @return [Rules] The defined rules object for the inflector.
36
+ attr_reader :rules
37
+
38
+ # Transforms the word to CamelCase.
39
+ #
40
+ # @param word [String] The word to transform.
41
+ # @param uppercase_first_letter [Boolean] If true, the first letter is
42
+ # capitalized. Defaults to true.
43
+ #
44
+ # @return [String] The word in CamelCase.
45
+ def camelize(word, uppercase_first_letter = true) # rubocop:disable Style/OptionalBooleanParameter
46
+ return '' if word.nil? || word.empty?
47
+
48
+ word = word.to_s.gsub(/(\b|[_-])([a-z])/) { Regexp.last_match(2).upcase }
49
+
50
+ (uppercase_first_letter ? word[0].upcase : word[0].downcase) + word[1..-1]
51
+ end
52
+
53
+ # rubocop:disable Metrics/AbcSize
54
+ # rubocop:disable Metrics/CyclomaticComplexity
55
+
56
+ # Transforms the word to a plural, lowercase form.
57
+ #
58
+ # @param word [String] The word to transform.
59
+ #
60
+ # @return [String] The word in plural form.
61
+ def pluralize(word)
62
+ return '' if word.nil? || word.empty?
63
+
64
+ normalized = word.to_s.strip.downcase
65
+
66
+ return normalized if uncountable_words.include?(normalized)
67
+
68
+ return normalized if irregular_words_reversed.key?(normalized)
69
+
70
+ return irregular_words[normalized] if irregular_words.key?(normalized)
71
+
72
+ plural_rules.each do |match, replace|
73
+ next unless normalized =~ match
74
+
75
+ return normalized.sub(match, replace)
76
+ end
77
+
78
+ word
79
+ end
80
+ # rubocop:enable Metrics/AbcSize
81
+ # rubocop:enable Metrics/CyclomaticComplexity
82
+
83
+ # Transforms the word to a singular, lowercase form.
84
+ #
85
+ # @param word [String] The word to transform.
86
+ #
87
+ # @return [String] The word in singular form.
88
+ def singularize(word) # rubocop:disable Metrics/MethodLength
89
+ return '' if word.nil? || word.empty?
90
+
91
+ normalized = word.to_s.strip.downcase
92
+
93
+ return normalized if irregular_words.key?(normalized)
94
+
95
+ if irregular_words_reversed.key?(normalized)
96
+ return irregular_words_reversed[normalized]
97
+ end
98
+
99
+ singular_rules.each do |match, replace|
100
+ next unless normalized =~ match
101
+
102
+ return normalized.sub(match, replace)
103
+ end
104
+
105
+ word
106
+ end
107
+
108
+ # Transforms the word to a lowercase, underscore-separated form.
109
+ #
110
+ # @param word [String] the word to transform.
111
+ #
112
+ # @return [String] The word in underscored form.
113
+ def underscore(word)
114
+ return '' if word.nil? || word.empty?
115
+
116
+ word = word.to_s.gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
117
+
118
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
119
+ word.tr!('-', '_')
120
+ word.downcase!
121
+ word
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ require 'sleeping_king_studios/tools/toolbox'
6
+
7
+ class SleepingKingStudios::Tools::Toolbox::Inflector
8
+ # Rules for inflecting words.
9
+ class Rules
10
+ # @param irregular_words [Hash<String, String>] Hash of irregular word
11
+ # pairs in singular => plural order, e.g. "child" => "children".
12
+ # @param plural_rules [Array<Array<(Regexp, String)>>] Rules for
13
+ # pluralizing words.
14
+ # @param singular_rules [Array<Array<(Regexp, String)>>] Rules for
15
+ # singularizing words.
16
+ # @param uncountable_words [Array<String>] List of uncountable words,
17
+ # e.g. "data".
18
+ def initialize(
19
+ irregular_words: nil,
20
+ plural_rules: nil,
21
+ singular_rules: nil,
22
+ uncountable_words: nil
23
+ )
24
+ @plural_rules = plural_rules || default_plural_rules
25
+ @singular_rules = singular_rules || default_singular_rules
26
+ @irregular_words = irregular_words || default_irregular_words
27
+ @uncountable_words =
28
+ Set.new(uncountable_words || default_uncountable_words)
29
+
30
+ @irregular_words_reversed = reverse_hash(@irregular_words)
31
+ end
32
+
33
+ # @return [Array<Array<(String, String)>] Hash of irregular word pairs in
34
+ # singular => plural order.
35
+ attr_reader :irregular_words
36
+
37
+ # @return [Array<Array<(String, String)>] Hash of irregular word pairs in
38
+ # plural => singular order.
39
+ attr_reader :irregular_words_reversed
40
+
41
+ # @return [Array<Array<(Regexp, String)>>] Rules for pluralizing words.
42
+ attr_reader :plural_rules
43
+
44
+ # @return [Array<Array<(Regexp, String)>>] Rules for singularizing words.
45
+ attr_reader :singular_rules
46
+
47
+ # @return [Array<String>] List of uncountable words.
48
+ attr_reader :uncountable_words
49
+
50
+ # Defines an irregular word pair.
51
+ #
52
+ # @param singular [String] The singular form of the word.
53
+ # @param plural [String] The plural form of the word.
54
+ #
55
+ # @return [Rules] The rules object.
56
+ def define_irregular_word(singular, plural)
57
+ validate_string(singular)
58
+ validate_string(plural)
59
+
60
+ @irregular_words[singular] = plural
61
+ @irregular_words_reversed[plural] = singular
62
+
63
+ self
64
+ end
65
+
66
+ # Defines a pluralization rule.
67
+ #
68
+ # @param pattern [Regexp] The pattern to match.
69
+ # @param replace [String] The string to replace.
70
+ #
71
+ # @return [Rules] The rules object.
72
+ def define_plural_rule(pattern, replace)
73
+ validate_pattern(pattern)
74
+ validate_string(replace, as: 'replace')
75
+
76
+ @plural_rules.unshift([pattern, replace])
77
+
78
+ self
79
+ end
80
+
81
+ # Defines a singularization rule.
82
+ #
83
+ # @param pattern [Regexp] The pattern to match.
84
+ # @param replace [String] The string to replace.
85
+ #
86
+ # @return [Rules] The rules object.
87
+ def define_singular_rule(pattern, replace)
88
+ validate_pattern(pattern)
89
+ validate_string(replace, as: 'replace')
90
+
91
+ @singular_rules.unshift([pattern, replace])
92
+
93
+ self
94
+ end
95
+
96
+ # Defines an uncountable word.
97
+ #
98
+ # @param word [String] The uncountable word.
99
+ #
100
+ # @return [Rules] The rules object.
101
+ def define_uncountable_word(word)
102
+ validate_string(word)
103
+
104
+ @uncountable_words << word
105
+
106
+ self
107
+ end
108
+
109
+ # @return [String] A human-readable representation of the rules object.
110
+ def inspect
111
+ "#<SleepingKingStudios::Tools::Toolbox::Inflector::Rules:#{object_id}>"
112
+ end
113
+
114
+ private
115
+
116
+ def default_irregular_words
117
+ {
118
+ 'child' => 'children',
119
+ 'person' => 'people'
120
+ }
121
+ end
122
+
123
+ def default_plural_rules
124
+ [
125
+ [/([^aeiouy])y$/i, '\1ies'], # Winery => Wineries
126
+ [/([^aeiouy]o)$/i, '\1es'], # Halo => Haloes
127
+ [/(ss|[xz]|[cs]h)$/i, '\1es'], # Truss => Trusses
128
+ [/s$/i, 's'], # Words => Words
129
+ [/$/, 's'] # Word => Words
130
+ ]
131
+ end
132
+
133
+ def default_singular_rules
134
+ [
135
+ [/([^aeiouy])ies$/i, '\1y'], # Wineries => Winery
136
+ [/([^aeiouy]o)es$/, '\1'], # Haloes => Halo
137
+ [/(ss|[sxz]|[cs]h)es$/, '\1'], # Torches => Torch
138
+ [/ss$/i, 'ss'], # Truss => Truss
139
+ [/s$/i, ''] # Words => Word
140
+ ]
141
+ end
142
+
143
+ def default_uncountable_words
144
+ %w[data]
145
+ end
146
+
147
+ def reverse_hash(hsh)
148
+ hsh.each.with_object({}) do |(key, value), reversed|
149
+ reversed[value] = key
150
+ end
151
+ end
152
+
153
+ def validate_pattern(rxp)
154
+ raise ArgumentError, "pattern can't be blank", caller(1..-1) if rxp.nil?
155
+
156
+ return if rxp.is_a?(Regexp)
157
+
158
+ raise ArgumentError, 'pattern must be a Regexp', caller(1..-1)
159
+ end
160
+
161
+ def validate_string(word, as: 'word')
162
+ raise ArgumentError, "#{as} can't be blank", caller(1..-1) if word.nil?
163
+
164
+ unless word.is_a?(String)
165
+ raise ArgumentError, "#{as} must be a String", caller(1..-1)
166
+ end
167
+
168
+ return unless word.empty?
169
+
170
+ raise ArgumentError, "#{as} can't be blank", caller(1..-1)
171
+ end
172
+ end
173
+ end