nrser 0.0.26 → 0.0.27

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