stannum 0.1.0 → 0.3.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 +32 -0
- data/README.md +85 -21
- data/config/locales/en.rb +17 -3
- data/lib/stannum/constraints/base.rb +11 -4
- data/lib/stannum/constraints/hashes/extra_keys.rb +10 -2
- data/lib/stannum/constraints/hashes/indifferent_extra_keys.rb +47 -0
- data/lib/stannum/constraints/hashes.rb +6 -2
- data/lib/stannum/constraints/parameters/extra_arguments.rb +23 -0
- data/lib/stannum/constraints/parameters/extra_keywords.rb +29 -0
- data/lib/stannum/constraints/parameters.rb +11 -0
- data/lib/stannum/constraints/properties/base.rb +124 -0
- data/lib/stannum/constraints/properties/do_not_match_property.rb +117 -0
- data/lib/stannum/constraints/properties/match_property.rb +117 -0
- data/lib/stannum/constraints/properties/matching.rb +112 -0
- data/lib/stannum/constraints/properties.rb +17 -0
- data/lib/stannum/constraints/tuples/extra_items.rb +1 -1
- data/lib/stannum/constraints/type.rb +1 -1
- data/lib/stannum/constraints/types/hash_type.rb +6 -2
- data/lib/stannum/constraints.rb +2 -0
- data/lib/stannum/contracts/builder.rb +13 -2
- data/lib/stannum/contracts/hash_contract.rb +14 -0
- data/lib/stannum/contracts/indifferent_hash_contract.rb +13 -0
- data/lib/stannum/contracts/parameters/arguments_contract.rb +2 -7
- data/lib/stannum/contracts/parameters/keywords_contract.rb +2 -7
- data/lib/stannum/contracts/tuple_contract.rb +1 -1
- data/lib/stannum/entities/attributes.rb +218 -0
- data/lib/stannum/entities/constraints.rb +177 -0
- data/lib/stannum/entities/properties.rb +186 -0
- data/lib/stannum/entities.rb +13 -0
- data/lib/stannum/entity.rb +83 -0
- data/lib/stannum/errors.rb +3 -3
- data/lib/stannum/messages/default_loader.rb +95 -0
- data/lib/stannum/messages/default_strategy.rb +31 -50
- data/lib/stannum/messages.rb +1 -0
- data/lib/stannum/rspec/match_errors_matcher.rb +6 -6
- data/lib/stannum/rspec/validate_parameter_matcher.rb +10 -9
- data/lib/stannum/schema.rb +78 -37
- data/lib/stannum/struct.rb +12 -346
- data/lib/stannum/support/coercion.rb +19 -0
- data/lib/stannum/version.rb +1 -1
- data/lib/stannum.rb +3 -0
- metadata +29 -19
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sleeping_king_studios/tools/toolbelt'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require 'stannum/messages'
|
7
|
+
|
8
|
+
module Stannum::Messages
|
9
|
+
# Loads and parses messages configuration files and merges configuration data.
|
10
|
+
class DefaultLoader
|
11
|
+
# @param file_paths [Array<String>] The directories from which to load the
|
12
|
+
# configuration files.
|
13
|
+
# @param locale [String] The name of the locale for which to load
|
14
|
+
# configuration.
|
15
|
+
def initialize(file_paths:, locale: 'en')
|
16
|
+
@file_paths = file_paths
|
17
|
+
@locale = locale
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Array<String>] the directories from which to load the
|
21
|
+
# configuration files.
|
22
|
+
attr_reader :file_paths
|
23
|
+
|
24
|
+
# @return [String] the name of the locale for which to load configuration.
|
25
|
+
attr_reader :locale
|
26
|
+
|
27
|
+
# Loads and parses each file, then deep merges the data from each file.
|
28
|
+
#
|
29
|
+
# The configuration file should be either a Ruby file or a YAML file, with
|
30
|
+
# the filename of the format locale.extname, e.g. en.rb or en-gb.yml, and
|
31
|
+
# located in one of the directories defined in #file_paths.
|
32
|
+
#
|
33
|
+
# The contents of each file should be either a Ruby Hash or a YAML document
|
34
|
+
# containing an associative array, with a single key equal to the locale.
|
35
|
+
# The value of the key must be a Hash or associative array, which contains
|
36
|
+
# the scoped messages to load.
|
37
|
+
#
|
38
|
+
# Each file is read in order and parsed into a Hash. Each hash is then deep
|
39
|
+
# merged in sequence, with nested hashes merged together instead of
|
40
|
+
# overwritten.
|
41
|
+
#
|
42
|
+
# @return [Hash<Symbol, Object>] the merged configuration data.
|
43
|
+
def call
|
44
|
+
file_paths.reduce({}) do |config, file_path|
|
45
|
+
loaded = load_configuration_file(file_path)
|
46
|
+
|
47
|
+
deep_update(config, loaded)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias load call
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def deep_update(original, target)
|
55
|
+
target.each do |key, value|
|
56
|
+
if original[key].is_a?(Hash) && value.is_a?(Hash)
|
57
|
+
deep_update(original[key], value)
|
58
|
+
else
|
59
|
+
original[key] = value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
original
|
64
|
+
end
|
65
|
+
|
66
|
+
def load_configuration_file(file_path)
|
67
|
+
ruby_file = File.join(file_path, "#{locale}.rb")
|
68
|
+
|
69
|
+
return read_ruby_file(ruby_file) if File.exist?(ruby_file)
|
70
|
+
|
71
|
+
yaml_file = File.join(file_path, "#{locale}.yml")
|
72
|
+
|
73
|
+
return read_yaml_file(yaml_file) if File.exist?(yaml_file)
|
74
|
+
|
75
|
+
{}
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_ruby_file(filename)
|
79
|
+
ruby = File.read(filename)
|
80
|
+
|
81
|
+
eval(ruby, binding, filename) # rubocop:disable Security/Eval
|
82
|
+
end
|
83
|
+
|
84
|
+
def read_yaml_file(filename)
|
85
|
+
raw = File.read(filename)
|
86
|
+
yaml = YAML.safe_load(raw)
|
87
|
+
|
88
|
+
tools.hsh.convert_keys_to_symbols(yaml)
|
89
|
+
end
|
90
|
+
|
91
|
+
def tools
|
92
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -9,16 +9,28 @@ require 'stannum/messages'
|
|
9
9
|
module Stannum::Messages
|
10
10
|
# Strategy to generate error messages from gem configuration.
|
11
11
|
class DefaultStrategy
|
12
|
+
# The default directories from which to load configured error messages.
|
13
|
+
DEFAULT_LOAD_PATHS = [Stannum::Messages.locales_path].freeze
|
14
|
+
|
15
|
+
# @return [Array<String>] The directories from which to load configured
|
16
|
+
# error messages.
|
17
|
+
def self.load_paths
|
18
|
+
@load_paths ||= DEFAULT_LOAD_PATHS.dup
|
19
|
+
end
|
20
|
+
|
12
21
|
# @param configuration [Hash{Symbol, Object}] The configured messages.
|
13
|
-
# @param
|
14
|
-
#
|
15
|
-
|
16
|
-
|
22
|
+
# @param load_paths [Array<String>] The directories from which to load
|
23
|
+
# configured error messages.
|
24
|
+
# @param locale [String] The locale used to load and scope configured
|
25
|
+
# messages.
|
26
|
+
def initialize(configuration: nil, load_paths: nil, locale: 'en')
|
27
|
+
@load_paths = Array(load_paths) unless load_paths.nil?
|
28
|
+
@locale = locale
|
17
29
|
@configuration = configuration
|
18
30
|
end
|
19
31
|
|
20
|
-
# @return [
|
21
|
-
attr_reader :
|
32
|
+
# @return [String] The locale used to load and scope configured messages.
|
33
|
+
attr_reader :locale
|
22
34
|
|
23
35
|
# @param error_type [String] The qualified path to the configured error
|
24
36
|
# message.
|
@@ -34,6 +46,12 @@ module Stannum::Messages
|
|
34
46
|
interpolate_message(message, options)
|
35
47
|
end
|
36
48
|
|
49
|
+
# @return [Array<String>] The directories from which to load configured
|
50
|
+
# error messages.
|
51
|
+
def load_paths
|
52
|
+
@load_paths || self.class.load_paths
|
53
|
+
end
|
54
|
+
|
37
55
|
# Reloads the configuration from the configured load_path.
|
38
56
|
#
|
39
57
|
# This can be useful when the load_path is updated after creating the
|
@@ -52,23 +70,9 @@ module Stannum::Messages
|
|
52
70
|
@configuration ||= load_configuration
|
53
71
|
end
|
54
72
|
|
55
|
-
def deep_merge(source, target)
|
56
|
-
hsh = tools.hash_tools.deep_dup(source)
|
57
|
-
|
58
|
-
target.each do |key, value|
|
59
|
-
hsh[key] = value.is_a?(Hash) ? deep_merge(hsh[key] || {}, value) : value
|
60
|
-
end
|
61
|
-
|
62
|
-
hsh
|
63
|
-
end
|
64
|
-
|
65
|
-
def default_filename
|
66
|
-
File.join(Stannum::Messages.locales_path, 'en.rb')
|
67
|
-
end
|
68
|
-
|
69
73
|
def generate_message(error_type, options)
|
70
74
|
path = error_type.to_s.split('.').map(&:intern)
|
71
|
-
path.unshift(
|
75
|
+
path.unshift(locale.intern)
|
72
76
|
|
73
77
|
message = configuration.dig(*path)
|
74
78
|
|
@@ -90,35 +94,12 @@ module Stannum::Messages
|
|
90
94
|
end
|
91
95
|
|
92
96
|
def load_configuration
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
case File.extname(filename)
|
100
|
-
when '.rb'
|
101
|
-
read_ruby_file(filename)
|
102
|
-
when '.yml'
|
103
|
-
read_yaml_file(filename)
|
104
|
-
else
|
105
|
-
raise "unable to load configuration file #{filename} with extension" \
|
106
|
-
" #{File.extname(filename)}"
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def read_ruby_file(filename)
|
111
|
-
eval(File.read(filename), binding, filename) # rubocop:disable Security/Eval
|
112
|
-
end
|
113
|
-
|
114
|
-
def read_yaml_file(filename)
|
115
|
-
tools.hash_tools.convert_keys_to_symbols(
|
116
|
-
YAML.safe_load(File.read(filename))
|
117
|
-
)
|
118
|
-
end
|
119
|
-
|
120
|
-
def tools
|
121
|
-
SleepingKingStudios::Tools::Toolbelt.instance
|
97
|
+
Stannum::Messages::DefaultLoader
|
98
|
+
.new(
|
99
|
+
file_paths: load_paths,
|
100
|
+
locale: locale
|
101
|
+
)
|
102
|
+
.call
|
122
103
|
end
|
123
104
|
end
|
124
105
|
end
|
data/lib/stannum/messages.rb
CHANGED
@@ -5,6 +5,7 @@ require 'stannum'
|
|
5
5
|
module Stannum
|
6
6
|
# Namespace for generating messages for Stannum::Errors.
|
7
7
|
module Messages
|
8
|
+
autoload :DefaultLoader, 'stannum/messages/default_loader'
|
8
9
|
autoload :DefaultStrategy, 'stannum/messages/default_strategy'
|
9
10
|
|
10
11
|
# @return [String] the absolute path to the configured locales.
|
@@ -4,8 +4,8 @@ begin
|
|
4
4
|
require 'rspec/sleeping_king_studios/matchers/core/deep_matcher'
|
5
5
|
rescue NameError
|
6
6
|
# :nocov:
|
7
|
-
Kernel.warn 'WARNING: RSpec::SleepingKingStudios is a dependency for using' \
|
8
|
-
'
|
7
|
+
Kernel.warn 'WARNING: RSpec::SleepingKingStudios is a dependency for using ' \
|
8
|
+
'the MatchErrorsMatcher or the #match_errors method.'
|
9
9
|
# :nocov:
|
10
10
|
end
|
11
11
|
|
@@ -40,8 +40,8 @@ module Stannum::RSpec
|
|
40
40
|
# @return [String] a summary message describing a failed expectation.
|
41
41
|
def failure_message
|
42
42
|
unless errors?
|
43
|
-
return 'expected the errors to match the expected errors, but the' \
|
44
|
-
'
|
43
|
+
return 'expected the errors to match the expected errors, but the ' \
|
44
|
+
'object is not an array or Errors object'
|
45
45
|
end
|
46
46
|
|
47
47
|
equality_matcher.failure_message
|
@@ -51,8 +51,8 @@ module Stannum::RSpec
|
|
51
51
|
# expectation.
|
52
52
|
def failure_message_when_negated
|
53
53
|
unless errors?
|
54
|
-
return 'expected the errors not to match the expected errors, but
|
55
|
-
' object is not an array or Errors object'
|
54
|
+
return 'expected the errors not to match the expected errors, but ' \
|
55
|
+
'the object is not an array or Errors object'
|
56
56
|
end
|
57
57
|
|
58
58
|
equality_matcher.failure_message_when_negated
|
@@ -6,6 +6,7 @@ rescue NameError
|
|
6
6
|
# Optional dependency.
|
7
7
|
end
|
8
8
|
|
9
|
+
require 'stannum/constraints/parameters/extra_keywords'
|
9
10
|
require 'stannum/rspec'
|
10
11
|
require 'stannum/support/coercion'
|
11
12
|
|
@@ -138,11 +139,11 @@ module Stannum::RSpec
|
|
138
139
|
when :method_does_not_have_parameter
|
139
140
|
"##{method_name} does not have a #{parameter_name.inspect} parameter"
|
140
141
|
when :parameter_not_validated
|
141
|
-
"##{method_name} does not expect a #{parameter_name.inspect}" \
|
142
|
-
"
|
142
|
+
"##{method_name} does not expect a #{parameter_name.inspect} " \
|
143
|
+
"#{parameter_type}"
|
143
144
|
when :valid_parameter_value
|
144
|
-
"#{valid_value.inspect} is a valid value for the" \
|
145
|
-
"
|
145
|
+
"#{valid_value.inspect} is a valid value for the " \
|
146
|
+
"#{parameter_name.inspect} #{parameter_type}"
|
146
147
|
end
|
147
148
|
|
148
149
|
[message, reason].compact.join(', but ')
|
@@ -280,20 +281,20 @@ module Stannum::RSpec
|
|
280
281
|
unless @expected_constraint.nil?
|
281
282
|
raise RuntimeError,
|
282
283
|
'#does_not_match? with #using_constraint is not supported',
|
283
|
-
caller[1
|
284
|
+
caller[1..]
|
284
285
|
end
|
285
286
|
|
286
287
|
unless @parameters.nil?
|
287
288
|
raise RuntimeError,
|
288
289
|
'#does_not_match? with #with_parameters is not supported',
|
289
|
-
caller[1
|
290
|
+
caller[1..]
|
290
291
|
end
|
291
292
|
|
292
293
|
return if @parameter_value.nil?
|
293
294
|
|
294
295
|
raise RuntimeError,
|
295
296
|
'#does_not_match? with #with_value is not supported',
|
296
|
-
caller[1
|
297
|
+
caller[1..]
|
297
298
|
end
|
298
299
|
|
299
300
|
def equality_matcher
|
@@ -313,9 +314,9 @@ module Stannum::RSpec
|
|
313
314
|
|
314
315
|
def extra_parameter?
|
315
316
|
extra_arguments_type =
|
316
|
-
Stannum::
|
317
|
+
Stannum::Constraints::Parameters::ExtraArguments::TYPE
|
317
318
|
extra_keywords_type =
|
318
|
-
Stannum::
|
319
|
+
Stannum::Constraints::Parameters::ExtraKeywords::TYPE
|
319
320
|
|
320
321
|
return false unless scoped_errors(indexed: true).any? do |error|
|
321
322
|
error[:type] == extra_arguments_type ||
|
data/lib/stannum/schema.rb
CHANGED
@@ -18,27 +18,6 @@ module Stannum
|
|
18
18
|
@attributes = {}
|
19
19
|
end
|
20
20
|
|
21
|
-
# @!method each
|
22
|
-
# Iterates through the the attributes by name and attribute object.
|
23
|
-
#
|
24
|
-
# @yieldparam name [String] The name of the attribute.
|
25
|
-
# @yieldparam attribute [Stannum::Attribute] The attribute object.
|
26
|
-
|
27
|
-
# @!method each_key
|
28
|
-
# Iterates through the the attributes by name.
|
29
|
-
#
|
30
|
-
# @yieldparam name [String] The name of the attribute.
|
31
|
-
|
32
|
-
# @!method each_value
|
33
|
-
# Iterates through the the attributes by attribute object.
|
34
|
-
#
|
35
|
-
# @yieldparam attribute [Stannum::Attribute] The attribute object.
|
36
|
-
|
37
|
-
def_delegators :attributes,
|
38
|
-
:each,
|
39
|
-
:each_key,
|
40
|
-
:each_value
|
41
|
-
|
42
21
|
# Retrieves the named attribute object.
|
43
22
|
#
|
44
23
|
# @param key [String, Symbol] The name of the requested attribute.
|
@@ -48,9 +27,17 @@ module Stannum
|
|
48
27
|
# @raise ArgumentError if the key is invalid.
|
49
28
|
# @raise KeyError if the attribute is not defined.
|
50
29
|
def [](key)
|
51
|
-
|
30
|
+
tools.assertions.assert_name(key, as: 'key', error_class: ArgumentError)
|
31
|
+
|
32
|
+
str = -key.to_s
|
52
33
|
|
53
|
-
|
34
|
+
each_ancestor do |ancestor|
|
35
|
+
next unless ancestor.own_attributes.key?(str)
|
36
|
+
|
37
|
+
return ancestor.own_attributes[str]
|
38
|
+
end
|
39
|
+
|
40
|
+
{}.fetch(str)
|
54
41
|
end
|
55
42
|
|
56
43
|
# rubocop:disable Metrics/MethodLength
|
@@ -81,33 +68,83 @@ module Stannum
|
|
81
68
|
end
|
82
69
|
# rubocop:enable Metrics/MethodLength
|
83
70
|
|
71
|
+
# Iterates through the the attributes by name and attribute object.
|
72
|
+
#
|
73
|
+
# @yieldparam name [String] The name of the attribute.
|
74
|
+
# @yieldparam attribute [Stannum::Attribute] The attribute object.
|
75
|
+
def each(&block)
|
76
|
+
return enum_for(:each) { size } unless block_given?
|
77
|
+
|
78
|
+
each_ancestor do |ancestor|
|
79
|
+
ancestor.own_attributes.each(&block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Iterates through the the attributes by name.
|
84
|
+
#
|
85
|
+
# @yieldparam name [String] The name of the attribute.
|
86
|
+
def each_key(&block)
|
87
|
+
return enum_for(:each_key) { size } unless block_given?
|
88
|
+
|
89
|
+
each_ancestor do |ancestor|
|
90
|
+
ancestor.own_attributes.each_key(&block)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Iterates through the the attributes by attribute object.
|
95
|
+
#
|
96
|
+
# @yieldparam attribute [Stannum::Attribute] The attribute object.
|
97
|
+
def each_value(&block)
|
98
|
+
return enum_for(:each_value) { size } unless block_given?
|
99
|
+
|
100
|
+
each_ancestor do |ancestor|
|
101
|
+
ancestor.own_attributes.each_value(&block)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
84
105
|
# Checks if the given attribute is defined.
|
85
106
|
#
|
86
107
|
# @param key [String, Symbol] the name of the attribute to check.
|
87
108
|
#
|
88
109
|
# @return [Boolean] true if the attribute is defined; otherwise false.
|
89
110
|
def key?(key)
|
90
|
-
|
111
|
+
tools.assertions.assert_name(key, as: 'key', error_class: ArgumentError)
|
91
112
|
|
92
|
-
|
113
|
+
each_ancestor.any? do |ancestor|
|
114
|
+
ancestor.own_attributes.key?(key.to_s)
|
115
|
+
end
|
93
116
|
end
|
94
117
|
alias has_key? key?
|
95
118
|
|
119
|
+
# Returns the defined attribute keys.
|
120
|
+
#
|
121
|
+
# @return [Array<String>] the attribute keys.
|
122
|
+
def keys
|
123
|
+
each_key.to_a
|
124
|
+
end
|
125
|
+
|
96
126
|
# @private
|
97
127
|
def own_attributes
|
98
128
|
@attributes
|
99
129
|
end
|
100
130
|
|
101
|
-
|
131
|
+
# @return [Integer] the number of defined attributes.
|
132
|
+
def size
|
133
|
+
each_ancestor.reduce(0) do |memo, ancestor|
|
134
|
+
memo + ancestor.own_attributes.size
|
135
|
+
end
|
136
|
+
end
|
137
|
+
alias count size
|
102
138
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
.reduce(&:merge)
|
139
|
+
# Returns the defined attribute value.
|
140
|
+
#
|
141
|
+
# @return [Array<Stannum::Attribute>] the attribute values.
|
142
|
+
def values
|
143
|
+
each_value.to_a
|
109
144
|
end
|
110
145
|
|
146
|
+
private
|
147
|
+
|
111
148
|
def define_reader(attr_name, reader_name)
|
112
149
|
define_method(reader_name) { @attributes[attr_name] }
|
113
150
|
end
|
@@ -118,14 +155,18 @@ module Stannum
|
|
118
155
|
end
|
119
156
|
end
|
120
157
|
|
121
|
-
def
|
122
|
-
|
158
|
+
def each_ancestor
|
159
|
+
return enum_for(:each_ancestor) unless block_given?
|
123
160
|
|
124
|
-
|
125
|
-
|
161
|
+
ancestors.reverse_each do |ancestor|
|
162
|
+
break unless ancestor.is_a?(Stannum::Schema)
|
163
|
+
|
164
|
+
yield ancestor
|
126
165
|
end
|
166
|
+
end
|
127
167
|
|
128
|
-
|
168
|
+
def tools
|
169
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
129
170
|
end
|
130
171
|
end
|
131
172
|
end
|