nrser 0.0.26 → 0.0.27

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nrser.rb +1 -0
  3. data/lib/nrser/array.rb +15 -0
  4. data/lib/nrser/binding.rb +7 -1
  5. data/lib/nrser/enumerable.rb +21 -1
  6. data/lib/nrser/errors.rb +56 -6
  7. data/lib/nrser/hash/deep_merge.rb +1 -1
  8. data/lib/nrser/message.rb +33 -0
  9. data/lib/nrser/meta/props.rb +77 -15
  10. data/lib/nrser/meta/props/prop.rb +276 -44
  11. data/lib/nrser/proc.rb +7 -3
  12. data/lib/nrser/refinements/array.rb +5 -0
  13. data/lib/nrser/refinements/enumerable.rb +5 -0
  14. data/lib/nrser/refinements/hash.rb +8 -0
  15. data/lib/nrser/refinements/object.rb +11 -1
  16. data/lib/nrser/refinements/string.rb +17 -3
  17. data/lib/nrser/refinements/symbol.rb +8 -0
  18. data/lib/nrser/refinements/tree.rb +22 -0
  19. data/lib/nrser/rspex.rb +312 -70
  20. data/lib/nrser/rspex/shared_examples.rb +116 -0
  21. data/lib/nrser/string.rb +159 -27
  22. data/lib/nrser/temp/unicode_math.rb +48 -0
  23. data/lib/nrser/text.rb +3 -0
  24. data/lib/nrser/text/indentation.rb +210 -0
  25. data/lib/nrser/text/lines.rb +52 -0
  26. data/lib/nrser/text/word_wrap.rb +29 -0
  27. data/lib/nrser/tree.rb +4 -78
  28. data/lib/nrser/tree/each_branch.rb +76 -0
  29. data/lib/nrser/tree/map_branches.rb +91 -0
  30. data/lib/nrser/tree/map_tree.rb +97 -0
  31. data/lib/nrser/tree/transform.rb +56 -13
  32. data/lib/nrser/types.rb +1 -0
  33. data/lib/nrser/types/array.rb +15 -3
  34. data/lib/nrser/types/is_a.rb +40 -1
  35. data/lib/nrser/types/nil.rb +17 -0
  36. data/lib/nrser/types/paths.rb +17 -2
  37. data/lib/nrser/types/strings.rb +57 -22
  38. data/lib/nrser/types/tuples.rb +5 -0
  39. data/lib/nrser/types/type.rb +47 -6
  40. data/lib/nrser/version.rb +1 -1
  41. data/spec/nrser/errors/abstract_method_error_spec.rb +46 -0
  42. data/spec/nrser/meta/props/to_and_from_data_spec.rb +74 -0
  43. data/spec/nrser/meta/props_spec.rb +6 -2
  44. data/spec/nrser/refinements/erb_spec.rb +100 -1
  45. data/spec/nrser/{common_prefix_spec.rb → string/common_prefix_spec.rb} +9 -0
  46. data/spec/nrser/text/dedent_spec.rb +80 -0
  47. data/spec/nrser/tree/map_branch_spec.rb +83 -0
  48. data/spec/nrser/tree/map_tree_spec.rb +123 -0
  49. data/spec/nrser/tree/transform_spec.rb +26 -29
  50. data/spec/nrser/tree/transformer_spec.rb +179 -0
  51. data/spec/nrser/types/paths_spec.rb +73 -45
  52. data/spec/spec_helper.rb +10 -0
  53. metadata +27 -7
  54. data/spec/nrser/dedent_spec.rb +0 -36
@@ -24,6 +24,7 @@ require 'pp'
24
24
  #
25
25
  require_relative './types/type'
26
26
  require_relative './types/is'
27
+ require_relative './types/nil'
27
28
  require_relative './types/is_a'
28
29
  require_relative './types/where'
29
30
  require_relative './types/combinators'
@@ -30,6 +30,7 @@ module NRSER; end
30
30
  # =======================================================================
31
31
 
32
32
  module NRSER::Types
33
+
33
34
  class ArrayType < IsA
34
35
  # Default value to split strings with in {#from_s} if the string provided
35
36
  # does is not recognized as an encoding format (as of writing, JSON is
@@ -51,6 +52,11 @@ module NRSER::Types
51
52
  end
52
53
 
53
54
 
55
+ def item_type
56
+ NRSER::Types.any
57
+ end
58
+
59
+
54
60
  # Called on an array of string items that have been split
55
61
  # from a single string by {#from_s} to convert each individual item before
56
62
  # {#check} is called on the value.
@@ -179,12 +185,17 @@ module NRSER::Types
179
185
  #
180
186
  class << self
181
187
 
182
- # array
188
+ # @!group Type Factory Functions
189
+
190
+ # {NRSER::Types::ArrayType} / {NRSER::Types::ArrayOfType} factory function.
191
+ #
192
+ # @param [Type | Object] item_type
193
+ # Optional type of items.
183
194
  #
184
195
  # @return [NRSER::Types::Type]
185
196
  #
186
- def array item_type = NRSER::NO_ARG, **options
187
- if item_type == NRSER::NO_ARG
197
+ def array item_type = any, **options
198
+ if item_type == any
188
199
  if options.empty?
189
200
  ARRAY
190
201
  else
@@ -199,4 +210,5 @@ module NRSER::Types
199
210
 
200
211
  end # class << self (Eigenclass)
201
212
 
213
+
202
214
  end # NRSER::Types
@@ -7,19 +7,58 @@ module NRSER::Types
7
7
  attr_reader :klass
8
8
 
9
9
  def initialize klass, **options
10
+ unless klass.is_a?( Class ) || klass.is_a?( Module )
11
+ raise ArgumentError.new binding.erb <<-ERB
12
+ `klass` argument must be a Class or Module, found:
13
+
14
+ <%= klass.pretty_inspect %>
15
+
16
+ ERB
17
+ end
18
+
10
19
  super **options
11
20
  @klass = klass
12
21
  end
13
22
 
14
23
  def default_name
15
- "#{ self.class.short_name }(#{ @klass })"
24
+ "#{ self.class.short_name }(#{ @klass.name })"
16
25
  end
17
26
 
18
27
  def test value
19
28
  value.is_a? @klass
20
29
  end
30
+
31
+
32
+ # If {#klass} responds to `#from_data`, call that and check results.
33
+ #
34
+ # Otherwise, forward up to {NRSER::Types::Type#from_data}.
35
+ #
36
+ # @param [Object] data
37
+ # Data to create the value from that will satisfy the type.
38
+ #
39
+ # @return [Object]
40
+ # Instance of {#klass}.
41
+ #
42
+ def from_data data
43
+ if @from_data.nil?
44
+ if @klass.respond_to? :from_data
45
+ check @klass.from_data( data )
46
+ else
47
+ super data
48
+ end
49
+ else
50
+ @from_data.call data
51
+ end
52
+ end
53
+
54
+
55
+ def has_from_data?
56
+ @from_data || @klass.respond_to?( :from_data )
57
+ end
58
+
21
59
  end # IsA
22
60
 
61
+
23
62
  # class membership
24
63
  def self.is_a klass, **options
25
64
  IsA.new klass, **options
@@ -0,0 +1,17 @@
1
+ require 'nrser/types/is'
2
+
3
+ module NRSER::Types
4
+
5
+ NIL_TYPE = is(
6
+ nil,
7
+ name: 'NilType',
8
+ # from_s: ->( s ) {
9
+ #
10
+ # }
11
+ ).freeze
12
+
13
+ # nothing
14
+ def self.nil
15
+ NIL_TYPE
16
+ end
17
+ end # NRSER::Types
@@ -45,7 +45,6 @@ module NRSER::Types
45
45
  where { |value| value.to_s.length > 0 },
46
46
  name: 'NonEmptyPathnameType'
47
47
 
48
-
49
48
  PATH = union non_empty_str, NON_EMPTY_PATHNAME, name: 'Path'
50
49
 
51
50
 
@@ -53,9 +52,10 @@ module NRSER::Types
53
52
  # ========================================================================
54
53
  #
55
54
  class << self
55
+ # @!group Type Factory Functions
56
56
 
57
57
  def pathname to_data: :to_s, **options
58
- if options.empty?
58
+ if options.empty? && to_data == :to_s
59
59
  PATHNAME
60
60
  else
61
61
  is_a \
@@ -82,6 +82,19 @@ module NRSER::Types
82
82
  end # #path
83
83
 
84
84
 
85
+ def path_segment **options
86
+ if options.empty?
87
+ POSIX_PATH_SEGMENT
88
+ else
89
+ intersection non_empty_str,
90
+ where { |string| ! string.include?( '/' ) },
91
+ name: 'POSIXPathSegment'
92
+ end
93
+ end
94
+
95
+ alias_method :path_seg, :path_segment
96
+
97
+
85
98
  # An absolute {#path}.
86
99
  #
87
100
  # @param **options see NRSER::Types::Type#initialize
@@ -139,5 +152,7 @@ module NRSER::Types
139
152
 
140
153
  end # class << self (Eigenclass)
141
154
 
155
+ POSIX_PATH_SEGMENT = path_segment name: 'POSIXPathSegment'
156
+
142
157
  end # module NRSER::Types
143
158
 
@@ -6,33 +6,68 @@ require 'nrser/types/attrs'
6
6
  using NRSER
7
7
 
8
8
  module NRSER::Types
9
- def self.str length: nil, **options
10
- if length.nil? && options.empty?
11
- # if there are no options can point to the constant for efficiency
12
- STR
13
- else
14
- if length.nil?
15
- IsA.new String, from_s: ->(s) { s }, **options
9
+
10
+ # Eigenclass (Singleton Class)
11
+ # ========================================================================
12
+ #
13
+ class << self
14
+ # @!group Type Factory Functions
15
+
16
+ def str length: nil, **options
17
+ if length.nil? && options.empty?
18
+ # if there are no options can point to the constant for efficiency
19
+ STR
16
20
  else
17
- intersection \
18
- IsA.new( String, from_s: ->(s) { s } ),
19
- NRSER::Types.length( length ),
20
- **options
21
+ if length.nil?
22
+ IsA.new String, from_s: ->(s) { s }, **options
23
+ else
24
+ intersection \
25
+ IsA.new( String, from_s: ->(s) { s } ),
26
+ NRSER::Types.length( length ),
27
+ **options
28
+ end
21
29
  end
30
+ end # string
31
+
32
+ alias_method :string, :str
33
+
34
+
35
+ def empty_str
36
+ EMPTY_STR
22
37
  end
23
- end # string
24
-
25
- singleton_class.send :alias_method, :string, :str
26
-
27
- STR = str( name: 'StrType' ).freeze
38
+
39
+
40
+ def non_empty_str **options
41
+ return NON_EMPTY_STR if options.empty?
42
+
43
+ str( length: {min: 1}, **options )
44
+ end # .non_empty_str
45
+
46
+
47
+ private
48
+ # ========================================================================
49
+
50
+
51
+ # @todo Document make_string_type method.
52
+ #
53
+ # @param [type] arg_name
54
+ # @todo Add name param description.
55
+ #
56
+ # @return [return_type]
57
+ # @todo Document return value.
58
+ #
59
+ def make_string_type length: nil, match: nil
60
+ # method body...
61
+ end # #make_string_type
62
+
63
+
64
+ # end private
65
+
66
+ end # class << self (Eigenclass)
28
67
 
29
- EMPTY_STR = Is.new( '' ).freeze
68
+ STR = str( name: 'StringType' ).freeze
30
69
 
31
- def self.non_empty_str **options
32
- return NON_EMPTY_STR if options.empty?
33
-
34
- str( length: {min: 1}, **options )
35
- end # .non_empty_str
70
+ EMPTY_STR = str( name: 'EmptyStringType', length: 0 ).freeze
36
71
 
37
72
  NON_EMPTY_STR = non_empty_str( name: 'NonEmptyStr' ).freeze
38
73
 
@@ -50,6 +50,11 @@ module NRSER::Types
50
50
  end # #initialize
51
51
 
52
52
 
53
+ def default_name
54
+ '[' + @types.map( &:name ).join( ', ' ) + ']'
55
+ end
56
+
57
+
53
58
  # Instance Methods
54
59
  # ======================================================================
55
60
 
@@ -35,7 +35,7 @@ module NRSER::Types
35
35
  # @param [nil | #call | #to_proc] to_data:
36
36
  #
37
37
  #
38
- def initialize name: nil, from_s: nil, to_data: nil
38
+ def initialize name: nil, from_s: nil, to_data: nil, from_data: nil
39
39
  @name = name
40
40
  @from_s = from_s
41
41
 
@@ -46,10 +46,37 @@ module NRSER::Types
46
46
  elsif to_data.respond_to?( :to_proc )
47
47
  to_data.to_proc
48
48
  else
49
- raise TypeError.squished <<-END
50
- `to_data:` keyword arg must be `nil`, respond to `:call` or respond
51
- to `:to_proc`; found #{ to_data.inspect }
52
- END
49
+ raise TypeError.new binding.erb <<-ERB
50
+ `to_data:` keyword arg must be `nil`, respond to `#call` or respond
51
+ to `#to_proc`.
52
+
53
+ Found value:
54
+
55
+ <%= to_data.pretty_inspect %>
56
+
57
+ (type <%= to_data.class %>)
58
+
59
+ ERB
60
+ end
61
+
62
+ @from_data = if from_data.nil?
63
+ nil
64
+ elsif from_data.respond_to?( :call )
65
+ from_data
66
+ elsif from_data.respond_to?( :to_proc )
67
+ from_data.to_proc
68
+ else
69
+ raise TypeError.new binding.erb <<-ERB
70
+ `to_data:` keyword arg must be `nil`, respond to `#call` or respond
71
+ to `#to_proc`.
72
+
73
+ Found value:
74
+
75
+ <%= from_data.pretty_inspect %>
76
+
77
+ (type <%= from_data.class %>)
78
+
79
+ ERB
53
80
  end
54
81
  end # #initialize
55
82
 
@@ -152,6 +179,15 @@ module NRSER::Types
152
179
  end
153
180
 
154
181
 
182
+ def from_data data
183
+ if @from_data.nil?
184
+ raise NoMethodError, "#from_data not defined"
185
+ end
186
+
187
+ check @from_data.call( data )
188
+ end
189
+
190
+
155
191
  # Test if the type knows how to load values from strings.
156
192
  #
157
193
  # If this method returns `true`, then we expect {#from_s} to succeed.
@@ -176,6 +212,11 @@ module NRSER::Types
176
212
  end # #has_to_data?
177
213
 
178
214
 
215
+ def has_from_data?
216
+ ! @from_data.nil?
217
+ end
218
+
219
+
179
220
  # Dumps a value of this type to "data" - structures and values suitable
180
221
  # for transport and storage, such as dumping to JSON or YAML, etc.
181
222
  #
@@ -186,7 +227,7 @@ module NRSER::Types
186
227
  # The data representation of the value.
187
228
  #
188
229
  def to_data value
189
- if @from_s.nil?
230
+ if @to_data.nil?
190
231
  raise NoMethodError, "#to_data not defined"
191
232
  end
192
233
 
@@ -1,5 +1,5 @@
1
1
  module NRSER
2
- VERSION = "0.0.26"
2
+ VERSION = "0.0.27"
3
3
 
4
4
  module Version
5
5
 
@@ -0,0 +1,46 @@
1
+
2
+ module NRSER::TestFixtures::AbstractMethodError
3
+
4
+ class Base
5
+ def f
6
+ raise NRSER::AbstractMethodError.new( self, __method__ )
7
+ end
8
+ end
9
+
10
+ class Sub < Base; end
11
+
12
+ end # module NRSER::TestFixtures::AbstractMethodError
13
+
14
+
15
+ describe_class NRSER::AbstractMethodError do
16
+
17
+ context(
18
+ "when raising method is invoked through instance of defining class"
19
+ ) do
20
+
21
+ it "explains that the instance's class is abstract" do
22
+ expect {
23
+ NRSER::TestFixtures::AbstractMethodError::Base.new.f
24
+ }.to raise_error(
25
+ NRSER::AbstractMethodError,
26
+ /Base is an abstract class/
27
+ )
28
+ end
29
+
30
+ end # when raising method is invoked through instance of defining class
31
+
32
+ context "when raising method is invoked through instance of a subclass" do
33
+
34
+ it "explains that an implementing class needs to be found or written" do
35
+ expect {
36
+ NRSER::TestFixtures::AbstractMethodError::Sub.new.f
37
+ }.to raise_error(
38
+ NRSER::AbstractMethodError,
39
+ /find a subclass of .*Sub to instantiate or write your own/
40
+ )
41
+ end
42
+
43
+ end # when raising method is invoked through instance of a subclass
44
+
45
+
46
+ end # Class NRSER::AbstractMethodError Description
@@ -0,0 +1,74 @@
1
+ require 'nrser/meta/props'
2
+
3
+ require 'nrser/refinements'
4
+ using NRSER
5
+
6
+ require 'nrser/refinements/types'
7
+ using NRSER::Types
8
+
9
+
10
+ describe NRSER::Meta::Props do
11
+
12
+ describe_section "to and from data" do
13
+ # ========================================================================
14
+
15
+ before( :all ) {
16
+ @cat_class = Class.new( NRSER::Meta::Props::Base ) do
17
+ prop :name, type: t.non_empty_str
18
+ prop :breed, type: t.non_empty_str
19
+ prop :age, type: t.unsigned
20
+
21
+ def self.name; 'Cat'; end
22
+ end
23
+ }
24
+
25
+ describe_group "simple nesting" do
26
+
27
+ before( :all ) {
28
+ # IMPORTANT!!! must bind *outside* the class declaration; can't use
29
+ # @cat_class in there because it resolves to a (nil)
30
+ # instance variable of the new class.
31
+ cat_class = @cat_class
32
+
33
+ @owner_class = Class.new( NRSER::Meta::Props::Base ) do
34
+ prop :name, type: t.non_empty_str
35
+
36
+ prop :cat, type: cat_class
37
+
38
+ def self.name; 'Owner'; end
39
+ end
40
+
41
+ @cat = @cat_class.new name: "Hooty", breed: "American Shorthair", age: 2
42
+
43
+ @owner = @owner_class.new name: "Neil", cat: @cat
44
+ }
45
+
46
+ it "is setup correctly" do
47
+ expect( @cat_class ).to be_a Class
48
+ expect( @cat_class.is_a? ::Class ).to be true
49
+ expect( @cat_class.name ).to eq "Cat"
50
+ expect( t.make @cat_class ).to be_a NRSER::Types::IsA
51
+
52
+ name_prop = @owner_class.props[:name]
53
+ expect( name_prop.type ).to be t.non_empty_str
54
+
55
+ cat_prop = @owner_class.props[:cat]
56
+ expect( cat_prop.type ).to be_a NRSER::Types::IsA
57
+ expect( cat_prop.type.klass ).to be @cat_class
58
+ expect( @cat_class.respond_to? :from_data ).to be true
59
+ expect( cat_prop.type.has_from_data? ).to be true
60
+ end
61
+
62
+ it "dumps to and loads from data" do
63
+ data = @owner.to_data
64
+ restored = @owner_class.from_data data
65
+ expect( restored.to_data ).to eq data
66
+ end
67
+
68
+ end # Group "simple nesting" Description
69
+
70
+
71
+ end # section to and from data
72
+ # ************************************************************************
73
+
74
+ end