sleeping_king_studios-tools 0.7.0.rc.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +46 -5
  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 +171 -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 +15 -11
  23. metadata +105 -36
  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,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sleeping_king_studios/tools/toolbox'
4
+
5
+ class SleepingKingStudios::Tools::Toolbox::Inflector
6
+ # Rules for inflecting words.
7
+ class Rules
8
+ # @param irregular_words [Hash<String, String>] Hash of irregular word
9
+ # pairs in singular => plural order, e.g. "child" => "children".
10
+ # @param plural_rules [Array<Array<(Regexp, String)>>] Rules for
11
+ # pluralizing words.
12
+ # @param singular_rules [Array<Array<(Regexp, String)>>] Rules for
13
+ # singularizing words.
14
+ # @param uncountable_words [Array<String>] List of uncountable words,
15
+ # e.g. "data".
16
+ def initialize(
17
+ irregular_words: nil,
18
+ plural_rules: nil,
19
+ singular_rules: nil,
20
+ uncountable_words: nil
21
+ )
22
+ @plural_rules = plural_rules || default_plural_rules
23
+ @singular_rules = singular_rules || default_singular_rules
24
+ @irregular_words = irregular_words || default_irregular_words
25
+ @uncountable_words =
26
+ Set.new(uncountable_words || default_uncountable_words)
27
+
28
+ @irregular_words_reversed = reverse_hash(@irregular_words)
29
+ end
30
+
31
+ # @return [Array<Array<(String, String)>] Hash of irregular word pairs in
32
+ # singular => plural order.
33
+ attr_reader :irregular_words
34
+
35
+ # @return [Array<Array<(String, String)>] Hash of irregular word pairs in
36
+ # plural => singular order.
37
+ attr_reader :irregular_words_reversed
38
+
39
+ # @return [Array<Array<(Regexp, String)>>] Rules for pluralizing words.
40
+ attr_reader :plural_rules
41
+
42
+ # @return [Array<Array<(Regexp, String)>>] Rules for singularizing words.
43
+ attr_reader :singular_rules
44
+
45
+ # @return [Array<String>] List of uncountable words.
46
+ attr_reader :uncountable_words
47
+
48
+ # Defines an irregular word pair.
49
+ #
50
+ # @param singular [String] The singular form of the word.
51
+ # @param plural [String] The plural form of the word.
52
+ #
53
+ # @return [Rules] The rules object.
54
+ def define_irregular_word(singular, plural)
55
+ validate_string(singular)
56
+ validate_string(plural)
57
+
58
+ @irregular_words[singular] = plural
59
+ @irregular_words_reversed[plural] = singular
60
+
61
+ self
62
+ end
63
+
64
+ # Defines a pluralization rule.
65
+ #
66
+ # @param pattern [Regexp] The pattern to match.
67
+ # @param replace [String] The string to replace.
68
+ #
69
+ # @return [Rules] The rules object.
70
+ def define_plural_rule(pattern, replace)
71
+ validate_pattern(pattern)
72
+ validate_string(replace, as: 'replace')
73
+
74
+ @plural_rules.unshift([pattern, replace])
75
+
76
+ self
77
+ end
78
+
79
+ # Defines a singularization rule.
80
+ #
81
+ # @param pattern [Regexp] The pattern to match.
82
+ # @param replace [String] The string to replace.
83
+ #
84
+ # @return [Rules] The rules object.
85
+ def define_singular_rule(pattern, replace)
86
+ validate_pattern(pattern)
87
+ validate_string(replace, as: 'replace')
88
+
89
+ @singular_rules.unshift([pattern, replace])
90
+
91
+ self
92
+ end
93
+
94
+ # Defines an uncountable word.
95
+ #
96
+ # @param word [String] The uncountable word.
97
+ #
98
+ # @return [Rules] The rules object.
99
+ def define_uncountable_word(word)
100
+ validate_string(word)
101
+
102
+ @uncountable_words << word
103
+
104
+ self
105
+ end
106
+
107
+ # @return [String] A human-readable representation of the rules object.
108
+ def inspect
109
+ "#<SleepingKingStudios::Tools::Toolbox::Inflector::Rules:#{object_id}>"
110
+ end
111
+
112
+ private
113
+
114
+ def default_irregular_words
115
+ {
116
+ 'child' => 'children',
117
+ 'person' => 'people'
118
+ }
119
+ end
120
+
121
+ def default_plural_rules
122
+ [
123
+ [/([^aeiouy])y$/i, '\1ies'], # Winery => Wineries
124
+ [/([^aeiouy]o)$/i, '\1es'], # Halo => Haloes
125
+ [/(ss|[xz]|[cs]h)$/i, '\1es'], # Truss => Trusses
126
+ [/s$/i, 's'], # Words => Words
127
+ [/$/, 's'] # Word => Words
128
+ ]
129
+ end
130
+
131
+ def default_singular_rules
132
+ [
133
+ [/([^aeiouy])ies$/i, '\1y'], # Wineries => Winery
134
+ [/([^aeiouy]o)es$/, '\1'], # Haloes => Halo
135
+ [/(ss|[sxz]|[cs]h)es$/, '\1'], # Torches => Torch
136
+ [/ss$/i, 'ss'], # Truss => Truss
137
+ [/s$/i, ''] # Words => Word
138
+ ]
139
+ end
140
+
141
+ def default_uncountable_words
142
+ %w[data]
143
+ end
144
+
145
+ def reverse_hash(hsh)
146
+ hsh.each.with_object({}) do |(key, value), reversed|
147
+ reversed[value] = key
148
+ end
149
+ end
150
+
151
+ def validate_pattern(rxp)
152
+ raise ArgumentError, "pattern can't be blank", caller(1..-1) if rxp.nil?
153
+
154
+ return if rxp.is_a?(Regexp)
155
+
156
+ raise ArgumentError, 'pattern must be a Regexp', caller(1..-1)
157
+ end
158
+
159
+ def validate_string(word, as: 'word')
160
+ raise ArgumentError, "#{as} can't be blank", caller(1..-1) if word.nil?
161
+
162
+ unless word.is_a?(String)
163
+ raise ArgumentError, "#{as} must be a String", caller(1..-1)
164
+ end
165
+
166
+ return unless word.empty?
167
+
168
+ raise ArgumentError, "#{as} can't be blank", caller(1..-1)
169
+ end
170
+ end
171
+ end