sleeping_king_studios-tools 1.3.0 → 1.4.0.rc.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 +40 -0
- data/README.md +0 -2
- data/lib/sleeping_king_studios/tools/assertions/messages_strategy.rb +1 -2
- data/lib/sleeping_king_studios/tools/assertions.rb +188 -76
- data/lib/sleeping_king_studios/tools/messages/strategies/file_strategy.rb +22 -4
- data/lib/sleeping_king_studios/tools/messages/strategies/hash_strategy.rb +73 -14
- data/lib/sleeping_king_studios/tools/messages/strategy.rb +103 -4
- data/lib/sleeping_king_studios/tools/messages.rb +31 -5
- data/lib/sleeping_king_studios/tools/toolbox/heritable_data.rb +98 -138
- data/lib/sleeping_king_studios/tools/version.rb +3 -3
- metadata +2 -2
|
@@ -5,20 +5,36 @@ require 'sleeping_king_studios/tools/messages/strategies/hash_strategy'
|
|
|
5
5
|
|
|
6
6
|
module SleepingKingStudios::Tools::Messages::Strategies
|
|
7
7
|
# Messaging strategy that loads message templates from the given file.
|
|
8
|
+
#
|
|
9
|
+
# Internally, the parsed templates are flattened - a nested Hash of
|
|
10
|
+
# { foo: { bar: { baz: 'template' } } } is stored as a flat Hash with
|
|
11
|
+
# { 'foo.bar.baz' => 'template' }. This makes lookup of scoped keys more
|
|
12
|
+
# efficient but prevents retrieving non-leaf nodes (such as "foo" or "foo.bar"
|
|
13
|
+
# in the above Hash). If you need to retrieve Hash values, initialize the
|
|
14
|
+
# strategy with flatten_templates: false.
|
|
8
15
|
class FileStrategy < HashStrategy
|
|
9
16
|
# Exception raised when reading the templates file.
|
|
10
17
|
class FileError < StandardError; end
|
|
11
18
|
|
|
12
|
-
# @
|
|
13
|
-
#
|
|
14
|
-
|
|
19
|
+
# @overload initialize(file_name, **options)
|
|
20
|
+
# @param file_name [String] the full path to the file with the templates
|
|
21
|
+
# data.
|
|
22
|
+
# @param options [Hash] additional options for the strategy.
|
|
23
|
+
#
|
|
24
|
+
# @option options flatten_templates [true, false] if true, the templates
|
|
25
|
+
# are flattened internally. Defaults to true.
|
|
26
|
+
# @option options validate_values [Proc, true, false] if false, skips
|
|
27
|
+
# validation of template values. If given a Proc, calls the Proc with
|
|
28
|
+
# each template value; the value fails validation if the Proc returns
|
|
29
|
+
# an error message. Defaults to true.
|
|
30
|
+
def initialize(file_name, flatten_templates: true, validate_values: true)
|
|
15
31
|
validate_file_name(file_name)
|
|
16
32
|
|
|
17
33
|
@file_name = file_name
|
|
18
34
|
raw_data = read_file(file_name)
|
|
19
35
|
templates = parse_templates(raw_data)
|
|
20
36
|
|
|
21
|
-
super(templates)
|
|
37
|
+
super(templates, flatten_templates:, validate_values:)
|
|
22
38
|
end
|
|
23
39
|
|
|
24
40
|
# @return [String] the full path to the file with the templates data.
|
|
@@ -68,6 +84,8 @@ module SleepingKingStudios::Tools::Messages::Strategies
|
|
|
68
84
|
end
|
|
69
85
|
end
|
|
70
86
|
|
|
87
|
+
def template_value_error_message = 'value is not a String'
|
|
88
|
+
|
|
71
89
|
def validate_file_name(file_name)
|
|
72
90
|
raise ArgumentError, "file name can't be blank" if file_name.nil?
|
|
73
91
|
|
|
@@ -4,19 +4,40 @@ require 'sleeping_king_studios/tools/messages/strategies'
|
|
|
4
4
|
|
|
5
5
|
module SleepingKingStudios::Tools::Messages::Strategies
|
|
6
6
|
# Messaging strategy that refers to an internal Hash of templates.
|
|
7
|
+
#
|
|
8
|
+
# Internally, the templates Hash is flattened - a nested Hash of
|
|
9
|
+
# { foo: { bar: { baz: 'template' } } } is stored as a flat Hash with
|
|
10
|
+
# { 'foo.bar.baz' => 'template' }. This makes lookup of scoped keys more
|
|
11
|
+
# efficient but prevents retrieving non-leaf nodes (such as "foo" or "foo.bar"
|
|
12
|
+
# in the above Hash). If you need to retrieve Hash values, initialize the
|
|
13
|
+
# strategy with flatten_templates: false.
|
|
7
14
|
class HashStrategy < SleepingKingStudios::Tools::Messages::Strategy
|
|
8
15
|
# Exception raised when parsing the templates input.
|
|
9
16
|
class ParseError < StandardError; end
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
VALID_TEMPLATE_VALUES = [Proc, String, Symbol].freeze
|
|
19
|
+
private_constant :VALID_TEMPLATE_VALUES
|
|
20
|
+
|
|
21
|
+
# @overload initialize(templates, **options)
|
|
22
|
+
# @param templates [Hash] the templates used to generate messages.
|
|
23
|
+
# @param options [Hash] additional options for the strategy.
|
|
24
|
+
#
|
|
25
|
+
# @option options flatten_templates [true, false] if true, the templates
|
|
26
|
+
# are flattened internally. Defaults to true.
|
|
27
|
+
# @option options validate_values [Proc, true, false] if false, skips
|
|
28
|
+
# validation of template values. If given a Proc, calls the Proc with
|
|
29
|
+
# each template value; the value fails validation if the Proc returns
|
|
30
|
+
# an error message. Defaults to true.
|
|
31
|
+
def initialize(templates, flatten_templates: true, validate_values: true)
|
|
13
32
|
super()
|
|
14
33
|
|
|
15
34
|
unless templates.is_a?(Hash)
|
|
16
35
|
raise ArgumentError, 'templates is not an instance of Hash'
|
|
17
36
|
end
|
|
18
37
|
|
|
19
|
-
@
|
|
38
|
+
@flatten_templates = flatten_templates
|
|
39
|
+
@validate_values = validate_values
|
|
40
|
+
@templates = self.flatten_templates(templates).freeze
|
|
20
41
|
end
|
|
21
42
|
|
|
22
43
|
# @return [Hash] the templates used to generate messages.
|
|
@@ -24,27 +45,66 @@ module SleepingKingStudios::Tools::Messages::Strategies
|
|
|
24
45
|
|
|
25
46
|
private
|
|
26
47
|
|
|
27
|
-
def
|
|
48
|
+
def convert_keys_to_strings(hsh, scope: nil) # rubocop:disable Metrics/MethodLength
|
|
49
|
+
hsh
|
|
50
|
+
.to_h do |key, value|
|
|
51
|
+
validate_template_key(key, scope:)
|
|
52
|
+
|
|
53
|
+
if value.is_a?(Hash)
|
|
54
|
+
local = scope ? "#{scope}.#{key}" : key.to_s
|
|
55
|
+
|
|
56
|
+
value = convert_keys_to_strings(value, scope: local)
|
|
57
|
+
else
|
|
58
|
+
validate_template_value(key, value, scope:)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
[key.to_s, value]
|
|
62
|
+
end
|
|
63
|
+
.freeze
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def flatten_templates(hsh, scope: nil, templates: {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
67
|
+
return convert_keys_to_strings(hsh, scope:) unless flatten_templates?
|
|
68
|
+
|
|
28
69
|
hsh.each do |(key, value)|
|
|
29
70
|
validate_template_key(key, scope:)
|
|
30
|
-
|
|
71
|
+
|
|
72
|
+
next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
31
73
|
|
|
32
74
|
local = scope ? "#{scope}.#{key}" : key.to_s
|
|
33
75
|
|
|
34
|
-
|
|
76
|
+
if value.is_a?(Hash)
|
|
77
|
+
next flatten_templates(value, scope: local, templates:)
|
|
78
|
+
end
|
|
35
79
|
|
|
36
|
-
|
|
80
|
+
validate_template_value(key, value, scope:)
|
|
37
81
|
|
|
38
|
-
|
|
82
|
+
templates[local] = value
|
|
39
83
|
end
|
|
40
84
|
|
|
41
85
|
templates
|
|
42
86
|
end
|
|
43
87
|
|
|
88
|
+
def flatten_templates? = @flatten_templates
|
|
89
|
+
|
|
44
90
|
def template_for(scoped_key, **)
|
|
45
|
-
templates[scoped_key]
|
|
91
|
+
return templates[scoped_key] if flatten_templates?
|
|
92
|
+
|
|
93
|
+
templates.dig(*scoped_key.split('.'))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def template_value_error(value)
|
|
97
|
+
return unless @validate_values
|
|
98
|
+
|
|
99
|
+
return @validate_values.call(value) if @validate_values.is_a?(Proc)
|
|
100
|
+
|
|
101
|
+
return if VALID_TEMPLATE_VALUES.any? { |klass| value.is_a?(klass) }
|
|
102
|
+
|
|
103
|
+
template_value_error_message
|
|
46
104
|
end
|
|
47
105
|
|
|
106
|
+
def template_value_error_message = 'value is not a String or a Proc'
|
|
107
|
+
|
|
48
108
|
def validate_template_key(key, scope:)
|
|
49
109
|
return if (key.is_a?(String) || key.is_a?(Symbol)) && !key.empty?
|
|
50
110
|
|
|
@@ -57,14 +117,13 @@ module SleepingKingStudios::Tools::Messages::Strategies
|
|
|
57
117
|
end
|
|
58
118
|
|
|
59
119
|
def validate_template_value(key, value, scope:)
|
|
60
|
-
|
|
61
|
-
|
|
120
|
+
error_message = template_value_error(value)
|
|
121
|
+
|
|
122
|
+
return if error_message.nil?
|
|
62
123
|
|
|
63
124
|
message = 'invalid value in templates'
|
|
64
125
|
message = "#{message}.#{scope}" if scope
|
|
65
|
-
message = "#{message}.#{key}"
|
|
66
|
-
message =
|
|
67
|
-
"#{message} - expected Hash, Proc, or String, got #{value.inspect}"
|
|
126
|
+
message = "#{message}.#{key} - #{error_message}, got #{value.inspect}"
|
|
68
127
|
|
|
69
128
|
raise ParseError, message
|
|
70
129
|
end
|
|
@@ -5,10 +5,27 @@ require 'sleeping_king_studios/tools/messages'
|
|
|
5
5
|
module SleepingKingStudios::Tools
|
|
6
6
|
# Abstract class for converting parameterized keys to user-readable messages.
|
|
7
7
|
class Messages::Strategy
|
|
8
|
+
UNDEFINED = SleepingKingStudios::Tools::UNDEFINED
|
|
9
|
+
private_constant :UNDEFINED
|
|
10
|
+
|
|
8
11
|
# @overload call(key, parameters: {}, scope: nil, **options)
|
|
9
12
|
# Generates a formatted string for the given key, parameters, and options.
|
|
10
13
|
#
|
|
14
|
+
# If the strategy does not define a message for the key, you can provide a
|
|
15
|
+
# default value.
|
|
16
|
+
#
|
|
17
|
+
# - If the default value is a Proc, it will be called with the fully
|
|
18
|
+
# scoped key, as a positional argument as well as any additional
|
|
19
|
+
# keywords passed to #call. The value returned by the Proc will be
|
|
20
|
+
# returned by #call.
|
|
21
|
+
# - If the default value is any other Object (including nil), the default
|
|
22
|
+
# value will be returned by #call.
|
|
23
|
+
# - If a default value is not provided, a missing message warning will be
|
|
24
|
+
# generated and returned.
|
|
25
|
+
#
|
|
11
26
|
# @param key [String, Symbol] the key used to resolve the message.
|
|
27
|
+
# @param default [Object] the default value to return if the strategy does
|
|
28
|
+
# not define a message for the key.
|
|
12
29
|
# @param parameters [Hash] the parameters used to generate the message,
|
|
13
30
|
# such as values for a template string.
|
|
14
31
|
# @param scope [String] the namespace for the key. Combined with the given
|
|
@@ -18,18 +35,98 @@ module SleepingKingStudios::Tools
|
|
|
18
35
|
#
|
|
19
36
|
# @return [String] the generated template string, or a generic error
|
|
20
37
|
# string if a message is not defined for the given key and parameters.
|
|
21
|
-
def call(key, parameters: {}, scope: nil, **)
|
|
38
|
+
def call(key, default: UNDEFINED, parameters: {}, scope: nil, **)
|
|
39
|
+
scoped_key = join_scope(key:, scope:)
|
|
40
|
+
template = fetch(key, scope:, **) do
|
|
41
|
+
return apply_default(scoped_key, default, **)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
generate(template, parameters:, scoped_key:, **)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @overload fetch(key, default = nil, scope: nil, **options)
|
|
48
|
+
# Retrieves the messages template with the given scope and options.
|
|
49
|
+
#
|
|
50
|
+
# If there is no template matching the given key, scope, and options,
|
|
51
|
+
# returns the default value or raises a KeyError if no default value is
|
|
52
|
+
# provided.
|
|
53
|
+
#
|
|
54
|
+
# @param key [String, Symbol] the key used to resolve the message.
|
|
55
|
+
# @param default [Object] the default value to return if the strategy does
|
|
56
|
+
# not define a message for the key.
|
|
57
|
+
# @param scope [String] the namespace for the key. Combined with the given
|
|
58
|
+
# key to generate the scoped key value.
|
|
59
|
+
# @param options [Hash] additional options for resolving or generating the
|
|
60
|
+
# message.
|
|
61
|
+
#
|
|
62
|
+
# @return [Object] the stored template for the requested key, or the
|
|
63
|
+
# provided default value.
|
|
64
|
+
#
|
|
65
|
+
# @raise [KeyError] if there is no matching template and no default value
|
|
66
|
+
# is provided.
|
|
67
|
+
#
|
|
68
|
+
# @overload fetch(key, scope: nil, **options, &default)
|
|
69
|
+
# If there is no template matching the given key, scope, and options,
|
|
70
|
+
# the block is called with the scoped key and the value returned by the
|
|
71
|
+
# block is returned by #fetch.
|
|
72
|
+
#
|
|
73
|
+
# @param key [String, Symbol] the key used to resolve the message.
|
|
74
|
+
# @param scope [String] the namespace for the key. Combined with the given
|
|
75
|
+
# key to generate the scoped key value.
|
|
76
|
+
# @param options [Hash] additional options for resolving or generating the
|
|
77
|
+
# message.
|
|
78
|
+
#
|
|
79
|
+
# @yieldparam [String] scoped_key the fully scoped key for the requested
|
|
80
|
+
# template, combining the given key and the given scope, if any.
|
|
81
|
+
# @yieldparam options [Hash] additional options for resolving the message.
|
|
82
|
+
#
|
|
83
|
+
# @yieldreturn [Object] the default value for the given scoped key.
|
|
84
|
+
#
|
|
85
|
+
# @return [Object] the stored template for the requested key, or the value
|
|
86
|
+
# returned by the provided default block.
|
|
87
|
+
def fetch(key, default = UNDEFINED, scope: nil, **, &block)
|
|
22
88
|
scoped_key = join_scope(key:, scope:)
|
|
23
89
|
template = template_for(scoped_key, **)
|
|
24
90
|
|
|
25
|
-
return
|
|
91
|
+
return template unless template.nil?
|
|
26
92
|
|
|
27
|
-
|
|
93
|
+
return block.call(scoped_key, **) if block_given?
|
|
94
|
+
|
|
95
|
+
return default unless default == UNDEFINED
|
|
96
|
+
|
|
97
|
+
raise KeyError, "template not found: #{scoped_key.inspect}"
|
|
28
98
|
end
|
|
29
99
|
|
|
100
|
+
# @overload get(key, scope: nil, **options)
|
|
101
|
+
# Retrieves the messages template with the given scope and options.
|
|
102
|
+
#
|
|
103
|
+
# @param key [String, Symbol] the key used to resolve the message.
|
|
104
|
+
# @param scope [String] the namespace for the key. Combined with the given
|
|
105
|
+
# key to generate the scoped key value.
|
|
106
|
+
# @param options [Hash] additional options for resolving or generating the
|
|
107
|
+
# message.
|
|
108
|
+
#
|
|
109
|
+
# @return [Object] the stored template for the requested key, or nil if
|
|
110
|
+
# there is no matching template.
|
|
111
|
+
def get(key, scope: nil, **) = fetch(key, nil, scope:, **)
|
|
112
|
+
|
|
30
113
|
private
|
|
31
114
|
|
|
32
|
-
def
|
|
115
|
+
def apply_default(key, default, **)
|
|
116
|
+
return missing_message(key, **) if default == UNDEFINED
|
|
117
|
+
|
|
118
|
+
return default unless default.is_a?(Proc)
|
|
119
|
+
|
|
120
|
+
default.call(key, **)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def generate( # rubocop:disable Metrics/MethodLength
|
|
124
|
+
template,
|
|
125
|
+
parameters: {},
|
|
126
|
+
reraise_exceptions: false,
|
|
127
|
+
scoped_key: nil,
|
|
128
|
+
**
|
|
129
|
+
)
|
|
33
130
|
raise ArgumentError, "template can't be blank" if template.nil?
|
|
34
131
|
|
|
35
132
|
case template
|
|
@@ -41,6 +138,8 @@ module SleepingKingStudios::Tools
|
|
|
41
138
|
raise ArgumentError, "invalid template #{template.inspect}"
|
|
42
139
|
end
|
|
43
140
|
rescue KeyError => exception
|
|
141
|
+
raise if reraise_exceptions
|
|
142
|
+
|
|
44
143
|
missing_parameters_message(scoped_key, exception:, **)
|
|
45
144
|
end
|
|
46
145
|
|
|
@@ -9,6 +9,9 @@ module SleepingKingStudios::Tools
|
|
|
9
9
|
autoload :Strategies, 'sleeping_king_studios/tools/messages/strategies'
|
|
10
10
|
autoload :Strategy, 'sleeping_king_studios/tools/messages/strategy'
|
|
11
11
|
|
|
12
|
+
UNDEFINED = SleepingKingStudios::Tools::UNDEFINED
|
|
13
|
+
private_constant :UNDEFINED
|
|
14
|
+
|
|
12
15
|
# @param registry [SleepingKingStudios::Tools::Messages::Registry] the
|
|
13
16
|
# strategies registry to use for the tool. Defaults to the value of
|
|
14
17
|
# Registry.global.
|
|
@@ -29,27 +32,50 @@ module SleepingKingStudios::Tools
|
|
|
29
32
|
# Generates a message from the given key, scope, and parameters.
|
|
30
33
|
#
|
|
31
34
|
# Internally, finds a messages strategy matching the combined key and
|
|
32
|
-
# scope and calls that strategy.
|
|
33
|
-
#
|
|
35
|
+
# scope and calls that strategy.
|
|
36
|
+
#
|
|
37
|
+
# If a strategy is not found, or if the strategy does not define a message
|
|
38
|
+
# for the key, you can provide a default value.
|
|
39
|
+
#
|
|
40
|
+
# - If the default value is a Proc, it will be called with the fully
|
|
41
|
+
# scoped key, as a positional argument as well as any additional
|
|
42
|
+
# keywords passed to #call. The value returned by the Proc will be
|
|
43
|
+
# returned by #call.
|
|
44
|
+
# - If the default value is any other Object (including nil), the default
|
|
45
|
+
# value will be returned by #call.
|
|
46
|
+
# - If a default value is not provided, a missing message warning will be
|
|
47
|
+
# generated and returned.
|
|
34
48
|
#
|
|
35
49
|
# @param key [String, Symbol] the key used to resolve the message.
|
|
50
|
+
# @param default [Object] the default value to return if there is no
|
|
51
|
+
# defined registry for the message, or if the strategy does not define a
|
|
52
|
+
# message for the key.
|
|
36
53
|
# @param parameters [Hash] the parameters used to generate the message,
|
|
37
54
|
# such as values for a template string.
|
|
38
55
|
# @param scope [String] the namespace for the key. Combined with the given
|
|
39
56
|
# key to generate the scoped key value.
|
|
40
57
|
# @param options [Hash] additional options for resolving or generating the
|
|
41
58
|
# message.
|
|
42
|
-
def message(key, parameters: {}, scope: nil, **)
|
|
59
|
+
def message(key, default: UNDEFINED, parameters: {}, scope: nil, **)
|
|
43
60
|
scoped_key = join_scope(key:, scope:)
|
|
44
61
|
strategy = registry.get(scoped_key)
|
|
45
62
|
|
|
46
|
-
return
|
|
63
|
+
return apply_default(scoped_key, default, **) unless strategy
|
|
47
64
|
|
|
48
|
-
strategy.call(key, parameters:, scope:, **)
|
|
65
|
+
strategy.call(key, default:, parameters:, scope:, **)
|
|
49
66
|
end
|
|
67
|
+
alias get message
|
|
50
68
|
|
|
51
69
|
private
|
|
52
70
|
|
|
71
|
+
def apply_default(key, default, **)
|
|
72
|
+
return missing_message(key, **) if default == UNDEFINED
|
|
73
|
+
|
|
74
|
+
return default unless default.is_a?(Proc)
|
|
75
|
+
|
|
76
|
+
default.call(key, **)
|
|
77
|
+
end
|
|
78
|
+
|
|
53
79
|
def missing_message(scoped_key, **)
|
|
54
80
|
"Message missing: #{scoped_key}"
|
|
55
81
|
end
|
|
@@ -5,97 +5,12 @@ require 'sleeping_king_studios/tools/toolbox'
|
|
|
5
5
|
module SleepingKingStudios::Tools::Toolbox
|
|
6
6
|
# Mixin to allow defining subclasses of Data classes.
|
|
7
7
|
module HeritableData
|
|
8
|
+
# Special thanks to `@havenwood` from the Ruby Discord on the solution here.
|
|
9
|
+
DEFINE = Data.singleton_class.instance_method(:define)
|
|
10
|
+
private_constant :DEFINE
|
|
11
|
+
|
|
8
12
|
# Methods prepended onto the singleton class when including HeritableData.
|
|
9
13
|
module ClassMethods
|
|
10
|
-
# Compares `self` and `other`.
|
|
11
|
-
#
|
|
12
|
-
# @param other [Object] the object to compare.
|
|
13
|
-
#
|
|
14
|
-
# @return [true] if self is a subclass of other, or if self inherits Data
|
|
15
|
-
# members and methods from other.
|
|
16
|
-
# @return [false] if self and other are the same class, if other is a
|
|
17
|
-
# subclass of self, or if other inherits Data members and methods from
|
|
18
|
-
# self.
|
|
19
|
-
# @return [nil] if none of the above is true.
|
|
20
|
-
def <(other)
|
|
21
|
-
(self <=> other)&.then { |cmp| cmp == -1 }
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Compares `self` and `other`.
|
|
25
|
-
#
|
|
26
|
-
# @param other [Object] the object to compare.
|
|
27
|
-
#
|
|
28
|
-
# @return [true] if self and other are the same class, if self is a
|
|
29
|
-
# subclass of other, or if self inherits Data members and methods from
|
|
30
|
-
# other.
|
|
31
|
-
# @return [false] if other is a subclass of self, or if other inherits
|
|
32
|
-
# Data members and methods from self.
|
|
33
|
-
# @return [nil] if none of the above is true.
|
|
34
|
-
def <=(other)
|
|
35
|
-
(self <=> other)&.then { |cmp| cmp < 1 }
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Compares `self` and `other`.
|
|
39
|
-
#
|
|
40
|
-
# @param other [Object] the object to compare.
|
|
41
|
-
#
|
|
42
|
-
# @return [-1] if self is a subclass of other, or if self inherits Data
|
|
43
|
-
# members and methods from other.
|
|
44
|
-
# @return [0] if self and other are the same class.
|
|
45
|
-
# @return [1] if self is a parent class of other, or if other inherits
|
|
46
|
-
# Data members and methods from self.
|
|
47
|
-
# @return [nil] if none of the above is true.
|
|
48
|
-
def <=>(other)
|
|
49
|
-
return super unless other.is_a?(Class)
|
|
50
|
-
|
|
51
|
-
return -1 if other.equal?(Data)
|
|
52
|
-
return 0 if other.equal?(self)
|
|
53
|
-
|
|
54
|
-
return nil unless other.const_defined?(:HeritableMethods)
|
|
55
|
-
|
|
56
|
-
return -1 if include?(other::HeritableMethods)
|
|
57
|
-
return 1 if other.include?(self::HeritableMethods)
|
|
58
|
-
|
|
59
|
-
nil
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# @return [true] if the object is an instance of the data class, or if the
|
|
63
|
-
# object is an instance of a Data class that inherits members and
|
|
64
|
-
# methods from self.
|
|
65
|
-
def ===(object)
|
|
66
|
-
return true if super
|
|
67
|
-
|
|
68
|
-
(self > object.class) || false
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Compares `self` and `other`.
|
|
72
|
-
#
|
|
73
|
-
# @param other [Object] the object to compare.
|
|
74
|
-
#
|
|
75
|
-
# @return [true] if other is a subclass of other, or if other inherits
|
|
76
|
-
# Data members and methods from self.
|
|
77
|
-
# @return [false] if self and other are the same class, if self is a
|
|
78
|
-
# subclass of other, or if self inherits Data members and methods from
|
|
79
|
-
# other.
|
|
80
|
-
# @return [nil] if none of the above is true.
|
|
81
|
-
def >(other)
|
|
82
|
-
(self <=> other)&.then { |cmp| cmp == 1 }
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Compares `self` and `other`.
|
|
86
|
-
#
|
|
87
|
-
# @param other [Object] the object to compare.
|
|
88
|
-
#
|
|
89
|
-
# @return [true] if self and other are the same class, if other is a
|
|
90
|
-
# subclass of other, or if other inherits Data members and methods from
|
|
91
|
-
# self.
|
|
92
|
-
# @return [false] if self is a subclass of other, or if self inherits Data
|
|
93
|
-
# members and methods from other.
|
|
94
|
-
# @return [nil] if none of the above is true.
|
|
95
|
-
def >=(other)
|
|
96
|
-
(self <=> other)&.then { |cmp| cmp > -1 }
|
|
97
|
-
end
|
|
98
|
-
|
|
99
14
|
# Defines a new Data class including the members/methods of this class.
|
|
100
15
|
#
|
|
101
16
|
# The defined data class will include HeritableData, and will be able to
|
|
@@ -106,40 +21,53 @@ module SleepingKingStudios::Tools::Toolbox
|
|
|
106
21
|
# parent Data class.
|
|
107
22
|
#
|
|
108
23
|
# @yield additional methods to define on the new Data class.
|
|
109
|
-
def define(*symbols, &)
|
|
110
|
-
|
|
24
|
+
def define(*symbols, &block)
|
|
25
|
+
# @deprecated v2.0
|
|
26
|
+
block = wrap_class_block(block)
|
|
27
|
+
|
|
28
|
+
DEFINE
|
|
29
|
+
.bind_call(self, *members, *symbols, &block)
|
|
30
|
+
.include(HeritableData)
|
|
31
|
+
.tap do |data_class|
|
|
32
|
+
# @deprecated v2.0
|
|
33
|
+
handle_deprecated_lifecycle_hooks(data_class)
|
|
34
|
+
end
|
|
111
35
|
end
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# Custom module used to propagate heritable methods across Data classes.
|
|
115
|
-
#
|
|
116
|
-
# @private
|
|
117
|
-
class HeritableMethods < Module
|
|
118
|
-
# @param parent_class [Class, nil] the parent data class, if any.
|
|
119
|
-
#
|
|
120
|
-
# @yield additional methods to define on the Data class.
|
|
121
|
-
def initialize(parent_class: nil, &methods)
|
|
122
|
-
super(&nil)
|
|
36
|
+
alias with define
|
|
123
37
|
|
|
124
|
-
|
|
38
|
+
private
|
|
125
39
|
|
|
126
|
-
|
|
40
|
+
# @deprecated v2.0
|
|
41
|
+
def handle_deprecated_lifecycle_hooks(data_class)
|
|
42
|
+
return if data_class.method(:included).owner == Module
|
|
43
|
+
|
|
44
|
+
SleepingKingStudios::Tools::Toolbelt
|
|
45
|
+
.instance
|
|
46
|
+
.core_tools
|
|
47
|
+
.deprecate(
|
|
48
|
+
'Defining class methods or including modules using an `included` ' \
|
|
49
|
+
'hook is deprecated. Define the methods or include the modules ' \
|
|
50
|
+
'directly in the block.'
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Ensure legacy class methods defined via .included hook are defined for
|
|
54
|
+
# the child class.
|
|
55
|
+
data_class.send(:included, data_class)
|
|
127
56
|
end
|
|
128
57
|
|
|
129
|
-
# @
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
# Iterates over the ::HeritableMethods modules for parent classes.
|
|
133
|
-
def each_heritable_module
|
|
134
|
-
return enum_for(:each_heritable_module) unless block_given?
|
|
58
|
+
# @deprecated v2.0
|
|
59
|
+
def wrap_class_block(block)
|
|
60
|
+
return block unless block&.lambda? && block.arity.zero?
|
|
135
61
|
|
|
136
|
-
|
|
62
|
+
SleepingKingStudios::Tools::Toolbelt
|
|
63
|
+
.instance
|
|
64
|
+
.core_tools
|
|
65
|
+
.deprecate(
|
|
66
|
+
'Data.define with a lambda of arity zero is not supported. This ' \
|
|
67
|
+
'will raise an ArgumentError starting in version 2.0.'
|
|
68
|
+
)
|
|
137
69
|
|
|
138
|
-
|
|
139
|
-
yield ancestor
|
|
140
|
-
|
|
141
|
-
ancestor = ancestor.parent_class&.then { |mod| mod::HeritableMethods }
|
|
142
|
-
end
|
|
70
|
+
->(mod) { mod.class_exec(&block) }
|
|
143
71
|
end
|
|
144
72
|
end
|
|
145
73
|
|
|
@@ -151,23 +79,51 @@ module SleepingKingStudios::Tools::Toolbox
|
|
|
151
79
|
# methods from.
|
|
152
80
|
#
|
|
153
81
|
# @yield additional methods to define on the new Data class.
|
|
154
|
-
def define(*symbols, parent_class: nil, &)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
82
|
+
def define(*symbols, parent_class: nil, &block) # rubocop:disable Metrics/MethodLength
|
|
83
|
+
# @deprecated v2.0
|
|
84
|
+
if parent_class
|
|
85
|
+
SleepingKingStudios::Tools::Toolbelt
|
|
86
|
+
.instance
|
|
87
|
+
.core_tools
|
|
88
|
+
.deprecate(
|
|
89
|
+
'HeritableData.define with a parent class is deprecated and ' \
|
|
90
|
+
'will be removed starting in version 2.0.'
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return parent_class.define(*symbols, &block)
|
|
159
94
|
end
|
|
95
|
+
|
|
96
|
+
# @deprecated v2.0
|
|
97
|
+
block = wrap_class_block(block)
|
|
98
|
+
|
|
99
|
+
Data
|
|
100
|
+
.define(*symbols, &block)
|
|
101
|
+
.include(HeritableData)
|
|
102
|
+
.tap do |data_class|
|
|
103
|
+
# @deprecated v2.0
|
|
104
|
+
handle_deprecated_lifecycle_hooks(data_class)
|
|
105
|
+
end
|
|
160
106
|
end
|
|
107
|
+
alias with define
|
|
161
108
|
|
|
162
109
|
private
|
|
163
110
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
111
|
+
# @deprecated v2.0
|
|
112
|
+
def handle_deprecated_lifecycle_hooks(data_class)
|
|
113
|
+
return if data_class.method(:included).owner == Module
|
|
114
|
+
|
|
115
|
+
SleepingKingStudios::Tools::Toolbelt
|
|
116
|
+
.instance
|
|
117
|
+
.core_tools
|
|
118
|
+
.deprecate(
|
|
119
|
+
'Defining class methods or including modules using an `included` ' \
|
|
120
|
+
'hook is deprecated. Define the methods or include the modules ' \
|
|
121
|
+
'directly in the block.'
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Ensure legacy class methods defined via .included hook are defined for
|
|
125
|
+
# the child class.
|
|
126
|
+
data_class.send(:included, data_class)
|
|
171
127
|
end
|
|
172
128
|
|
|
173
129
|
def included(other)
|
|
@@ -175,17 +131,21 @@ module SleepingKingStudios::Tools::Toolbox
|
|
|
175
131
|
|
|
176
132
|
other.singleton_class.prepend(ClassMethods)
|
|
177
133
|
end
|
|
178
|
-
end
|
|
179
134
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
135
|
+
# @deprecated v2.0
|
|
136
|
+
def wrap_class_block(block)
|
|
137
|
+
return block unless block&.lambda? && block.arity.zero?
|
|
138
|
+
|
|
139
|
+
SleepingKingStudios::Tools::Toolbelt
|
|
140
|
+
.instance
|
|
141
|
+
.core_tools
|
|
142
|
+
.deprecate(
|
|
143
|
+
'Data.define with a lambda of arity zero is not supported. This ' \
|
|
144
|
+
'will raise an ArgumentError starting in version 2.0.'
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
->(mod) { mod.class_exec(&block) }
|
|
148
|
+
end
|
|
188
149
|
end
|
|
189
|
-
alias kind_of? is_a?
|
|
190
150
|
end
|
|
191
151
|
end
|
|
@@ -9,13 +9,13 @@ module SleepingKingStudios
|
|
|
9
9
|
# Major version.
|
|
10
10
|
MAJOR = 1
|
|
11
11
|
# Minor version.
|
|
12
|
-
MINOR =
|
|
12
|
+
MINOR = 4
|
|
13
13
|
# Patch version.
|
|
14
14
|
PATCH = 0
|
|
15
15
|
# Prerelease version.
|
|
16
|
-
PRERELEASE =
|
|
16
|
+
PRERELEASE = :rc
|
|
17
17
|
# Build metadata.
|
|
18
|
-
BUILD =
|
|
18
|
+
BUILD = 0
|
|
19
19
|
|
|
20
20
|
class << self
|
|
21
21
|
# Generates the gem version string from the Version constants.
|