nrser 0.0.25 → 0.0.26
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/README.md +39 -11
- data/lib/nrser.rb +5 -1
- data/lib/nrser/array.rb +10 -53
- data/lib/nrser/enumerable.rb +21 -0
- data/lib/nrser/hash.rb +13 -476
- data/lib/nrser/hash/bury.rb +154 -0
- data/lib/nrser/hash/deep_merge.rb +57 -0
- data/lib/nrser/hash/except_keys.rb +42 -0
- data/lib/nrser/hash/guess_label_key_type.rb +37 -0
- data/lib/nrser/hash/slice_keys.rb +41 -0
- data/lib/nrser/hash/stringify_keys.rb +37 -0
- data/lib/nrser/hash/symbolize_keys.rb +41 -0
- data/lib/nrser/hash/transform_keys.rb +45 -0
- data/lib/nrser/merge_by.rb +26 -0
- data/lib/nrser/message.rb +125 -0
- data/lib/nrser/meta/props.rb +2 -2
- data/lib/nrser/meta/props/prop.rb +5 -2
- data/lib/nrser/object.rb +5 -0
- data/lib/nrser/object/as_array.rb +37 -0
- data/lib/nrser/object/as_hash.rb +101 -0
- data/lib/nrser/{truthy.rb → object/truthy.rb} +0 -0
- data/lib/nrser/proc.rb +132 -0
- data/lib/nrser/refinements.rb +1 -2
- data/lib/nrser/refinements/array.rb +94 -5
- data/lib/nrser/refinements/enumerable.rb +5 -0
- data/lib/nrser/refinements/hash.rb +43 -6
- data/lib/nrser/refinements/object.rb +22 -2
- data/lib/nrser/refinements/symbol.rb +12 -0
- data/lib/nrser/refinements/tree.rb +41 -0
- data/lib/nrser/rspex.rb +329 -0
- data/lib/nrser/string.rb +3 -0
- data/lib/nrser/string/looks_like.rb +51 -0
- data/lib/nrser/temp/where.rb +52 -0
- data/lib/nrser/tree.rb +86 -0
- data/lib/nrser/tree/leaves.rb +92 -0
- data/lib/nrser/tree/map_leaves.rb +63 -0
- data/lib/nrser/tree/transform.rb +30 -0
- data/lib/nrser/types.rb +9 -4
- data/lib/nrser/types/any.rb +1 -1
- data/lib/nrser/types/array.rb +167 -25
- data/lib/nrser/types/{hash.rb → hashes.rb} +19 -5
- data/lib/nrser/types/in.rb +47 -0
- data/lib/nrser/types/is_a.rb +2 -2
- data/lib/nrser/types/labels.rb +49 -0
- data/lib/nrser/types/numbers.rb +63 -27
- data/lib/nrser/types/pairs.rb +109 -0
- data/lib/nrser/types/responds.rb +2 -3
- data/lib/nrser/types/strings.rb +17 -18
- data/lib/nrser/types/symbols.rb +39 -0
- data/lib/nrser/types/trees.rb +93 -0
- data/lib/nrser/types/tuples.rb +116 -0
- data/lib/nrser/types/type.rb +26 -2
- data/lib/nrser/version.rb +1 -1
- data/spec/nrser/hash/{guess_name_type_spec.rb → guess_label_key_type_spec.rb} +3 -3
- data/spec/nrser/hash_spec.rb +0 -20
- data/spec/nrser/merge_by_spec.rb +73 -0
- data/spec/nrser/meta/props_spec.rb +136 -43
- data/spec/nrser/op/message_spec.rb +62 -0
- data/spec/nrser/refinements/array_spec.rb +36 -0
- data/spec/nrser/refinements/hash_spec.rb +34 -0
- data/spec/nrser/string/looks_like_spec.rb +31 -0
- data/spec/nrser/tree/each_branch_spec.rb +82 -0
- data/spec/nrser/tree/leaves_spec.rb +112 -0
- data/spec/nrser/tree/transform_spec.rb +165 -0
- data/spec/nrser/types/array_spec.rb +82 -0
- data/spec/nrser/types/attrs_spec.rb +4 -4
- data/spec/nrser/types/pairs_spec.rb +41 -0
- data/spec/nrser/types/paths_spec.rb +3 -3
- data/spec/nrser/types/strings_spec.rb +66 -0
- data/spec/nrser/types/symbols_spec.rb +38 -0
- data/spec/nrser/types/tuples_spec.rb +37 -0
- data/spec/nrser/types_spec.rb +0 -13
- data/spec/spec_helper.rb +71 -22
- metadata +58 -10
- data/lib/nrser/spex.rb +0 -68
- data/lib/nrser/types/symbol.rb +0 -23
@@ -14,8 +14,7 @@ module NRSER::Types
|
|
14
14
|
super ::Hash, **options
|
15
15
|
|
16
16
|
@keys = NRSER::Types.make keys
|
17
|
-
@values = NRSER::Types.make
|
18
|
-
|
17
|
+
@values = NRSER::Types.make values
|
19
18
|
end
|
20
19
|
|
21
20
|
def test value
|
@@ -31,9 +30,20 @@ module NRSER::Types
|
|
31
30
|
end
|
32
31
|
end # HashType
|
33
32
|
|
34
|
-
HASH = HashType.new.freeze
|
35
33
|
|
36
|
-
|
34
|
+
|
35
|
+
# Type satisfied by {Hash} instances.
|
36
|
+
#
|
37
|
+
# @param [Array] *args
|
38
|
+
# Passed to {NRSER::Types::HashType#initialize} unless empty.
|
39
|
+
#
|
40
|
+
# @return [NRSER::Types::HASH]
|
41
|
+
# If `args` are empty.
|
42
|
+
#
|
43
|
+
# @return [NRSER::Types::Type]
|
44
|
+
# Newly constructed hash type from `args`.
|
45
|
+
#
|
46
|
+
def self.hash_type *args
|
37
47
|
if args.empty?
|
38
48
|
HASH
|
39
49
|
else
|
@@ -41,5 +51,9 @@ module NRSER::Types
|
|
41
51
|
end
|
42
52
|
end
|
43
53
|
|
44
|
-
singleton_class.send :alias_method, :dict, :
|
54
|
+
singleton_class.send :alias_method, :dict, :hash_type
|
55
|
+
singleton_class.send :alias_method, :hash_, :hash_type
|
56
|
+
|
57
|
+
HASH = HashType.new.freeze
|
58
|
+
|
45
59
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Requirements
|
2
|
+
# =======================================================================
|
3
|
+
|
4
|
+
# Stdlib
|
5
|
+
# -----------------------------------------------------------------------
|
6
|
+
|
7
|
+
# Deps
|
8
|
+
# -----------------------------------------------------------------------
|
9
|
+
|
10
|
+
# Project / Package
|
11
|
+
# -----------------------------------------------------------------------
|
12
|
+
require_relative './where'
|
13
|
+
|
14
|
+
|
15
|
+
# Refinements
|
16
|
+
# =======================================================================
|
17
|
+
|
18
|
+
require 'nrser/refinements'
|
19
|
+
using NRSER
|
20
|
+
|
21
|
+
|
22
|
+
# Declarations
|
23
|
+
# =======================================================================
|
24
|
+
|
25
|
+
module NRSER; end
|
26
|
+
|
27
|
+
|
28
|
+
# Definitions
|
29
|
+
# =======================================================================
|
30
|
+
|
31
|
+
module NRSER::Types
|
32
|
+
|
33
|
+
# Type that tests value for membership in a group object via that object's
|
34
|
+
# `#include?` method.
|
35
|
+
#
|
36
|
+
# @param [#include?] group
|
37
|
+
# `#include?` will be called on this value to determine type membership.
|
38
|
+
#
|
39
|
+
# @return [NRSER::Types::Type]
|
40
|
+
#
|
41
|
+
def self.in group, **options
|
42
|
+
where( name: "In(#{ group })", **options ) { |value|
|
43
|
+
group.include? value
|
44
|
+
}
|
45
|
+
end # .in
|
46
|
+
|
47
|
+
end # module NRSER::Types
|
data/lib/nrser/types/is_a.rb
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Requirements
|
2
|
+
# =======================================================================
|
3
|
+
|
4
|
+
# Stdlib
|
5
|
+
# -----------------------------------------------------------------------
|
6
|
+
|
7
|
+
# Deps
|
8
|
+
# -----------------------------------------------------------------------
|
9
|
+
|
10
|
+
# Project / Package
|
11
|
+
# -----------------------------------------------------------------------
|
12
|
+
|
13
|
+
|
14
|
+
# Refinements
|
15
|
+
# =======================================================================
|
16
|
+
|
17
|
+
require 'nrser/refinements'
|
18
|
+
using NRSER
|
19
|
+
|
20
|
+
|
21
|
+
# Declarations
|
22
|
+
# =======================================================================
|
23
|
+
|
24
|
+
module NRSER; end
|
25
|
+
|
26
|
+
|
27
|
+
# Definitions
|
28
|
+
# =======================================================================
|
29
|
+
|
30
|
+
module NRSER::Types
|
31
|
+
|
32
|
+
# A label is a non-empty {String} or {Symbol}.
|
33
|
+
#
|
34
|
+
# @param [Hash] **options
|
35
|
+
# Options to pass to {NRSER::Types::Type#initialize}.
|
36
|
+
#
|
37
|
+
# @return [NRSER::Types::Type]
|
38
|
+
#
|
39
|
+
def self.label **options
|
40
|
+
if options.empty?
|
41
|
+
LABEL
|
42
|
+
else
|
43
|
+
union non_empty_str, non_empty_sym, **options
|
44
|
+
end
|
45
|
+
end # .label
|
46
|
+
|
47
|
+
LABEL = label( name: 'LabelType' ).freeze
|
48
|
+
|
49
|
+
end # module NRSER::Types
|
data/lib/nrser/types/numbers.rb
CHANGED
@@ -7,91 +7,127 @@ require 'nrser/types/bounded'
|
|
7
7
|
using NRSER
|
8
8
|
|
9
9
|
module NRSER::Types
|
10
|
+
# Parse a string into a number.
|
11
|
+
#
|
12
|
+
# @return [Integer]
|
13
|
+
# If the string represents a whole integer.
|
14
|
+
#
|
15
|
+
# @return [Float]
|
16
|
+
# If the string represents a decimal number.
|
17
|
+
#
|
10
18
|
def self.parse_number s
|
11
|
-
float = s
|
19
|
+
float = Float s
|
12
20
|
int = float.to_i
|
13
21
|
if float == int then int else float end
|
14
22
|
end
|
15
23
|
|
16
|
-
# zero
|
17
|
-
# ====
|
18
24
|
|
19
|
-
|
25
|
+
# Zero
|
26
|
+
# =====================================================================
|
27
|
+
|
28
|
+
ZERO = is(
|
29
|
+
0,
|
30
|
+
name: 'ZeroType',
|
31
|
+
from_s: method( :parse_number )
|
32
|
+
).freeze
|
20
33
|
|
21
34
|
def self.zero
|
22
35
|
ZERO
|
23
36
|
end
|
24
37
|
|
25
|
-
# number (Numeric)
|
26
|
-
# ================
|
27
38
|
|
28
|
-
|
39
|
+
# Number ({Numeric})
|
40
|
+
# =====================================================================
|
41
|
+
|
42
|
+
NUM = IsA.new(
|
43
|
+
Numeric,
|
44
|
+
name: 'NumType',
|
45
|
+
from_s: method( :parse_number )
|
46
|
+
).freeze
|
29
47
|
|
30
48
|
def self.num
|
31
49
|
NUM
|
32
50
|
end
|
33
51
|
|
34
|
-
|
35
|
-
|
52
|
+
singleton_class.send :alias_method, :number, :num
|
53
|
+
|
54
|
+
|
55
|
+
# Integers
|
56
|
+
# =====================================================================
|
36
57
|
|
37
|
-
INT = IsA.new
|
58
|
+
INT = IsA.new(
|
59
|
+
Integer,
|
60
|
+
name: 'IntType',
|
61
|
+
from_s: method( :parse_number )
|
62
|
+
).freeze
|
38
63
|
|
39
64
|
def self.int
|
40
65
|
INT
|
41
66
|
end
|
42
67
|
|
43
|
-
|
44
|
-
int
|
45
|
-
end
|
68
|
+
singleton_class.send :alias_method, :integer, :int
|
46
69
|
|
47
|
-
# bounded integers
|
48
|
-
# ================
|
49
|
-
#
|
50
70
|
|
51
|
-
#
|
71
|
+
# Bounded Integers
|
72
|
+
# ---------------------------------------------------------------------
|
73
|
+
|
74
|
+
# Positive Integer
|
52
75
|
# ----------------
|
53
76
|
#
|
54
|
-
#
|
77
|
+
# Integer greater than zero.
|
55
78
|
#
|
56
79
|
|
57
|
-
POS_INT = intersection
|
80
|
+
POS_INT = intersection(
|
81
|
+
INT,
|
82
|
+
bounded(min: 1),
|
83
|
+
name: 'PosIntType'
|
84
|
+
).freeze
|
58
85
|
|
59
86
|
def self.pos_int
|
60
87
|
POS_INT
|
61
88
|
end
|
62
89
|
|
63
|
-
|
90
|
+
|
91
|
+
# Negative Integer
|
64
92
|
# ----------------
|
65
93
|
#
|
66
|
-
#
|
94
|
+
# Integer less than zero.
|
67
95
|
#
|
68
96
|
|
69
|
-
NEG_INT = intersection
|
97
|
+
NEG_INT = intersection(
|
98
|
+
INT,
|
99
|
+
bounded(max: -1),
|
100
|
+
name: 'NegIntType'
|
101
|
+
).freeze
|
70
102
|
|
71
103
|
def self.neg_int
|
72
104
|
NEG_INT
|
73
105
|
end
|
74
106
|
|
75
|
-
|
107
|
+
|
108
|
+
# Non-Negative Integer
|
76
109
|
# --------------------
|
77
110
|
#
|
78
|
-
#
|
111
|
+
# Positive integers and zero... but it seems more efficient to define these
|
79
112
|
# as bounded instead of a union.
|
80
113
|
#
|
81
114
|
|
82
|
-
NON_NEG_INT = intersection INT, bounded(min: 0), name: '
|
115
|
+
NON_NEG_INT = intersection INT, bounded(min: 0), name: 'NonNegIntType'
|
83
116
|
|
84
117
|
def self.non_neg_int
|
85
118
|
NON_NEG_INT
|
86
119
|
end
|
87
120
|
|
88
|
-
|
121
|
+
singleton_class.send :alias_method, :unsigned, :non_neg_int
|
122
|
+
|
123
|
+
|
124
|
+
# Non-Positive Integer
|
89
125
|
# --------------------
|
90
126
|
#
|
91
127
|
# negative integers and zero.
|
92
128
|
#
|
93
129
|
|
94
|
-
NON_POS_INT = intersection INT, bounded(max: 0), name: '
|
130
|
+
NON_POS_INT = intersection INT, bounded(max: 0), name: 'NonPosIntType'
|
95
131
|
|
96
132
|
def self.non_pos_int
|
97
133
|
NON_POS_INT
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# Requirements
|
2
|
+
# =======================================================================
|
3
|
+
|
4
|
+
# Stdlib
|
5
|
+
# -----------------------------------------------------------------------
|
6
|
+
|
7
|
+
# Deps
|
8
|
+
# -----------------------------------------------------------------------
|
9
|
+
|
10
|
+
# Project / Package
|
11
|
+
# -----------------------------------------------------------------------
|
12
|
+
require_relative './combinators'
|
13
|
+
|
14
|
+
|
15
|
+
# Refinements
|
16
|
+
# =======================================================================
|
17
|
+
|
18
|
+
|
19
|
+
# Declarations
|
20
|
+
# =======================================================================
|
21
|
+
|
22
|
+
module NRSER; end
|
23
|
+
|
24
|
+
|
25
|
+
# Definitions
|
26
|
+
# =======================================================================
|
27
|
+
|
28
|
+
module NRSER::Types
|
29
|
+
|
30
|
+
|
31
|
+
# @todo Document array_pair method.
|
32
|
+
#
|
33
|
+
# @param [type] arg_name
|
34
|
+
# @todo Add name param description.
|
35
|
+
#
|
36
|
+
# @return [return_type]
|
37
|
+
# @todo Document return value.
|
38
|
+
#
|
39
|
+
def self.array_pair **options
|
40
|
+
return ARRAY_PAIR if options.empty?
|
41
|
+
|
42
|
+
key = options.delete(:key) || ANY
|
43
|
+
value = options.delete(:value) || ANY
|
44
|
+
|
45
|
+
tuple key, value, **options
|
46
|
+
end # .array_pair
|
47
|
+
|
48
|
+
ARRAY_PAIR = array_pair( name: 'ArrayPairType' ).freeze
|
49
|
+
|
50
|
+
|
51
|
+
# Type for a {Hash} that consists of only a single key and value pair.
|
52
|
+
#
|
53
|
+
# @param [String] name:
|
54
|
+
# Name to give the new type.
|
55
|
+
#
|
56
|
+
# @param [Hash] **options
|
57
|
+
# Other options to pass to
|
58
|
+
#
|
59
|
+
# @return [NRSER::Types::Type]
|
60
|
+
#
|
61
|
+
def self.hash_pair **options
|
62
|
+
return HASH_PAIR if options.empty?
|
63
|
+
|
64
|
+
hash_options = {}
|
65
|
+
{key: :keys, value: :values}.each { |from_key, to_key|
|
66
|
+
if options.key? from_key
|
67
|
+
hash_options[to_key] = options.delete from_key
|
68
|
+
end
|
69
|
+
}
|
70
|
+
|
71
|
+
if hash_options.empty?
|
72
|
+
intersection is_a( Hash ), length( 1 ), **options
|
73
|
+
else
|
74
|
+
intersection \
|
75
|
+
hash_type( **hash_options ),
|
76
|
+
length( 1 ),
|
77
|
+
**options
|
78
|
+
end
|
79
|
+
|
80
|
+
end # .hash_pair
|
81
|
+
|
82
|
+
HASH_PAIR = hash_pair( name: 'HashPairType' ).freeze
|
83
|
+
|
84
|
+
|
85
|
+
# @todo Document pair method.
|
86
|
+
#
|
87
|
+
# @param [type] arg_name
|
88
|
+
# @todo Add name param description.
|
89
|
+
#
|
90
|
+
# @return [return_type]
|
91
|
+
# @todo Document return value.
|
92
|
+
#
|
93
|
+
def self.pair **options
|
94
|
+
if options.empty?
|
95
|
+
PAIR
|
96
|
+
else
|
97
|
+
type_options = NRSER.slice_keys! options, :key, :value
|
98
|
+
|
99
|
+
union \
|
100
|
+
array_pair( **type_options ),
|
101
|
+
hash_pair( **type_options ),
|
102
|
+
**options
|
103
|
+
end
|
104
|
+
end # #pair
|
105
|
+
|
106
|
+
PAIR = pair( name: 'PairType' ).freeze
|
107
|
+
|
108
|
+
end # module NRSER::Types
|
109
|
+
|
data/lib/nrser/types/responds.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
|
2
|
-
# @todo document NRSER::Types module.
|
3
2
|
module NRSER::Types
|
4
3
|
|
5
|
-
#
|
4
|
+
# Type that encodes messages mapped to result types that member values must
|
5
|
+
# satisfy.
|
6
6
|
class Responds < NRSER::Types::Type
|
7
7
|
|
8
8
|
# Constants
|
@@ -87,7 +87,6 @@ module NRSER::Types
|
|
87
87
|
end # #responds
|
88
88
|
|
89
89
|
|
90
|
-
|
91
90
|
# @todo Document respond_to Responds.
|
92
91
|
#
|
93
92
|
# @param [type] arg_name
|
data/lib/nrser/types/strings.rb
CHANGED
@@ -6,35 +6,34 @@ require 'nrser/types/attrs'
|
|
6
6
|
using NRSER
|
7
7
|
|
8
8
|
module NRSER::Types
|
9
|
-
|
10
|
-
|
11
|
-
def self.str **options
|
12
|
-
if options.empty?
|
9
|
+
def self.str length: nil, **options
|
10
|
+
if length.nil? && options.empty?
|
13
11
|
# if there are no options can point to the constant for efficiency
|
14
12
|
STR
|
15
13
|
else
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
if length.nil?
|
15
|
+
IsA.new String, from_s: ->(s) { s }, **options
|
16
|
+
else
|
17
|
+
intersection \
|
18
|
+
IsA.new( String, from_s: ->(s) { s } ),
|
19
|
+
NRSER::Types.length( length ),
|
20
|
+
**options
|
20
21
|
end
|
21
|
-
|
22
|
-
intersection STR, *types
|
23
22
|
end
|
24
23
|
end # string
|
25
24
|
|
26
|
-
|
27
|
-
str
|
28
|
-
end
|
29
|
-
|
30
|
-
EMPTY_STR = Is.new ''
|
25
|
+
singleton_class.send :alias_method, :string, :str
|
31
26
|
|
32
|
-
|
27
|
+
STR = str( name: 'StrType' ).freeze
|
33
28
|
|
29
|
+
EMPTY_STR = Is.new( '' ).freeze
|
34
30
|
|
35
|
-
def self.non_empty_str
|
36
|
-
NON_EMPTY_STR
|
31
|
+
def self.non_empty_str **options
|
32
|
+
return NON_EMPTY_STR if options.empty?
|
33
|
+
|
34
|
+
str( length: {min: 1}, **options )
|
37
35
|
end # .non_empty_str
|
38
36
|
|
37
|
+
NON_EMPTY_STR = non_empty_str( name: 'NonEmptyStr' ).freeze
|
39
38
|
|
40
39
|
end # NRSER::Types
|