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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +39 -11
  3. data/lib/nrser.rb +5 -1
  4. data/lib/nrser/array.rb +10 -53
  5. data/lib/nrser/enumerable.rb +21 -0
  6. data/lib/nrser/hash.rb +13 -476
  7. data/lib/nrser/hash/bury.rb +154 -0
  8. data/lib/nrser/hash/deep_merge.rb +57 -0
  9. data/lib/nrser/hash/except_keys.rb +42 -0
  10. data/lib/nrser/hash/guess_label_key_type.rb +37 -0
  11. data/lib/nrser/hash/slice_keys.rb +41 -0
  12. data/lib/nrser/hash/stringify_keys.rb +37 -0
  13. data/lib/nrser/hash/symbolize_keys.rb +41 -0
  14. data/lib/nrser/hash/transform_keys.rb +45 -0
  15. data/lib/nrser/merge_by.rb +26 -0
  16. data/lib/nrser/message.rb +125 -0
  17. data/lib/nrser/meta/props.rb +2 -2
  18. data/lib/nrser/meta/props/prop.rb +5 -2
  19. data/lib/nrser/object.rb +5 -0
  20. data/lib/nrser/object/as_array.rb +37 -0
  21. data/lib/nrser/object/as_hash.rb +101 -0
  22. data/lib/nrser/{truthy.rb → object/truthy.rb} +0 -0
  23. data/lib/nrser/proc.rb +132 -0
  24. data/lib/nrser/refinements.rb +1 -2
  25. data/lib/nrser/refinements/array.rb +94 -5
  26. data/lib/nrser/refinements/enumerable.rb +5 -0
  27. data/lib/nrser/refinements/hash.rb +43 -6
  28. data/lib/nrser/refinements/object.rb +22 -2
  29. data/lib/nrser/refinements/symbol.rb +12 -0
  30. data/lib/nrser/refinements/tree.rb +41 -0
  31. data/lib/nrser/rspex.rb +329 -0
  32. data/lib/nrser/string.rb +3 -0
  33. data/lib/nrser/string/looks_like.rb +51 -0
  34. data/lib/nrser/temp/where.rb +52 -0
  35. data/lib/nrser/tree.rb +86 -0
  36. data/lib/nrser/tree/leaves.rb +92 -0
  37. data/lib/nrser/tree/map_leaves.rb +63 -0
  38. data/lib/nrser/tree/transform.rb +30 -0
  39. data/lib/nrser/types.rb +9 -4
  40. data/lib/nrser/types/any.rb +1 -1
  41. data/lib/nrser/types/array.rb +167 -25
  42. data/lib/nrser/types/{hash.rb → hashes.rb} +19 -5
  43. data/lib/nrser/types/in.rb +47 -0
  44. data/lib/nrser/types/is_a.rb +2 -2
  45. data/lib/nrser/types/labels.rb +49 -0
  46. data/lib/nrser/types/numbers.rb +63 -27
  47. data/lib/nrser/types/pairs.rb +109 -0
  48. data/lib/nrser/types/responds.rb +2 -3
  49. data/lib/nrser/types/strings.rb +17 -18
  50. data/lib/nrser/types/symbols.rb +39 -0
  51. data/lib/nrser/types/trees.rb +93 -0
  52. data/lib/nrser/types/tuples.rb +116 -0
  53. data/lib/nrser/types/type.rb +26 -2
  54. data/lib/nrser/version.rb +1 -1
  55. data/spec/nrser/hash/{guess_name_type_spec.rb → guess_label_key_type_spec.rb} +3 -3
  56. data/spec/nrser/hash_spec.rb +0 -20
  57. data/spec/nrser/merge_by_spec.rb +73 -0
  58. data/spec/nrser/meta/props_spec.rb +136 -43
  59. data/spec/nrser/op/message_spec.rb +62 -0
  60. data/spec/nrser/refinements/array_spec.rb +36 -0
  61. data/spec/nrser/refinements/hash_spec.rb +34 -0
  62. data/spec/nrser/string/looks_like_spec.rb +31 -0
  63. data/spec/nrser/tree/each_branch_spec.rb +82 -0
  64. data/spec/nrser/tree/leaves_spec.rb +112 -0
  65. data/spec/nrser/tree/transform_spec.rb +165 -0
  66. data/spec/nrser/types/array_spec.rb +82 -0
  67. data/spec/nrser/types/attrs_spec.rb +4 -4
  68. data/spec/nrser/types/pairs_spec.rb +41 -0
  69. data/spec/nrser/types/paths_spec.rb +3 -3
  70. data/spec/nrser/types/strings_spec.rb +66 -0
  71. data/spec/nrser/types/symbols_spec.rb +38 -0
  72. data/spec/nrser/types/tuples_spec.rb +37 -0
  73. data/spec/nrser/types_spec.rb +0 -13
  74. data/spec/spec_helper.rb +71 -22
  75. metadata +58 -10
  76. data/lib/nrser/spex.rb +0 -68
  77. data/lib/nrser/types/symbol.rb +0 -23
@@ -0,0 +1,39 @@
1
+ require 'nrser/refinements'
2
+ require 'nrser/types/is'
3
+ require 'nrser/types/is_a'
4
+
5
+ require 'nrser/refinements'
6
+ using NRSER
7
+
8
+ module NRSER::Types
9
+
10
+ def self.sym **options
11
+ if options.empty?
12
+ # if there are no options can point to the constant for efficiency
13
+ SYM
14
+ else
15
+ IsA.new(
16
+ Symbol,
17
+ from_s: :to_sym.to_proc,
18
+ **options
19
+ )
20
+ end
21
+ end # sym
22
+
23
+ singleton_class.send :alias_method, :symbol, :sym
24
+
25
+ SYM = sym( name: 'SymType' ).freeze
26
+
27
+
28
+ def self.non_empty_sym **options
29
+ return NON_EMPTY_SYM if options.empty?
30
+
31
+ intersection \
32
+ SYM,
33
+ attrs( {to_s: non_empty_str} ),
34
+ **options
35
+ end
36
+
37
+ NON_EMPTY_SYM = non_empty_sym( name: 'NonEmptySym' ).freeze
38
+
39
+ end # NRSER::Types
@@ -0,0 +1,93 @@
1
+ # Requirements
2
+ # =======================================================================
3
+
4
+ # Stdlib
5
+ # -----------------------------------------------------------------------
6
+
7
+ # Deps
8
+ # -----------------------------------------------------------------------
9
+
10
+ # Project / Package
11
+ # -----------------------------------------------------------------------
12
+ require_relative './combinators'
13
+ require_relative './responds'
14
+
15
+
16
+ # Refinements
17
+ # =======================================================================
18
+
19
+ require 'nrser/refinements'
20
+ using NRSER
21
+
22
+
23
+ # Definitions
24
+ # =======================================================================
25
+
26
+ module NRSER::Types
27
+
28
+ # @todo Document array_like method.
29
+ #
30
+ # @param [type] arg_name
31
+ # @todo Add name param description.
32
+ #
33
+ # @return [return_type]
34
+ # @todo Document return value.
35
+ #
36
+ def self.array_like **options
37
+ if options.empty?
38
+ ARRAY_LIKE
39
+ else
40
+ intersection \
41
+ is_a( Enumerable ),
42
+ respond_to( :each_index ),
43
+ **options
44
+ end
45
+ end # .array_like
46
+
47
+ ARRAY_LIKE = array_like( name: 'ArrayLikeType' ).freeze
48
+
49
+
50
+ # @todo Document hash_like method.
51
+ #
52
+ # @param [type] arg_name
53
+ # @todo Add name param description.
54
+ #
55
+ # @return [return_type]
56
+ # @todo Document return value.
57
+ #
58
+ def self.hash_like **options
59
+ if options.empty?
60
+ HASH_LIKE
61
+ else
62
+ intersection \
63
+ is_a( Enumerable ),
64
+ respond_to( :each_pair ),
65
+ **options
66
+ end
67
+ end # .hash_like
68
+
69
+ HASH_LIKE = hash_like( name: 'HashLikeType' ).freeze
70
+
71
+
72
+ # @todo Document tree method.
73
+ #
74
+ # @param [type] arg_name
75
+ # @todo Add name param description.
76
+ #
77
+ # @return [return_type]
78
+ # @todo Document return value.
79
+ #
80
+ def self.tree **options
81
+ if options.empty?
82
+ TREE
83
+ else
84
+ union \
85
+ array_like,
86
+ hash_like,
87
+ **options
88
+ end
89
+ end # .tree
90
+
91
+ TREE = tree( name: 'TreeType' ).freeze
92
+
93
+ end # module NRSER::Types
@@ -0,0 +1,116 @@
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
+ # Definitions
22
+ # =======================================================================
23
+
24
+ module NRSER::Types
25
+
26
+ # Tuple type - array of fixed length and types (though those could be
27
+ # {NRSER::Types::ANY}).
28
+ #
29
+ class TupleType < NRSER::Types::ArrayType
30
+
31
+ # Constants
32
+ # ======================================================================
33
+
34
+
35
+ # Class Methods
36
+ # ======================================================================
37
+
38
+
39
+ # Attributes
40
+ # ======================================================================
41
+
42
+
43
+ # Constructor
44
+ # ======================================================================
45
+
46
+ # Instantiate a new `TupleType`.
47
+ def initialize *types, **options
48
+ super **options
49
+ @types = types.map &NRSER::Types.method(:make)
50
+ end # #initialize
51
+
52
+
53
+ # Instance Methods
54
+ # ======================================================================
55
+
56
+ # @todo Document test method.
57
+ #
58
+ # @param [type] arg_name
59
+ # @todo Add name param description.
60
+ #
61
+ # @return [return_type]
62
+ # @todo Document return value.
63
+ #
64
+ def test value
65
+ # Test the super class first
66
+ return false unless super( value )
67
+
68
+ # If it's not the right length then it doesn't pass
69
+ return false unless value.length == @types.length
70
+
71
+ # Test each item type
72
+ @types.each_with_index.all? { |type, index|
73
+ type.test value[index]
74
+ }
75
+ end # #test
76
+
77
+
78
+ # @return [Boolean]
79
+ # `true` if this type can load values from a string, which is true if
80
+ # *all* it's types can load values from strings.
81
+ #
82
+ def has_from_s?
83
+ @types.all? &:has_from_s?
84
+ end # #has_from_s?
85
+
86
+
87
+ # Load each value in an array of strings split out by
88
+ # {NRSER::Types::ArrayType#from_s} by passing each value to `#from_s` in
89
+ # the type of the corresponding index.
90
+ #
91
+ # @param [Array<String>] strings
92
+ #
93
+ # @return [Array]
94
+ #
95
+ def items_from_strings strings
96
+ @types.each_with_index.map { |type, index|
97
+ type.from_s strings[index]
98
+ }
99
+ end
100
+
101
+ end # class TupleType
102
+
103
+
104
+ # @todo Document tuple method.
105
+ #
106
+ # @param [type] arg_name
107
+ # @todo Add name param description.
108
+ #
109
+ # @return [return_type]
110
+ # @todo Document return value.
111
+ #
112
+ def self.tuple *types, **options
113
+ TupleType.new *types, **options
114
+ end # .tuple
115
+
116
+ end # module NRSER::Types
@@ -194,12 +194,36 @@ module NRSER::Types
194
194
  end # #to_data
195
195
 
196
196
 
197
+ # Language Inter-Op
198
+ # =====================================================================
199
+
200
+
197
201
  # @return [String]
198
- # a brief string description of the type.
202
+ # a brief string description of the type - just it's {#name} surrounded
203
+ # by some back-ticks to make it easy to see where it starts and stops.
199
204
  #
200
205
  def to_s
201
- "`Type: #{ name }`"
206
+ "`#{ name }`"
202
207
  end
203
208
 
209
+
210
+ # Inspecting
211
+ # ---------------------------------------------------------------------
212
+ #
213
+ # Due to their combinatoric nature, types can quickly become large data
214
+ # hierarchies, and the built-in {#inspect} will produce a massive dump
215
+ # that's distracting and hard to decipher.
216
+ #
217
+ # {#inspect} is readily used in tools like `pry` and `rspec`, significantly
218
+ # impacting their usefulness when working with types.
219
+ #
220
+ # As a solution, we alias the built-in `#inspect` as {#builtin_inspect},
221
+ # so it's available in situations where you really want all those gory
222
+ # details, and point {#inspect} to {#to_s}.
223
+ #
224
+
225
+ alias_method :builtin_inspect, :inspect
226
+ alias_method :inspect, :to_s
227
+
204
228
  end # Type
205
229
  end # NRSER::Types
data/lib/nrser/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module NRSER
2
- VERSION = "0.0.25"
2
+ VERSION = "0.0.26"
3
3
 
4
4
  module Version
5
5
 
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "NRSER.guess_name_type" do
4
- subject { NRSER.method :guess_name_type }
3
+ describe "NRSER.guess_label_key_type" do
4
+ subject { NRSER.method :guess_label_key_type }
5
5
 
6
6
  it "can't guess about an empty hash" do
7
7
  expect( subject.call( {} ) ).to be nil
@@ -44,4 +44,4 @@ describe "NRSER.guess_name_type" do
44
44
  ).to be nil
45
45
  end
46
46
 
47
- end # NRSER.guess_name_type
47
+ end # NRSER.guess_label_key_type
@@ -5,26 +5,6 @@ require 'spec_helper'
5
5
 
6
6
  using NRSER
7
7
 
8
- describe NRSER.method(:leaves) do
9
- it do
10
- expect(NRSER.leaves({a: 1, b: 2})).to eq ({[:a] => 1, [:b] => 2})
11
- expect(
12
- NRSER.leaves({
13
- a: {
14
- x: 'ex',
15
- y: {
16
- z: 'zee'
17
- }
18
- },
19
- b: 'bee',
20
- })
21
- ).to eq({
22
- [:a, :x] => 'ex',
23
- [:a, :y, :z] => 'zee',
24
- [:b] => 'bee',
25
- })
26
- end
27
- end # NRSER.leaves
28
8
 
29
9
  describe NRSER.method(:map_values) do
30
10
 
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ require 'nrser/refinements'
4
+ using NRSER
5
+
6
+
7
+ # NRSER.merge_by
8
+ # ========================================================================
9
+ #
10
+ describe "NRSER.merge_by" do
11
+ subject { NRSER.method :merge_by }
12
+
13
+ context "line items example" do
14
+ let( :current ) {
15
+ [
16
+ {
17
+ line_item_id: 123,
18
+ item_id: 1,
19
+ quantity: 1,
20
+ },
21
+
22
+ {
23
+ line_item_id: 456,
24
+ item_id: 2,
25
+ quantity: 4,
26
+ },
27
+ ]
28
+ }
29
+
30
+ let( :update ) {
31
+ [
32
+ {
33
+ item_id: 1,
34
+ quantity: 4,
35
+ },
36
+
37
+ {
38
+ item_id: 3,
39
+ quantity: 1,
40
+ }
41
+ ]
42
+ }
43
+
44
+ subject {
45
+ super().call current, update, &[:item_id].digger
46
+ }
47
+
48
+ it {
49
+ is_expected.to include(
50
+ {
51
+ line_item_id: 123,
52
+ item_id: 1,
53
+ quantity: 4,
54
+ },
55
+
56
+ {
57
+ line_item_id: 456,
58
+ item_id: 2,
59
+ quantity: 4,
60
+ },
61
+
62
+ {
63
+ item_id: 3,
64
+ quantity: 1,
65
+ }
66
+ )
67
+ }
68
+
69
+ end # line items example
70
+
71
+ end # NRSER.merge_by
72
+
73
+ # ************************************************************************
@@ -1,67 +1,160 @@
1
1
  require 'spec_helper'
2
2
 
3
+ require 'nrser/refinements'
4
+ using NRSER
5
+
6
+ require 'nrser/refinements/types'
3
7
  using NRSER::Types
4
8
 
9
+
5
10
  describe NRSER::Meta::Props do
6
11
 
7
- # Setup
8
- # =====================================================================
9
-
10
- let(:point) {
11
- Class.new(NRSER::Meta::Props::Base) do
12
- # include NRSER::Meta::Props
13
-
14
- prop :x, type: t.int
15
- prop :y, type: t.int
16
- prop :blah, type: t.str, source: :blah
17
-
18
- def blah
19
- "blah!"
12
+ context "simple Point class" do
13
+
14
+ # Setup
15
+ # =====================================================================
16
+
17
+ let(:point_class) {
18
+ Class.new(NRSER::Meta::Props::Base) do
19
+ # include NRSER::Meta::Props
20
+
21
+ prop :x, type: t.int
22
+ prop :y, type: t.int
23
+ prop :blah, type: t.str, source: :blah
24
+
25
+ def blah
26
+ "blah!"
27
+ end
20
28
  end
21
- end
22
- }
23
-
24
- it "has the props" do
25
- props = point.props
29
+ }
26
30
 
27
- expect(props).to be_a Hash
28
31
 
29
- [:x, :y, :blah].each do |name|
30
- expect(props[name]).to be_a NRSER::Meta::Props::Prop
31
- end
32
+ describe ".props" do
33
+ # ========================================================================
34
+
35
+ subject { point_class.props }
36
+
37
+ it {
38
+ is_expected.to be_a( Hash ).and have_attributes \
39
+ keys: eq( [:x, :y, :blah] ),
40
+ values: all( be_a NRSER::Meta::Props::Prop )
41
+ }
42
+
43
+ describe 'primary props `x` and `y`' do
44
+ [:x, :y].each do |name|
45
+ describe "prop `#{ name }`" do
46
+ subject { super()[name] }
47
+
48
+ include_examples "expect subject", to: {
49
+ be_a: NRSER::Meta::Props::Prop,
50
+ have_attributes: {
51
+ source?: false,
52
+ primary?: true,
53
+ }
54
+ }
55
+ end
56
+ end
57
+ end # primary props `x` and `y`'
58
+
59
+ describe "derived (sourced) prop `blah`" do
60
+ subject { super()[:blah] }
61
+
62
+ include_examples "expect subject", to: {
63
+ be_a: NRSER::Meta::Props::Prop,
64
+ have_attributes: {
65
+ source?: true,
66
+ primary?: false,
67
+ }
68
+ }
69
+ end # derived (sourced) prop :blah
70
+
71
+ end # .props
72
+
73
+ # ************************************************************************
32
74
 
33
- [:x, :y].each do |name|
34
- expect(props[name].source?).to be false
35
- expect(props[name].primary?).to be true
36
- end
37
75
 
38
- expect(props[:blah].source?).to be true
39
- expect(props[:blah].primary?).to be false
76
+ describe ".props only_primary: true" do
77
+ # ========================================================================
78
+
79
+ subject { point_class.props only_primary: true }
80
+
81
+ it {
82
+ is_expected.to be_a( Hash ).
83
+ and have_attributes(
84
+ keys: eq( [:x, :y] ),
85
+ values: all(
86
+ be_a( NRSER::Meta::Props::Prop ).
87
+ and have_attributes source?: false, primary?: true
88
+ )
89
+ )
90
+ }
91
+
92
+ end # .props only_primary: true
40
93
 
41
- primary_props = point.props only_primary: true
94
+ # ************************************************************************
42
95
 
43
- expect(primary_props.key? :blah).to be false
96
+ describe "Point instance where x=1 and y=2 (default blah)" do
97
+ # ========================================================================
98
+
99
+ subject { point_class.new x: 1, y: 2 }
100
+
101
+ it { is_expected.to have_attributes x: 1, y: 2, blah: "blah!" }
102
+
103
+ describe "#to_h" do
104
+ subject { super().to_h }
105
+ it { is_expected.to eq x: 1, y: 2, blah: 'blah!' }
106
+ end
107
+
108
+ describe "#to_h only_primary: true" do
109
+ subject { super().to_h only_primary: true }
110
+ it { is_expected.to eq x: 1, y: 2 }
111
+ end
112
+
113
+ # ************************************************************************
114
+
115
+ end # Point instance where x=1 and y=2 (default blah)
44
116
 
45
- p = point.new x: 1, y: 2
117
+ # ************************************************************************
46
118
 
47
- expect(p.x).to be 1
48
- expect(p.y).to be 2
49
119
 
50
- expect(p.to_h).to eq({x: 1, y: 2, blah: "blah!"})
51
- expect(p.to_h(only_primary: true)).to eq({x: 1, y: 2})
120
+ describe "bad constructor args" do
121
+ # ========================================================================
122
+
123
+ it "rejects string `y: 'why?'` value" do
124
+ expect { point_class.new x: 1, y: 'why?' }.to raise_error TypeError
125
+ end
126
+
127
+ end # bad constructor args
52
128
 
53
- expect { point.new x: 1, y: 'why?' }.to raise_error TypeError
54
- expect { p.x = 3 }.to raise_error NoMethodError
55
129
 
56
- p_hash = p.to_h
130
+ describe ".new" do
131
+ subject { point_class.method :new }
132
+
133
+ it_behaves_like "function",
134
+ mapping: {
135
+ [{x: 1, y: 2}] => NRSER::Message.new(
136
+ :have_attributes, x: 1, y: 2, blah: 'blah!'
137
+ ),
138
+ },
139
+ raising: {
140
+ [{x: 1, y: 'why?'}] => [TypeError, /must be of type `IntType`/],
141
+ }
142
+ end # .new
57
143
 
58
- p2 = point.new p_hash
59
144
 
60
- expect(p2.x).to be 1
61
- expect(p2.y).to be 2
145
+ describe "dump / load cycle" do
146
+ context "Point with x=1, y=2" do
147
+ let( :point ) { point_class.new x: 1, y: 2 }
148
+ let( :point_hash ) { point.to_h }
149
+
150
+ describe "new Point from old point's #to_h" do
151
+ subject { point_class.new point_hash }
152
+ it { is_expected.to have_attributes x: 1, y: 2, blah: 'blah!' }
153
+ end # new Point from old point's #to_h
154
+ end
155
+ end # dump / load cycle
62
156
 
63
- expect(p2.to_h).to eq({x: 1, y: 2, blah: "blah!"})
64
- end
157
+ end # simple Point class
65
158
 
66
159
  end # NRSER::Meta::Props
67
160