nrser 0.3.9 → 0.3.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|