nrser 0.0.25 → 0.0.26

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