nrser 0.3.9 → 0.3.10
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/lib/nrser/char/alpha_numeric_sub.rb +9 -19
- data/lib/nrser/char/special.rb +5 -5
- data/lib/nrser/core_ext/array.rb +36 -13
- data/lib/nrser/core_ext/enumerable.rb +1 -0
- data/lib/nrser/core_ext/enumerable/find_map.rb +1 -1
- data/lib/nrser/core_ext/hash/bury.rb +3 -0
- data/lib/nrser/core_ext/hash/extract_values_at.rb +2 -2
- data/lib/nrser/core_ext/method/full_name.rb +1 -1
- data/lib/nrser/core_ext/module/method_objects.rb +1 -1
- data/lib/nrser/core_ext/module/source_locations.rb +27 -15
- data/lib/nrser/core_ext/object/lazy_var.rb +1 -1
- data/lib/nrser/core_ext/pathname.rb +67 -12
- data/lib/nrser/core_ext/pathname/subpath.rb +86 -0
- data/lib/nrser/core_ext/string.rb +28 -1
- data/lib/nrser/core_ext/symbol.rb +11 -12
- data/lib/nrser/errors/README.md +154 -0
- data/lib/nrser/errors/attr_error.rb +146 -53
- data/lib/nrser/errors/count_error.rb +61 -12
- data/lib/nrser/errors/nicer_error.rb +42 -71
- data/lib/nrser/errors/value_error.rb +53 -58
- data/lib/nrser/functions.rb +0 -2
- data/lib/nrser/functions/enumerable.rb +5 -17
- data/lib/nrser/functions/enumerable/associate.rb +14 -5
- data/lib/nrser/functions/enumerable/find_all_map.rb +1 -1
- data/lib/nrser/functions/enumerable/include_slice/array_include_slice.rb +1 -1
- data/lib/nrser/functions/hash/bury.rb +2 -12
- data/lib/nrser/functions/merge_by.rb +2 -2
- data/lib/nrser/functions/module/method_objects.rb +2 -2
- data/lib/nrser/functions/path.rb +185 -165
- data/lib/nrser/functions/path/normalized.rb +84 -0
- data/lib/nrser/functions/string.rb +4 -4
- data/lib/nrser/functions/text/README.md +4 -0
- data/lib/nrser/functions/text/format.rb +53 -0
- data/lib/nrser/functions/text/indentation.rb +6 -6
- data/lib/nrser/functions/text/word_wrap.rb +2 -2
- data/lib/nrser/functions/tree/map_leaves.rb +3 -3
- data/lib/nrser/functions/tree/map_tree.rb +2 -2
- data/lib/nrser/functions/tree/transform.rb +1 -18
- data/lib/nrser/gem_ext/README.md +4 -0
- data/lib/nrser/labs/README.md +8 -0
- data/lib/nrser/labs/config.rb +163 -0
- data/lib/nrser/labs/i8.rb +49 -159
- data/lib/nrser/labs/i8/struct.rb +167 -0
- data/lib/nrser/labs/i8/struct/hash.rb +140 -0
- data/lib/nrser/labs/i8/struct/vector.rb +149 -0
- data/lib/nrser/labs/i8/surjection.rb +211 -0
- data/lib/nrser/labs/lots/consumer.rb +19 -0
- data/lib/nrser/labs/lots/parser.rb +21 -1
- data/lib/nrser/labs/stash.rb +4 -4
- data/lib/nrser/log.rb +25 -21
- data/lib/nrser/log/appender/sync.rb +15 -11
- data/lib/nrser/log/formatters/color.rb +0 -3
- data/lib/nrser/log/formatters/mixin.rb +4 -4
- data/lib/nrser/log/logger.rb +54 -6
- data/lib/nrser/log/mixin.rb +2 -1
- data/lib/nrser/log/plugin.rb +6 -6
- data/lib/nrser/log/types.rb +46 -29
- data/lib/nrser/mean_streak.rb +0 -8
- data/lib/nrser/mean_streak/document.rb +1 -4
- data/lib/nrser/message.rb +3 -3
- data/lib/nrser/meta/README.md +4 -0
- data/lib/nrser/meta/lazy_attr.rb +2 -2
- data/lib/nrser/meta/source/location.rb +1 -1
- data/lib/nrser/props.rb +34 -3
- data/lib/nrser/props/class_methods.rb +2 -1
- data/lib/nrser/props/instance_methods.rb +9 -9
- data/lib/nrser/props/metadata.rb +4 -12
- data/lib/nrser/props/mutable/stash.rb +5 -2
- data/lib/nrser/props/prop.rb +10 -19
- data/lib/nrser/rspex.rb +1 -20
- data/lib/nrser/rspex/example_group/describe_attribute.rb +3 -0
- data/lib/nrser/rspex/example_group/describe_called_with.rb +9 -4
- data/lib/nrser/rspex/example_group/describe_case.rb +1 -0
- data/lib/nrser/rspex/example_group/describe_class.rb +2 -0
- data/lib/nrser/rspex/example_group/describe_group.rb +1 -1
- data/lib/nrser/rspex/example_group/describe_instance.rb +3 -1
- data/lib/nrser/rspex/example_group/describe_message.rb +1 -1
- data/lib/nrser/rspex/example_group/describe_method.rb +64 -30
- data/lib/nrser/rspex/example_group/describe_response_to.rb +1 -1
- data/lib/nrser/rspex/example_group/describe_section.rb +4 -1
- data/lib/nrser/rspex/example_group/describe_sent_to.rb +1 -1
- data/lib/nrser/rspex/example_group/describe_setup.rb +1 -0
- data/lib/nrser/rspex/example_group/describe_source_file.rb +1 -1
- data/lib/nrser/rspex/example_group/describe_spec_file.rb +4 -2
- data/lib/nrser/rspex/example_group/describe_when.rb +2 -1
- data/lib/nrser/rspex/example_group/describe_x.rb +5 -5
- data/lib/nrser/rspex/format.rb +0 -15
- data/lib/nrser/sugar/method_missing_forwarder.rb +3 -3
- data/lib/nrser/sys/env/path.rb +2 -28
- data/lib/nrser/types.rb +63 -12
- data/lib/nrser/types/README.md +76 -0
- data/lib/nrser/types/arrays.rb +192 -137
- data/lib/nrser/types/attributes.rb +269 -0
- data/lib/nrser/types/booleans.rb +134 -83
- data/lib/nrser/types/bounded.rb +110 -47
- data/lib/nrser/types/collections.rb +119 -0
- data/lib/nrser/types/combinators.rb +283 -196
- data/lib/nrser/types/doc/display_table.md +66 -0
- data/lib/nrser/types/eqiuvalent.rb +91 -0
- data/lib/nrser/types/errors/check_error.rb +5 -11
- data/lib/nrser/types/errors/from_string_error.rb +3 -3
- data/lib/nrser/types/factory.rb +287 -20
- data/lib/nrser/types/hashes.rb +227 -179
- data/lib/nrser/types/in.rb +73 -36
- data/lib/nrser/types/is.rb +67 -60
- data/lib/nrser/types/is_a.rb +141 -84
- data/lib/nrser/types/labels.rb +45 -16
- data/lib/nrser/types/maybe.rb +6 -3
- data/lib/nrser/types/nil.rb +64 -27
- data/lib/nrser/types/not.rb +92 -34
- data/lib/nrser/types/numbers.rb +224 -169
- data/lib/nrser/types/pairs.rb +113 -89
- data/lib/nrser/types/paths.rb +250 -137
- data/lib/nrser/types/responds.rb +167 -89
- data/lib/nrser/types/selector.rb +234 -0
- data/lib/nrser/types/shape.rb +136 -65
- data/lib/nrser/types/strings.rb +189 -63
- data/lib/nrser/types/symbols.rb +83 -33
- data/lib/nrser/types/top.rb +89 -0
- data/lib/nrser/types/tuples.rb +134 -98
- data/lib/nrser/types/type.rb +617 -505
- data/lib/nrser/types/when.rb +123 -98
- data/lib/nrser/types/where.rb +182 -91
- data/lib/nrser/version.rb +1 -1
- data/spec/lib/nrser/core_ext/pathname/subpath_spec.rb +22 -0
- data/spec/lib/nrser/errors/attr_error_spec.rb +68 -0
- data/spec/lib/nrser/errors/count_error_spec.rb +69 -0
- data/spec/lib/nrser/functions/path/normalize_path_spec.rb +35 -0
- data/spec/lib/nrser/functions/tree/map_tree_spec.rb +74 -96
- data/spec/lib/nrser/functions/tree/transform_spec.rb +11 -11
- data/spec/lib/nrser/labs/config_spec.rb +22 -0
- data/spec/lib/nrser/labs/i8/struct_spec.rb +39 -0
- data/spec/lib/nrser/types/display_spec.rb +50 -0
- data/spec/lib/nrser/types/paths_spec.rb +16 -10
- data/spec/lib/nrser/types/selector_spec.rb +125 -0
- data/spec/spec_helper.rb +4 -5
- metadata +105 -22
- data/lib/nrser/types/any.rb +0 -41
- data/lib/nrser/types/attrs.rb +0 -213
- data/lib/nrser/types/trees.rb +0 -42
data/lib/nrser/types/bounded.rb
CHANGED
@@ -1,53 +1,116 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Requirements
|
5
|
+
# ========================================================================
|
6
|
+
|
7
|
+
# Project / Package
|
8
|
+
# ------------------------------------------------------------------------
|
9
|
+
|
1
10
|
require 'nrser/types/type'
|
11
|
+
|
12
|
+
|
13
|
+
# Namespace
|
14
|
+
# ========================================================================
|
15
|
+
|
16
|
+
module NRSER
|
17
|
+
module Types
|
18
|
+
|
19
|
+
|
20
|
+
# Definitions
|
21
|
+
# ========================================================================
|
22
|
+
|
23
|
+
# Types whose members satisfy a {#min}, {#max} or both (inclusive).
|
24
|
+
#
|
25
|
+
# @note
|
26
|
+
# Construct {Bounded} types using the {.Bounded} factory.
|
27
|
+
#
|
28
|
+
class Bounded < Type
|
2
29
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
max: nil,
|
22
|
-
**options
|
23
|
-
super **options
|
24
|
-
|
25
|
-
@min = min
|
26
|
-
@max = max
|
27
|
-
end
|
28
|
-
|
29
|
-
def test? value
|
30
|
-
return false if @min && value < @min
|
31
|
-
return false if @max && value > @max
|
32
|
-
true
|
33
|
-
end
|
30
|
+
# Minimum value.
|
31
|
+
#
|
32
|
+
# @return [Number]
|
33
|
+
#
|
34
|
+
attr_reader :min
|
35
|
+
|
36
|
+
|
37
|
+
# Minimum value.
|
38
|
+
#
|
39
|
+
# @return [Number]
|
40
|
+
#
|
41
|
+
attr_reader :max
|
42
|
+
|
43
|
+
|
44
|
+
def initialize min: nil,
|
45
|
+
max: nil,
|
46
|
+
**options
|
47
|
+
super **options
|
34
48
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
49
|
+
@min = min
|
50
|
+
@max = max
|
51
|
+
end
|
52
|
+
|
53
|
+
def test? value
|
54
|
+
return false if @min && value < @min
|
55
|
+
return false if @max && value > @max
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def symbolic
|
60
|
+
if min
|
61
|
+
if max
|
62
|
+
# has min and max, use range notation
|
63
|
+
"(#{ min.inspect }..#{ max.inspect })"
|
64
|
+
else
|
65
|
+
# only has min
|
66
|
+
"(#{ min.inspect }..)"
|
67
|
+
# "{ x : x #{ NRSER::Types::GEQ } #{ min } }"
|
68
|
+
end
|
69
|
+
else
|
70
|
+
# only has max
|
71
|
+
"(..#{ max.inspect })"
|
72
|
+
# "{ x : x #{ NRSER::Types::LEQ } #{ max } }"
|
45
73
|
end
|
46
|
-
|
47
|
-
end # Bounded
|
74
|
+
end
|
48
75
|
|
49
|
-
|
50
|
-
|
76
|
+
def explain
|
77
|
+
attrs_str = ['min', 'max'].map {|name|
|
78
|
+
[name, instance_variable_get("@#{ name }")]
|
79
|
+
}.reject {|name, value|
|
80
|
+
value.nil?
|
81
|
+
}.map {|name, value|
|
82
|
+
"#{ name }=#{ value }"
|
83
|
+
}.join(', ')
|
84
|
+
|
85
|
+
"#{ self.class.demod_name }<#{ attrs_str }>"
|
51
86
|
end
|
52
|
-
|
53
|
-
end #
|
87
|
+
|
88
|
+
end # Bounded
|
89
|
+
|
90
|
+
|
91
|
+
# @!group Bounded Type Factories
|
92
|
+
# ----------------------------------------------------------------------------
|
93
|
+
|
94
|
+
#@!method self.Bounded **options
|
95
|
+
# Create a {Bounded} type instance that matches values between `min` and
|
96
|
+
# `max` (inclusive).
|
97
|
+
#
|
98
|
+
# @param [Hash] options
|
99
|
+
# Passed to {Type#initialize}.
|
100
|
+
#
|
101
|
+
# @return [Type]
|
102
|
+
#
|
103
|
+
def_type :Bounded,
|
104
|
+
parameterize: [ :min, :max ],
|
105
|
+
&->( min: nil, max: nil, **options ) do
|
106
|
+
Bounded.new min: min, max: max, **options
|
107
|
+
end # .Bounded
|
108
|
+
|
109
|
+
# @!endgroup Bounded Type Factories # ****************************************
|
110
|
+
|
111
|
+
|
112
|
+
# /Namespace
|
113
|
+
# ========================================================================
|
114
|
+
|
115
|
+
end # module Types
|
116
|
+
end # module NRSER
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
|
5
|
+
# Requirements
|
6
|
+
# =======================================================================
|
7
|
+
|
8
|
+
# Project / Package
|
9
|
+
# -----------------------------------------------------------------------
|
10
|
+
require_relative './combinators'
|
11
|
+
require_relative './responds'
|
12
|
+
require_relative './is_a'
|
13
|
+
|
14
|
+
|
15
|
+
# Namespace
|
16
|
+
# ========================================================================
|
17
|
+
|
18
|
+
module NRSER
|
19
|
+
module Types
|
20
|
+
|
21
|
+
|
22
|
+
# Definitions
|
23
|
+
# =======================================================================
|
24
|
+
|
25
|
+
# @!group Collection Type Factories
|
26
|
+
# ----------------------------------------------------------------------------
|
27
|
+
|
28
|
+
#@!method self.Vector **options
|
29
|
+
# An "array-like" {Enumerable} that responds to `#each_index` and
|
30
|
+
# `#slice` / `#[]`.
|
31
|
+
#
|
32
|
+
# @param [Hash] options
|
33
|
+
# Passed to {Type#initialize}.
|
34
|
+
#
|
35
|
+
# @return [Type]
|
36
|
+
#
|
37
|
+
def_type :Vector,
|
38
|
+
aliases: [ :array_like ],
|
39
|
+
&->( **options ) do
|
40
|
+
intersection \
|
41
|
+
is_a( Enumerable ),
|
42
|
+
respond_to( :each_index ),
|
43
|
+
respond_to( :slice ),
|
44
|
+
respond_to( :[] ),
|
45
|
+
name: name,
|
46
|
+
**options
|
47
|
+
end # .Vector
|
48
|
+
|
49
|
+
|
50
|
+
#@!method self.Map **options
|
51
|
+
# A "hash-like" {Enumerable} that responds to `#each_pair` and `#[]`.
|
52
|
+
#
|
53
|
+
# @param [Hash] options
|
54
|
+
# Passed to {Type#initialize}.
|
55
|
+
#
|
56
|
+
# @return [Type]
|
57
|
+
#
|
58
|
+
def_type :Map,
|
59
|
+
aliases: [ :hash_like, :assoc ],
|
60
|
+
&->( **options ) do
|
61
|
+
intersection \
|
62
|
+
is_a( Enumerable ),
|
63
|
+
respond_to( :each_pair ),
|
64
|
+
respond_to( :[] ),
|
65
|
+
name: name,
|
66
|
+
**options
|
67
|
+
end # .Map
|
68
|
+
|
69
|
+
|
70
|
+
#@!method self.Bag **options
|
71
|
+
# An {Enumerable} that does **not** respond to `#each_pair`.
|
72
|
+
#
|
73
|
+
# Meant to encompass {Set}, {Array} and the like *without* {Hash} and other
|
74
|
+
# associative containers.
|
75
|
+
#
|
76
|
+
# Elements may or may not be indexed.
|
77
|
+
#
|
78
|
+
# @param [Hash] options
|
79
|
+
# Passed to {Type#initialize}.
|
80
|
+
#
|
81
|
+
# @return [Type]
|
82
|
+
#
|
83
|
+
def_type :Bag,
|
84
|
+
&->( **options ) do
|
85
|
+
intersection \
|
86
|
+
is_a( Enumerable ),
|
87
|
+
self.not( respond_to( :each_pair ) ),
|
88
|
+
name: name,
|
89
|
+
**options
|
90
|
+
end # .Bag
|
91
|
+
|
92
|
+
|
93
|
+
#@!method self.Tree **options
|
94
|
+
# Either a {.Vector} or {.Map} - {Enumerable} collections with indexed
|
95
|
+
# elements that work with the {NRSER} "tree" functions.
|
96
|
+
#
|
97
|
+
# @param [Hash] options
|
98
|
+
# Passed to {Type#initialize}.
|
99
|
+
#
|
100
|
+
# @return [Type]
|
101
|
+
#
|
102
|
+
def_type :Tree,
|
103
|
+
&->( **options ) do
|
104
|
+
union \
|
105
|
+
array_like,
|
106
|
+
hash_like,
|
107
|
+
name: name,
|
108
|
+
**options
|
109
|
+
end # .Tree
|
110
|
+
|
111
|
+
# @!endgroup Collection Type Factories # *************************************
|
112
|
+
|
113
|
+
|
114
|
+
# /Namespace
|
115
|
+
# ========================================================================
|
116
|
+
|
117
|
+
end # module Types
|
118
|
+
end # module NRSER
|
119
|
+
|
@@ -3,226 +3,313 @@
|
|
3
3
|
|
4
4
|
require 'nrser/types/type'
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
|
7
|
+
|
8
|
+
# Namespace
|
9
|
+
# ========================================================================
|
10
|
+
|
11
|
+
module NRSER
|
12
|
+
module Types
|
13
|
+
|
14
|
+
|
15
|
+
# Definitions
|
16
|
+
# ========================================================================
|
17
|
+
|
18
|
+
# Abstract base class for logically combining types to create new ones.
|
19
|
+
#
|
20
|
+
# @see Union
|
21
|
+
# @see Intersection
|
22
|
+
# @see XOR
|
23
|
+
#
|
24
|
+
class Combinator < Type
|
8
25
|
|
9
|
-
#
|
26
|
+
# The parameterized types, in the order they will be tested.
|
10
27
|
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
28
|
+
# @return [Array<NRSER::Types::Type>]
|
29
|
+
#
|
30
|
+
attr_reader :types
|
31
|
+
|
32
|
+
|
33
|
+
def initialize *types, **options
|
34
|
+
super **options
|
35
|
+
@types = types.map { |type| NRSER::Types.make type }.freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def string_format method
|
40
|
+
NRSER::Types::L_PAREN +
|
41
|
+
# ' ' + no spaces
|
42
|
+
@types.map { |type| type.send method }.join( self.class::JOIN_SYMBOL ) +
|
43
|
+
# ' ' + no spaces
|
44
|
+
NRSER::Types::R_PAREN
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def default_symbolic
|
49
|
+
string_format( :to_s )
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def explain
|
54
|
+
return string_format( :explain )
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# Parse a satisfying value from a {String} or raise a {TypeError}.
|
59
|
+
#
|
60
|
+
# If this type has it's own `@from_s` that was provided via the `from_s:`
|
61
|
+
# keyword at construction, then that and **only** that is **always** used
|
62
|
+
# - the type will never try any of the combined types' `#from_s`.
|
63
|
+
#
|
64
|
+
# It's considered *the* way to parse a string into a value that satisfies
|
65
|
+
# the type. If it fails, a {TypeError} will be raised (or any error the
|
66
|
+
# `@from_s` proc itself raises before we get to checking it).
|
67
|
+
#
|
68
|
+
# If the type doesn't have it's own `@from_s`, each of the combined types'
|
69
|
+
# `#from_s` will be tried in sequence, and the first value that satisfies
|
70
|
+
# the combined type will be returned.
|
71
|
+
#
|
72
|
+
# This is obviously much less efficient, but provides a nice automatic
|
73
|
+
# mechanism for parsing from strings for combined types. If none of the
|
74
|
+
# combined types' `#from_s` succeed (or if there are none) a {TypeError}
|
75
|
+
# is raised.
|
76
|
+
#
|
77
|
+
# @param [String] s
|
78
|
+
# String to parse.
|
79
|
+
#
|
80
|
+
# @return [Object]
|
81
|
+
# Object that satisfies the type.
|
82
|
+
#
|
83
|
+
# @raise [TypeError]
|
84
|
+
# See write up above.
|
85
|
+
#
|
86
|
+
def custom_from_s string
|
87
|
+
# If we have an explicit `@from_s` then use that and that only.
|
88
|
+
unless @from_s.nil?
|
89
|
+
return check! @from_s.call( string )
|
23
90
|
end
|
91
|
+
|
92
|
+
errors_by_type = {}
|
24
93
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
|
94
|
+
types.each { |type|
|
95
|
+
if type.has_from_s?
|
96
|
+
begin
|
97
|
+
return check! type.from_s( string )
|
98
|
+
|
99
|
+
# We want to catch any standard error here so `from_s` implementations
|
100
|
+
# can kinda "code without care" and if one fails we will move on to
|
101
|
+
# try the next.
|
102
|
+
rescue StandardError => error
|
103
|
+
errors_by_type[type] = error
|
104
|
+
end
|
32
105
|
else
|
33
|
-
"
|
34
|
-
@types.map { |type| type.explain }.join( ',' ) +
|
35
|
-
">"
|
106
|
+
errors_by_type[type] = "Does not {#has_from_s?}"
|
36
107
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# Parse a satisfying value from a {String} or raise a {TypeError}.
|
41
|
-
#
|
42
|
-
# If this type has it's own `@from_s` that was provided via the `from_s:`
|
43
|
-
# keyword at construction, then that and **only** that is **always** used
|
44
|
-
# - the type will never try any of the combined types' `#from_s`.
|
45
|
-
#
|
46
|
-
# It's considered *the* way to parse a string into a value that satisfies
|
47
|
-
# the type. If it fails, a {TypeError} will be raised (or any error the
|
48
|
-
# `@from_s` proc itself raises before we get to checking it).
|
49
|
-
#
|
50
|
-
# If the type doesn't have it's own `@from_s`, each of the combined types'
|
51
|
-
# `#from_s` will be tried in sequence, and the first value that satisfies
|
52
|
-
# the combined type will be returned.
|
53
|
-
#
|
54
|
-
# This is obviously much less efficient, but provides a nice automatic
|
55
|
-
# mechanism for parsing from strings for combined types. If none of the
|
56
|
-
# combined types' `#from_s` succeed (or if there are none) a {TypeError}
|
57
|
-
# is raised.
|
58
|
-
#
|
59
|
-
# @param [String] s
|
60
|
-
# String to parse.
|
61
|
-
#
|
62
|
-
# @return [Object]
|
63
|
-
# Object that satisfies the type.
|
64
|
-
#
|
65
|
-
# @raise [TypeError]
|
66
|
-
# See write up above.
|
67
|
-
#
|
68
|
-
def custom_from_s s
|
69
|
-
unless @from_s.nil?
|
70
|
-
return check @from_s.call( s )
|
71
|
-
end
|
72
|
-
|
73
|
-
@types.each { |type|
|
74
|
-
if type.has_from_s?
|
75
|
-
begin
|
76
|
-
return check type.from_s(s)
|
77
|
-
rescue TypeError => e
|
78
|
-
# pass
|
79
|
-
end
|
80
|
-
end
|
81
|
-
}
|
82
|
-
|
83
|
-
raise TypeError,
|
84
|
-
"none of combinator #{ self.to_s } types could convert #{ s.inspect }"
|
85
|
-
end
|
86
|
-
|
108
|
+
}
|
87
109
|
|
88
|
-
#
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
110
|
+
# TODO This should be "nicer"... teach {NRSER::MultipleErrors} about
|
111
|
+
# {NRSER::NicerError}?
|
112
|
+
raise TypeError.new \
|
113
|
+
"none of combinator", self.to_s, "types could convert", string.inspect,
|
114
|
+
string: string,
|
115
|
+
errors_by_type: errors_by_type
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
# Overridden to delegate functionality to the combined types.
|
120
|
+
#
|
121
|
+
# A combinator may attempt to parse from a string if:
|
122
|
+
#
|
123
|
+
# 1. It has it's own `@from_s` provided at construction.
|
124
|
+
#
|
125
|
+
# 2. Any of it's combined types can parse from a string.
|
126
|
+
#
|
127
|
+
# See {#from_s} for details of how it actually happens.
|
128
|
+
#
|
129
|
+
# @return [Boolean]
|
130
|
+
#
|
131
|
+
def has_from_s?
|
132
|
+
!@from_s.nil? || @types.any? { |type| type.has_from_s? }
|
133
|
+
end # has_from_s
|
134
|
+
|
135
|
+
|
136
|
+
def has_from_data?
|
137
|
+
@types.any? { |type| type.has_from_data? }
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# Overridden to
|
142
|
+
def from_data data
|
143
|
+
unless has_from_data?
|
144
|
+
raise NoMethodError, "#from_data not defined"
|
107
145
|
end
|
108
146
|
|
147
|
+
errors = []
|
109
148
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
errors = []
|
117
|
-
|
118
|
-
types.each do |type|
|
119
|
-
if type.has_from_data?
|
120
|
-
begin
|
121
|
-
return check!( type.from_data data )
|
122
|
-
rescue StandardError => error
|
123
|
-
errors << error
|
124
|
-
end
|
149
|
+
types.each do |type|
|
150
|
+
if type.has_from_data?
|
151
|
+
begin
|
152
|
+
return check!( type.from_data data )
|
153
|
+
rescue StandardError => error
|
154
|
+
errors << error
|
125
155
|
end
|
126
156
|
end
|
127
|
-
|
128
|
-
raise NRSER::MultipleErrors.new \
|
129
|
-
errors,
|
130
|
-
headline: "No type successfully loaded data"
|
131
157
|
end
|
132
158
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
#
|
138
|
-
# @return [Boolean]
|
139
|
-
#
|
140
|
-
def has_to_data?
|
141
|
-
@types.any? { |type| type.has_to_data? }
|
142
|
-
end # #has_to_data
|
143
|
-
|
144
|
-
|
145
|
-
# Overridden to delegate functionality to the combined types:
|
146
|
-
#
|
147
|
-
# The first of the combined types that responds to `#to_data` is used to
|
148
|
-
# dump the value.
|
149
|
-
#
|
150
|
-
# @param [Object] value
|
151
|
-
# Value of this type (though it is *not* checked).
|
152
|
-
#
|
153
|
-
# @return [Object]
|
154
|
-
# The data representation of the value.
|
155
|
-
#
|
156
|
-
def to_data value
|
157
|
-
@types.each { |type|
|
158
|
-
if type.has_to_data?
|
159
|
-
return type.to_data value
|
160
|
-
end
|
161
|
-
}
|
162
|
-
|
163
|
-
raise NoMethodError, "#to_data not defined"
|
164
|
-
end # #to_data
|
165
|
-
|
166
|
-
|
167
|
-
def == other
|
168
|
-
equal?(other) || (
|
169
|
-
other.class == self.class && other.types == @types
|
170
|
-
)
|
171
|
-
end
|
172
|
-
|
173
|
-
end # class Combinator
|
174
|
-
|
175
|
-
|
176
|
-
class Union < Combinator
|
177
|
-
JOIN_SYMBOL = ' | ' # ' ⋁ '
|
178
|
-
|
179
|
-
def test? value
|
180
|
-
@types.any? { |type| type.test value }
|
181
|
-
end
|
182
|
-
end # class Union
|
159
|
+
raise NRSER::MultipleErrors.new \
|
160
|
+
errors,
|
161
|
+
headline: "No type successfully loaded data"
|
162
|
+
end
|
183
163
|
|
184
164
|
|
185
|
-
#
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
165
|
+
# Overridden to delegate functionality to the combined types:
|
166
|
+
#
|
167
|
+
# A combinator can convert a value to data if *any* of it's types can.
|
168
|
+
#
|
169
|
+
# @return [Boolean]
|
170
|
+
#
|
171
|
+
def has_to_data?
|
172
|
+
@types.any? { |type| type.has_to_data? }
|
173
|
+
end # #has_to_data
|
192
174
|
|
193
175
|
|
194
|
-
|
195
|
-
|
176
|
+
# Overridden to delegate functionality to the combined types:
|
177
|
+
#
|
178
|
+
# The first of the combined types that responds to `#to_data` is used to
|
179
|
+
# dump the value.
|
180
|
+
#
|
181
|
+
# @param [Object] value
|
182
|
+
# Value of this type (though it is *not* checked).
|
183
|
+
#
|
184
|
+
# @return [Object]
|
185
|
+
# The data representation of the value.
|
186
|
+
#
|
187
|
+
def to_data value
|
188
|
+
@types.each { |type|
|
189
|
+
if type.has_to_data?
|
190
|
+
return type.to_data value
|
191
|
+
end
|
192
|
+
}
|
196
193
|
|
197
|
-
|
198
|
-
|
199
|
-
end
|
200
|
-
end # class Intersection
|
194
|
+
raise NoMethodError, "#to_data not defined"
|
195
|
+
end # #to_data
|
201
196
|
|
202
197
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
) do |*types, **options|
|
208
|
-
Intersection.new *types, **options
|
198
|
+
def == other
|
199
|
+
equal?(other) || (
|
200
|
+
other.class == self.class && other.types == @types
|
201
|
+
)
|
209
202
|
end
|
203
|
+
|
204
|
+
end # class Combinator *****************************************************
|
205
|
+
|
206
|
+
|
207
|
+
# Concrete Implementation Classes
|
208
|
+
# ----------------------------------------------------------------------------
|
209
|
+
|
210
|
+
|
211
|
+
# Union combinator. (`union`, `one_of`, `or`, `|`).
|
212
|
+
#
|
213
|
+
class Union < Combinator
|
214
|
+
JOIN_SYMBOL = ' | ' # ' ⋁ '
|
210
215
|
|
211
|
-
|
212
|
-
|
213
|
-
JOIN_SYMBOL = ' ⊕ '
|
214
|
-
|
215
|
-
def test? value
|
216
|
-
@types.count { |type| type === value } == 1
|
217
|
-
end
|
216
|
+
def test? value
|
217
|
+
@types.any? { |type| type.test value }
|
218
218
|
end
|
219
|
+
end # class Union
|
220
|
+
|
221
|
+
|
222
|
+
# Intersection combinator (`intersection`, `all_of`, `and`, `&).
|
223
|
+
#
|
224
|
+
class Intersection < Combinator
|
225
|
+
JOIN_SYMBOL = ' & ' # ' ⋀ '
|
219
226
|
|
220
|
-
|
221
|
-
|
222
|
-
:xor,
|
223
|
-
aliases: [ :exclusive_or, :only_one_of ],
|
224
|
-
) do |*types, **options|
|
225
|
-
XOR.new *types, **options
|
227
|
+
def test? value
|
228
|
+
@types.all? { |type| type.test? value }
|
226
229
|
end
|
230
|
+
end # class Intersection
|
231
|
+
|
232
|
+
|
233
|
+
# XOR combinator - Exclusive Or (`xor`).
|
234
|
+
#
|
235
|
+
class XOR < Combinator
|
236
|
+
JOIN_SYMBOL = ' ⊕ '
|
227
237
|
|
228
|
-
|
238
|
+
def test? value
|
239
|
+
@types.count { |type| type === value } == 1
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
# @!group Combinator Type Factories
|
245
|
+
# ----------------------------------------------------------------------------
|
246
|
+
|
247
|
+
|
248
|
+
#@!method self.Union *types, **options
|
249
|
+
# Match any of the types.
|
250
|
+
#
|
251
|
+
# @param [Type | Object] types
|
252
|
+
# Types to combine over. Objects that are not {Type} instances will me
|
253
|
+
# made into them via {.make}.
|
254
|
+
#
|
255
|
+
# @param [Hash] options
|
256
|
+
# Passed to {Type#initialize}.
|
257
|
+
#
|
258
|
+
# @return [Type]
|
259
|
+
#
|
260
|
+
def_type :Union,
|
261
|
+
aliases: [ :one_of, :or ],
|
262
|
+
parameterize: [ :types ],
|
263
|
+
&->( *types, **options ) do
|
264
|
+
Union.new *types, **options
|
265
|
+
end # .Union
|
266
|
+
|
267
|
+
|
268
|
+
#@!method self.Intersection *types, **options
|
269
|
+
# Match all of the types
|
270
|
+
#
|
271
|
+
# @param [Type | Object] types
|
272
|
+
# Types to combine over. Objects that are not {Type} instances will me
|
273
|
+
# made into them via {.make}.
|
274
|
+
#
|
275
|
+
# @param [Hash] options
|
276
|
+
# Passed to {Type#initialize}.
|
277
|
+
#
|
278
|
+
# @return [Type]
|
279
|
+
#
|
280
|
+
def_type :Intersection,
|
281
|
+
aliases: [ :all_of, :and ],
|
282
|
+
parameterize: [ :types ],
|
283
|
+
&->( *types, **options ) do
|
284
|
+
Intersection.new *types, **options
|
285
|
+
end # .Intersection
|
286
|
+
|
287
|
+
|
288
|
+
#@!method self.XOR *types, **options
|
289
|
+
# Match one of the types only.
|
290
|
+
#
|
291
|
+
# @param [Type | Object] types
|
292
|
+
# Types to combine over. Objects that are not {Type} instances will me
|
293
|
+
# made into them via {.make}.
|
294
|
+
#
|
295
|
+
# @param [Hash] options
|
296
|
+
# Passed to {Type#initialize}.
|
297
|
+
#
|
298
|
+
# @return [Type]
|
299
|
+
#
|
300
|
+
def_type :XOR,
|
301
|
+
aliases: [ :exclusive_or, :only_one_of ],
|
302
|
+
parameterize: [ :types ],
|
303
|
+
&->( *types, **options ) do
|
304
|
+
XOR.new *types, **options
|
305
|
+
end # .XOR
|
306
|
+
|
307
|
+
# @!endgroup Combinator Type Factories # *************************************
|
308
|
+
|
309
|
+
|
310
|
+
# /Namespace
|
311
|
+
# ========================================================================
|
312
|
+
|
313
|
+
end # module Types
|
314
|
+
end # module NRSER
|
315
|
+
|