hanami-utils 0.7.2 → 0.8.0
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 +4 -4
- data/CHANGELOG.md +15 -2
- data/README.md +5 -1
- data/hanami-utils.gemspec +5 -6
- data/lib/hanami-utils.rb +1 -1
- data/lib/hanami/interactor.rb +7 -5
- data/lib/hanami/logger.rb +178 -30
- data/lib/hanami/utils/attributes.rb +11 -2
- data/lib/hanami/utils/basic_object.rb +4 -3
- data/lib/hanami/utils/blank.rb +45 -0
- data/lib/hanami/utils/callbacks.rb +1 -3
- data/lib/hanami/utils/class.rb +34 -3
- data/lib/hanami/utils/class_attribute.rb +5 -1
- data/lib/hanami/utils/deprecation.rb +2 -1
- data/lib/hanami/utils/duplicable.rb +2 -2
- data/lib/hanami/utils/escape.rb +53 -52
- data/lib/hanami/utils/hash.rb +9 -9
- data/lib/hanami/utils/inflector.rb +84 -62
- data/lib/hanami/utils/io.rb +2 -2
- data/lib/hanami/utils/json.rb +51 -0
- data/lib/hanami/utils/kernel.rb +12 -11
- data/lib/hanami/utils/load_paths.rb +4 -2
- data/lib/hanami/utils/path_prefix.rb +4 -3
- data/lib/hanami/utils/string.rb +12 -11
- data/lib/hanami/utils/version.rb +1 -1
- metadata +7 -20
@@ -10,7 +10,7 @@ module Hanami
|
|
10
10
|
#
|
11
11
|
# @see http://ruby-doc.org/core/Object.html#method-i-class
|
12
12
|
def class
|
13
|
-
(class << self; self end).superclass
|
13
|
+
(class << self; self; end).superclass
|
14
14
|
end
|
15
15
|
|
16
16
|
# Bare minimum inspect for debugging purposes.
|
@@ -21,7 +21,7 @@ module Hanami
|
|
21
21
|
#
|
22
22
|
# @see http://ruby-doc.org/core/Object.html#method-i-inspect
|
23
23
|
def inspect
|
24
|
-
"#<#{
|
24
|
+
"#<#{self.class}:#{'%x' % (__id__ << 1)}#{__inspect}>" # rubocop:disable Style/FormatString
|
25
25
|
end
|
26
26
|
|
27
27
|
# Returns true if responds to the given method.
|
@@ -36,11 +36,12 @@ module Hanami
|
|
36
36
|
end
|
37
37
|
|
38
38
|
private
|
39
|
+
|
39
40
|
# Must be overridden by descendants
|
40
41
|
#
|
41
42
|
# @since 0.3.5
|
42
43
|
# @api private
|
43
|
-
def respond_to_missing?(
|
44
|
+
def respond_to_missing?(_method_name, _include_all)
|
44
45
|
::Kernel.raise ::NotImplementedError
|
45
46
|
end
|
46
47
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Utils
|
3
|
+
# Checks for blank
|
4
|
+
# @since 0.8.0
|
5
|
+
class Blank
|
6
|
+
# Matcher for blank strings
|
7
|
+
#
|
8
|
+
# @since 0.8.0
|
9
|
+
# @api private
|
10
|
+
STRING_MATCHER = /\A[[:space:]]*\z/
|
11
|
+
|
12
|
+
# Checks object is blank
|
13
|
+
#
|
14
|
+
# @example Basic Usage
|
15
|
+
# require 'hanami/utils/blank'
|
16
|
+
#
|
17
|
+
# Hanami::Utils::Blank.blank?(Hanami::Utils::String.new('')) # => true
|
18
|
+
# Hanami::Utils::Blank.blank?(' ') # => true
|
19
|
+
# Hanami::Utils::Blank.blank?(nil) # => true
|
20
|
+
# Hanami::Utils::Blank.blank?(Hanami::Utils::Hash.new({})) # => true
|
21
|
+
# Hanami::Utils::Blank.blank?(true) # => false
|
22
|
+
# Hanami::Utils::Blank.blank?(1) # => false
|
23
|
+
#
|
24
|
+
# @param object the argument
|
25
|
+
#
|
26
|
+
# @return [TrueClass,FalseClass]
|
27
|
+
#
|
28
|
+
# @since 0.8.0
|
29
|
+
def self.blank?(object) # rubocop:disable Metrics/MethodLength
|
30
|
+
case object
|
31
|
+
when String, ::String
|
32
|
+
STRING_MATCHER === object # rubocop:disable Style/CaseEquality
|
33
|
+
when Hash, ::Hash, ::Array
|
34
|
+
object.empty?
|
35
|
+
when TrueClass, Numeric
|
36
|
+
false
|
37
|
+
when FalseClass, NilClass
|
38
|
+
true
|
39
|
+
else
|
40
|
+
object.respond_to?(:empty?) ? !!object.empty? : !self
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -16,7 +16,7 @@ module Hanami
|
|
16
16
|
#
|
17
17
|
# @since 0.2.0
|
18
18
|
def initialize
|
19
|
-
@chain =
|
19
|
+
@chain = []
|
20
20
|
end
|
21
21
|
|
22
22
|
# Appends the given callbacks to the end of the chain.
|
@@ -168,14 +168,12 @@ module Hanami
|
|
168
168
|
@chain.freeze
|
169
169
|
end
|
170
170
|
|
171
|
-
|
172
171
|
private
|
173
172
|
|
174
173
|
def callables(callbacks, block)
|
175
174
|
callbacks.push(block) if block
|
176
175
|
callbacks.map { |c| Factory.fabricate(c) }
|
177
176
|
end
|
178
|
-
|
179
177
|
end
|
180
178
|
|
181
179
|
# Callback factory
|
data/lib/hanami/utils/class.rb
CHANGED
@@ -41,6 +41,37 @@ module Hanami
|
|
41
41
|
namespace.const_get(name.to_s)
|
42
42
|
end
|
43
43
|
|
44
|
+
# Loads a class for the given name, only if it's defined.
|
45
|
+
#
|
46
|
+
# @param name [String, Class] the specific class name
|
47
|
+
# @param namespace [Class, Module] the Ruby namespace where we want to perform the lookup.
|
48
|
+
# @return [Class, Module, NilClass] the Ruby constant, or nil if not found.
|
49
|
+
#
|
50
|
+
# @since 0.8.0
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# require 'hanami/utils/class'
|
54
|
+
#
|
55
|
+
# module App
|
56
|
+
# module Service
|
57
|
+
# class Endpoint
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# class ServiceEndpoint
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# # basic usage
|
66
|
+
# Hanami::Utils::Class.load('App::Service') # => App::Service
|
67
|
+
# Hanami::Utils::Class.load(App::Service) # => App::Service
|
68
|
+
#
|
69
|
+
# # with explicit namespace
|
70
|
+
# Hanami::Utils::Class.load('Service', App) # => App::Service
|
71
|
+
def self.load(name, namespace = Object)
|
72
|
+
load!(name, namespace) if namespace.const_defined?(name.to_s)
|
73
|
+
end
|
74
|
+
|
44
75
|
# Loads a class from the given pattern name and namespace
|
45
76
|
#
|
46
77
|
# @param pattern [String] the class name pattern
|
@@ -82,12 +113,12 @@ module Hanami
|
|
82
113
|
String.new(pattern).tokenize do |token|
|
83
114
|
begin
|
84
115
|
return namespace.const_get(token)
|
85
|
-
rescue NameError
|
116
|
+
rescue NameError # rubocop:disable Lint/HandleExceptions
|
86
117
|
end
|
87
118
|
end
|
88
119
|
|
89
|
-
full_name = [
|
90
|
-
raise NameError.new("uninitialized constant #{
|
120
|
+
full_name = [(namespace == Object ? nil : namespace), pattern].compact.join('::')
|
121
|
+
raise NameError.new("uninitialized constant #{full_name}")
|
91
122
|
end
|
92
123
|
end
|
93
124
|
end
|
@@ -12,6 +12,8 @@ module Hanami
|
|
12
12
|
base.extend ClassMethods
|
13
13
|
end
|
14
14
|
|
15
|
+
# @since 0.1.0
|
16
|
+
# @api private
|
15
17
|
module ClassMethods
|
16
18
|
# Defines a class level accessor for the given attribute(s).
|
17
19
|
#
|
@@ -64,13 +66,14 @@ module Hanami
|
|
64
66
|
# SmallAirplane.wheels # => 8
|
65
67
|
def class_attribute(*attributes)
|
66
68
|
singleton_class.class_eval do
|
67
|
-
attr_accessor
|
69
|
+
attr_accessor(*attributes)
|
68
70
|
end
|
69
71
|
|
70
72
|
class_attributes.merge(attributes)
|
71
73
|
end
|
72
74
|
|
73
75
|
protected
|
76
|
+
|
74
77
|
# @see Class#inherited
|
75
78
|
def inherited(subclass)
|
76
79
|
class_attributes.each do |attr|
|
@@ -84,6 +87,7 @@ module Hanami
|
|
84
87
|
end
|
85
88
|
|
86
89
|
private
|
90
|
+
|
87
91
|
# Class accessor for class attributes.
|
88
92
|
# @private
|
89
93
|
def class_attributes
|
@@ -58,10 +58,11 @@ module Hanami
|
|
58
58
|
# # => old_method is deprecated, please use new_method - called from: test.rb:20:in `start'.
|
59
59
|
# # => started
|
60
60
|
def initialize(message)
|
61
|
-
::Kernel.warn("#{
|
61
|
+
::Kernel.warn("#{message} - called from: #{caller[caller_index]}.")
|
62
62
|
end
|
63
63
|
|
64
64
|
private
|
65
|
+
|
65
66
|
def caller_index
|
66
67
|
Utils.jruby? || Utils.rubinius? ? 1 : 2
|
67
68
|
end
|
@@ -45,7 +45,7 @@ module Hanami
|
|
45
45
|
# result = Hanami::Utils::Duplicable.dup(object)
|
46
46
|
#
|
47
47
|
# puts result # => "hello"
|
48
|
-
# puts result.object_id # => 70172671467020
|
48
|
+
# puts result.object_id # => 70172671467020 - Different object
|
49
49
|
#
|
50
50
|
# @example Custom Logic
|
51
51
|
# require 'hanami/utils/duplicable'
|
@@ -64,7 +64,7 @@ module Hanami
|
|
64
64
|
# end
|
65
65
|
#
|
66
66
|
# puts result # => "{:a=>1}"
|
67
|
-
# puts result.object_id # => 70207105185500
|
67
|
+
# puts result.object_id # => 70207105185500 - Different object
|
68
68
|
def self.dup(value, &blk)
|
69
69
|
case value
|
70
70
|
when NilClass, FalseClass, TrueClass, Symbol, Numeric
|
data/lib/hanami/utils/escape.rb
CHANGED
@@ -11,7 +11,7 @@ module Hanami
|
|
11
11
|
# @see https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
|
12
12
|
# @see https://www.owasp.org/index.php/ESAPI
|
13
13
|
# @see https://github.com/ESAPI/esapi-java-legacy
|
14
|
-
module Escape
|
14
|
+
module Escape # rubocop:disable Metrics/ModuleLength
|
15
15
|
# Hex base for base 10 integer conversion
|
16
16
|
#
|
17
17
|
# @since 0.4.0
|
@@ -30,14 +30,14 @@ module Hanami
|
|
30
30
|
#
|
31
31
|
# @since 0.4.0
|
32
32
|
# @api private
|
33
|
-
REPLACEMENT_HEX
|
33
|
+
REPLACEMENT_HEX = 'fffd'.freeze
|
34
34
|
|
35
35
|
# Low hex codes lookup table
|
36
36
|
#
|
37
37
|
# @since 0.4.0
|
38
38
|
# @api private
|
39
39
|
HEX_CODES = (0..255).each_with_object({}) do |c, codes|
|
40
|
-
if (c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A)
|
40
|
+
if (c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A) # rubocop:disable Style/ConditionalAssignment
|
41
41
|
codes[c] = nil
|
42
42
|
else
|
43
43
|
codes[c] = c.to_s(HEX_BASE)
|
@@ -362,7 +362,7 @@ module Hanami
|
|
362
362
|
# @api private
|
363
363
|
#
|
364
364
|
# @see Hanami::Utils::Escape.url
|
365
|
-
DEFAULT_URL_SCHEMES =
|
365
|
+
DEFAULT_URL_SCHEMES = %w(http https mailto).freeze
|
366
366
|
|
367
367
|
# The output of an escape.
|
368
368
|
#
|
@@ -511,65 +511,66 @@ module Hanami
|
|
511
511
|
return input if input.is_a?(SafeString)
|
512
512
|
|
513
513
|
SafeString.new(
|
514
|
-
URI.extract(
|
515
|
-
URI.
|
514
|
+
URI::Parser.new.extract(
|
515
|
+
URI.decode_www_form_component(input),
|
516
516
|
schemes
|
517
517
|
).first.to_s
|
518
518
|
)
|
519
519
|
end
|
520
520
|
|
521
|
-
|
522
|
-
|
523
|
-
#
|
524
|
-
# @param input [String] the input
|
525
|
-
#
|
526
|
-
# @return [String] an UTF-8 encoded string
|
527
|
-
#
|
528
|
-
# @since 0.4.0
|
529
|
-
# @api private
|
530
|
-
def self.encode(input)
|
531
|
-
return '' if input.nil?
|
532
|
-
input.to_s.encode(Encoding::UTF_8)
|
533
|
-
rescue Encoding::UndefinedConversionError
|
534
|
-
input.dup.force_encoding(Encoding::UTF_8)
|
535
|
-
end
|
521
|
+
class << self
|
522
|
+
private
|
536
523
|
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
524
|
+
# Encode the given string into UTF-8
|
525
|
+
#
|
526
|
+
# @param input [String] the input
|
527
|
+
#
|
528
|
+
# @return [String] an UTF-8 encoded string
|
529
|
+
#
|
530
|
+
# @since 0.4.0
|
531
|
+
# @api private
|
532
|
+
def encode(input)
|
533
|
+
return '' if input.nil?
|
534
|
+
input.to_s.encode(Encoding::UTF_8)
|
535
|
+
rescue Encoding::UndefinedConversionError
|
536
|
+
input.dup.force_encoding(Encoding::UTF_8)
|
537
|
+
end
|
548
538
|
|
549
|
-
|
550
|
-
|
551
|
-
|
539
|
+
# Encode the given UTF-8 char.
|
540
|
+
#
|
541
|
+
# @param char [String] an UTF-8 char
|
542
|
+
# @param safe_chars [Hash] a table of safe chars
|
543
|
+
#
|
544
|
+
# @return [String] an HTML encoded string
|
545
|
+
#
|
546
|
+
# @since 0.4.0
|
547
|
+
# @api private
|
548
|
+
def encode_char(char, safe_chars = {})
|
549
|
+
return char if safe_chars[char]
|
552
550
|
|
553
|
-
|
554
|
-
hex
|
555
|
-
|
551
|
+
code = char.ord
|
552
|
+
hex = hex_for_non_alphanumeric_code(code)
|
553
|
+
return char if hex.nil?
|
556
554
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
555
|
+
hex = REPLACEMENT_HEX if NON_PRINTABLE_CHARS[code]
|
556
|
+
|
557
|
+
if entity = HTML_ENTITIES[code] # rubocop:disable Lint/AssignmentInCondition
|
558
|
+
"&#{entity};"
|
559
|
+
else
|
560
|
+
"&#x#{hex};"
|
561
|
+
end
|
561
562
|
end
|
562
|
-
end
|
563
563
|
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
564
|
+
# Transforms the given char code
|
565
|
+
#
|
566
|
+
# @since 0.4.0
|
567
|
+
# @api private
|
568
|
+
def hex_for_non_alphanumeric_code(input)
|
569
|
+
if input < LOW_HEX_CODE_LIMIT
|
570
|
+
HEX_CODES[input]
|
571
|
+
else
|
572
|
+
input.to_s(HEX_BASE)
|
573
|
+
end
|
573
574
|
end
|
574
575
|
end
|
575
576
|
end
|
data/lib/hanami/utils/hash.rb
CHANGED
@@ -10,7 +10,7 @@ module Hanami
|
|
10
10
|
#
|
11
11
|
# @see Hanami::Utils::Hash#deep_dup
|
12
12
|
# @see Hanami::Utils::Duplicable
|
13
|
-
DUPLICATE_LOGIC =
|
13
|
+
DUPLICATE_LOGIC = proc do |value|
|
14
14
|
case value
|
15
15
|
when Hash
|
16
16
|
value.deep_dup
|
@@ -65,7 +65,7 @@ module Hanami
|
|
65
65
|
def symbolize!
|
66
66
|
keys.each do |k|
|
67
67
|
v = delete(k)
|
68
|
-
v =
|
68
|
+
v = self.class.new(v).symbolize! if v.is_a?(::Hash) || v.is_a?(self.class)
|
69
69
|
|
70
70
|
self[k.to_sym] = v
|
71
71
|
end
|
@@ -90,7 +90,7 @@ module Hanami
|
|
90
90
|
def stringify!
|
91
91
|
keys.each do |k|
|
92
92
|
v = delete(k)
|
93
|
-
v =
|
93
|
+
v = self.class.new(v).stringify! if v.is_a?(::Hash) || v.is_a?(self.class)
|
94
94
|
|
95
95
|
self[k.to_s] = v
|
96
96
|
end
|
@@ -160,8 +160,8 @@ module Hanami
|
|
160
160
|
# # it deeply duplicates Hanami::Utils::Hash, by preserving the class
|
161
161
|
# duped['u_hash'].class # => Hanami::Utils::Hash
|
162
162
|
def deep_dup
|
163
|
-
|
164
|
-
@hash.each {|k, v| result[k] = Duplicable.dup(v, &DUPLICATE_LOGIC) }
|
163
|
+
self.class.new.tap do |result|
|
164
|
+
@hash.each { |k, v| result[k] = Duplicable.dup(v, &DUPLICATE_LOGIC) }
|
165
165
|
end
|
166
166
|
end
|
167
167
|
|
@@ -229,7 +229,7 @@ module Hanami
|
|
229
229
|
end
|
230
230
|
end
|
231
231
|
|
232
|
-
|
232
|
+
alias to_hash to_h
|
233
233
|
|
234
234
|
# Converts into a nested array of [ key, value ] arrays.
|
235
235
|
#
|
@@ -251,7 +251,7 @@ module Hanami
|
|
251
251
|
@hash == other.to_h
|
252
252
|
end
|
253
253
|
|
254
|
-
|
254
|
+
alias eql? ==
|
255
255
|
|
256
256
|
# Returns the hash of the internal @hash
|
257
257
|
#
|
@@ -283,7 +283,7 @@ module Hanami
|
|
283
283
|
h = self.class.new(h) if h.is_a?(::Hash)
|
284
284
|
h
|
285
285
|
else
|
286
|
-
raise NoMethodError.new(%(undefined method `#{
|
286
|
+
raise NoMethodError.new(%(undefined method `#{m}' for #{@hash}:#{self.class}))
|
287
287
|
end
|
288
288
|
end
|
289
289
|
|
@@ -291,7 +291,7 @@ module Hanami
|
|
291
291
|
#
|
292
292
|
# @api private
|
293
293
|
# @since 0.3.0
|
294
|
-
def respond_to_missing?(m, include_private=false)
|
294
|
+
def respond_to_missing?(m, include_private = false)
|
295
295
|
@hash.respond_to?(m, include_private)
|
296
296
|
end
|
297
297
|
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
require 'hanami/utils/class_attribute'
|
2
|
+
require 'hanami/utils/blank'
|
2
3
|
|
3
4
|
module Hanami
|
4
5
|
module Utils
|
5
6
|
# String inflector
|
6
7
|
#
|
7
8
|
# @since 0.4.1
|
8
|
-
|
9
|
+
#
|
10
|
+
# rubocop:disable Style/PerlBackrefs
|
11
|
+
module Inflector # rubocop:disable Metrics/ModuleLength
|
9
12
|
# Rules for irregular plurals
|
10
13
|
#
|
11
14
|
# @since 0.6.0
|
@@ -40,12 +43,6 @@ module Hanami
|
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
43
|
-
# Matcher for blank strings
|
44
|
-
#
|
45
|
-
# @since 0.4.1
|
46
|
-
# @api private
|
47
|
-
BLANK_STRING_MATCHER = /\A[[:space:]]*\z/.freeze
|
48
|
-
|
49
46
|
# @since 0.4.1
|
50
47
|
# @api private
|
51
48
|
A = 'a'.freeze
|
@@ -142,6 +139,14 @@ module Hanami
|
|
142
139
|
# @api private
|
143
140
|
OUSE = 'ouse'.freeze
|
144
141
|
|
142
|
+
# @since 0.4.1
|
143
|
+
# @api private
|
144
|
+
RSE = 'rse'.freeze
|
145
|
+
|
146
|
+
# @since 0.4.1
|
147
|
+
# @api private
|
148
|
+
RSES = 'rses'.freeze
|
149
|
+
|
145
150
|
# @since 0.4.1
|
146
151
|
# @api private
|
147
152
|
S = 's'.freeze
|
@@ -193,61 +198,66 @@ module Hanami
|
|
193
198
|
# @since 0.6.0
|
194
199
|
# @api private
|
195
200
|
class_attribute :plurals
|
196
|
-
self.plurals = IrregularRules.new(
|
201
|
+
self.plurals = IrregularRules.new(
|
197
202
|
# irregular
|
198
|
-
'cactus'
|
199
|
-
'child'
|
200
|
-
'corpus'
|
201
|
-
'foot'
|
202
|
-
'genus'
|
203
|
-
'goose'
|
204
|
-
'
|
205
|
-
'
|
206
|
-
'
|
207
|
-
'
|
208
|
-
'
|
209
|
-
'
|
210
|
-
'
|
211
|
-
'
|
203
|
+
'cactus' => 'cacti',
|
204
|
+
'child' => 'children',
|
205
|
+
'corpus' => 'corpora',
|
206
|
+
'foot' => 'feet',
|
207
|
+
'genus' => 'genera',
|
208
|
+
'goose' => 'geese',
|
209
|
+
'louse' => 'lice',
|
210
|
+
'man' => 'men',
|
211
|
+
'mouse' => 'mice',
|
212
|
+
'ox' => 'oxen',
|
213
|
+
'person' => 'people',
|
214
|
+
'quiz' => 'quizzes',
|
215
|
+
'sex' => 'sexes',
|
216
|
+
'testis' => 'testes',
|
217
|
+
'tooth' => 'teeth',
|
218
|
+
'woman' => 'women',
|
212
219
|
# uncountable
|
213
|
-
'deer'
|
214
|
-
'equipment'
|
215
|
-
'fish'
|
216
|
-
'information'
|
217
|
-
'means'
|
218
|
-
'money'
|
219
|
-
'news'
|
220
|
-
'offspring'
|
221
|
-
'rice'
|
222
|
-
'series'
|
223
|
-
'sheep'
|
224
|
-
'species'
|
220
|
+
'deer' => 'deer',
|
221
|
+
'equipment' => 'equipment',
|
222
|
+
'fish' => 'fish',
|
223
|
+
'information' => 'information',
|
224
|
+
'means' => 'means',
|
225
|
+
'money' => 'money',
|
226
|
+
'news' => 'news',
|
227
|
+
'offspring' => 'offspring',
|
228
|
+
'rice' => 'rice',
|
229
|
+
'series' => 'series',
|
230
|
+
'sheep' => 'sheep',
|
231
|
+
'species' => 'species',
|
232
|
+
'police' => 'police',
|
225
233
|
# regressions
|
226
234
|
# https://github.com/hanami/utils/issues/106
|
227
|
-
'album'
|
228
|
-
|
235
|
+
'album' => 'albums'
|
236
|
+
)
|
229
237
|
|
230
238
|
# Irregular rules for singulars
|
231
239
|
#
|
232
240
|
# @since 0.6.0
|
233
241
|
# @api private
|
234
242
|
class_attribute :singulars
|
235
|
-
self.singulars = IrregularRules.new(
|
243
|
+
self.singulars = IrregularRules.new(
|
236
244
|
# irregular
|
237
|
-
'cacti'
|
238
|
-
'children'=> 'child',
|
239
|
-
'corpora'
|
240
|
-
'feet'
|
241
|
-
'genera'
|
242
|
-
'geese'
|
243
|
-
'
|
244
|
-
'
|
245
|
-
'
|
246
|
-
'
|
247
|
-
'
|
248
|
-
'
|
249
|
-
'
|
250
|
-
'
|
245
|
+
'cacti' => 'cactus',
|
246
|
+
'children' => 'child',
|
247
|
+
'corpora' => 'corpus',
|
248
|
+
'feet' => 'foot',
|
249
|
+
'genera' => 'genus',
|
250
|
+
'geese' => 'goose',
|
251
|
+
'lice' => 'louse',
|
252
|
+
'men' => 'man',
|
253
|
+
'mice' => 'mouse',
|
254
|
+
'oxen' => 'ox',
|
255
|
+
'people' => 'person',
|
256
|
+
'quizzes' => 'quiz',
|
257
|
+
'sexes' => 'sex',
|
258
|
+
'testes' => 'testis',
|
259
|
+
'teeth' => 'tooth',
|
260
|
+
'women' => 'woman',
|
251
261
|
# uncountable
|
252
262
|
'deer' => 'deer',
|
253
263
|
'equipment' => 'equipment',
|
@@ -263,9 +273,8 @@ module Hanami
|
|
263
273
|
'species' => 'species',
|
264
274
|
'police' => 'police',
|
265
275
|
# fallback
|
266
|
-
'hives' => 'hive'
|
267
|
-
|
268
|
-
})
|
276
|
+
'hives' => 'hive'
|
277
|
+
)
|
269
278
|
|
270
279
|
# Block for custom inflection rules.
|
271
280
|
#
|
@@ -338,8 +347,12 @@ module Hanami
|
|
338
347
|
#
|
339
348
|
# @api private
|
340
349
|
# @since 0.4.1
|
350
|
+
#
|
351
|
+
# rubocop:disable Metrics/AbcSize
|
352
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
353
|
+
# rubocop:disable Metrics/MethodLength
|
341
354
|
def self.pluralize(string)
|
342
|
-
return string if string.nil? || string
|
355
|
+
return string if string.nil? || string =~ Utils::Blank::STRING_MATCHER
|
343
356
|
|
344
357
|
case string
|
345
358
|
when plurals
|
@@ -358,8 +371,6 @@ module Hanami
|
|
358
371
|
string + TA
|
359
372
|
when /\A(.*)(um|#{ A })\z/
|
360
373
|
$1 + A
|
361
|
-
when /\A(.*)(ouse|#{ ICE })\z/
|
362
|
-
$1 + ICE
|
363
374
|
when /\A(buffal|domin|ech|embarg|her|mosquit|potat|tomat)#{ O }\z/i
|
364
375
|
$1 + OES
|
365
376
|
when /\A(.*)(en|#{ INA })\z/
|
@@ -380,6 +391,9 @@ module Hanami
|
|
380
391
|
string + S
|
381
392
|
end
|
382
393
|
end
|
394
|
+
# rubocop:enable Metrics/AbcSize
|
395
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
396
|
+
# rubocop:enable Metrics/MethodLength
|
383
397
|
|
384
398
|
# Singularize the given string
|
385
399
|
#
|
@@ -389,8 +403,13 @@ module Hanami
|
|
389
403
|
#
|
390
404
|
# @api private
|
391
405
|
# @since 0.4.1
|
406
|
+
#
|
407
|
+
# rubocop:disable Metrics/AbcSize
|
408
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
409
|
+
# rubocop:disable Metrics/MethodLength
|
410
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
392
411
|
def self.singularize(string)
|
393
|
-
return string if string.nil? || string
|
412
|
+
return string if string.nil? || string =~ Utils::Blank::STRING_MATCHER
|
394
413
|
|
395
414
|
case string
|
396
415
|
when singulars
|
@@ -399,18 +418,16 @@ module Hanami
|
|
399
418
|
string.sub(CHES, CH)
|
400
419
|
when /\A.*[^aeiou]#{IES}\z/
|
401
420
|
string.sub(IES, Y)
|
402
|
-
when /\A(.*)#{ICE}\z/
|
403
|
-
$1 + OUSE
|
404
421
|
when /\A.*#{EAUX}\z/
|
405
422
|
string.chop
|
406
423
|
when /\A(.*)#{IDES}\z/
|
407
424
|
$1 + IS
|
408
425
|
when /\A(.*)#{US}\z/
|
409
426
|
$1 + I
|
427
|
+
when /\A(.*)#{RSES}\z/
|
428
|
+
$1 + RSE
|
410
429
|
when /\A(.*)#{SES}\z/
|
411
430
|
$1 + S
|
412
|
-
when /\A(.*)#{OUSE}\z/
|
413
|
-
$1 + ICE
|
414
431
|
when /\A(.*)#{MATA}\z/
|
415
432
|
$1 + MA
|
416
433
|
when /\A(.*)#{OES}\z/
|
@@ -437,6 +454,11 @@ module Hanami
|
|
437
454
|
string.chop
|
438
455
|
end
|
439
456
|
end
|
457
|
+
# rubocop:enable Metrics/AbcSize
|
458
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
459
|
+
# rubocop:enable Metrics/MethodLength
|
460
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
440
461
|
end
|
462
|
+
# rubocop:enable Style/PerlBackrefs
|
441
463
|
end
|
442
464
|
end
|