nrser 0.0.20 → 0.0.21

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.
@@ -0,0 +1,9 @@
1
+ require 'set'
2
+
3
+ require_relative './enumerable'
4
+
5
+ module NRSER
6
+ refine ::Set do
7
+ include NRSER::Refinements::Enumerable
8
+ end # refine ::Set
9
+ end # NRSER
@@ -1,3 +1,5 @@
1
+ require 'pathname'
2
+
1
3
  module NRSER
2
4
  refine String do
3
5
  def squish
@@ -27,5 +29,12 @@ module NRSER
27
29
 
28
30
  alias_method :to_const, :constantize
29
31
 
32
+ # @return [Pathname]
33
+ # Convert self into a {Pathname}
34
+ #
35
+ def to_pn
36
+ Pathname.new self
37
+ end
38
+
30
39
  end # refine String
31
40
  end # NRSER
@@ -5,3 +5,6 @@ require_relative './refinements/hash'
5
5
  require_relative './refinements/pathname'
6
6
  require_relative './refinements/exception'
7
7
  require_relative './refinements/binding'
8
+ require_relative './refinements/set'
9
+ require_relative './refinements/open_struct'
10
+ require_relative './refinements/enumerator'
@@ -8,62 +8,89 @@ module NRSER::Types
8
8
  class Attrs < NRSER::Types::Type
9
9
  def initialize attrs, **options
10
10
  super **options
11
- @attrs = attrs
11
+ @attrs = NRSER.map_values(attrs) { |name, type|
12
+ NRSER::Types.make type
13
+ }
12
14
  end
13
15
 
14
16
  def default_name
15
- attrs_str = @attrs.map {|name, type|
17
+ attrs_str = @attrs.map { |name, type|
16
18
  "#{ name }=#{ type.name }"
17
19
  }.join(', ')
18
20
 
19
- "#{ self.class.short_name }(#{ attrs_str })"
21
+ "#{ self.class.short_name } #{ attrs_str }"
20
22
  end
21
23
 
22
24
  def test value
23
- @attrs.all? {|name, type|
25
+ @attrs.all? { |name, type|
24
26
  value.respond_to?(name) && type.test(value.method(name).call)
25
27
  }
26
28
  end
27
29
  end # Attrs
28
30
 
29
- def self.attrs attrs, options = {}
30
- Attrs.new attrs, **options
31
- end
32
31
 
33
- def self.length *args
34
- bounds = {}
35
- options = {}
36
-
37
- case args.length
38
- when 1
39
- case args[0]
40
- when ::Integer
41
- bounds[:min] = bounds[:max] = non_neg_int.check(args[0])
42
-
43
- when ::Hash
44
- options = args[0].reject {|k, v|
45
- if k == :min || k == :max
46
- bounds[k] = non_neg_int.check(v)
32
+ # Eigenclass (Singleton Class)
33
+ # ========================================================================
34
+ #
35
+ class << self
36
+ def attrs attrs, options = {}
37
+ Attrs.new attrs, **options
38
+ end
39
+
40
+ def length *args
41
+ bounds = {}
42
+ options = {}
43
+
44
+ case args.length
45
+ when 1
46
+ case args[0]
47
+ when ::Integer
48
+ bounds[:min] = bounds[:max] = non_neg_int.check(args[0])
49
+
50
+ when ::Hash
51
+ options = NRSER.symbolize_keys args[0]
52
+
53
+ bounds[:min] = options.delete :min
54
+ bounds[:max] = options.delete :max
55
+
56
+ if length = options.delete(:length)
57
+ bounds[:min] = length
58
+ bounds[:max] = length
47
59
  end
48
- }
60
+
61
+ else
62
+ raise ArgumentError, <<-END.squish
63
+ arg must be positive integer or option hash, found:
64
+ #{ args[0].inspect } of type #{ args[0].class }
65
+ END
66
+ end
49
67
 
50
- else
68
+ when 2
69
+ bounds[:min] = bounds[:max] = non_neg_int.check(args[0])
70
+ options = args[1]
71
+
72
+ else
51
73
  raise ArgumentError, <<-END.squish
52
- arg must be positive integer or option hash, found:
53
- #{ args[0].inspect } of type #{ args[0].class }
74
+ must provided 1 or 2 args.
54
75
  END
55
76
  end
56
77
 
57
- when 2
58
- bounds[:min] = bounds[:max] = non_neg_int.check(args[0])
59
- options = args[1]
78
+ bounded_type = bounded bounds
60
79
 
61
- else
62
- raise ArgumentError, <<-END.squish
63
- must provided 1 or 2 args.
64
- END
65
- end
80
+ length_type = if !bounded_type.min.nil? && bounded_type.min >= 0
81
+ # We don't need the non-neg check
82
+ bounded_type
83
+ else
84
+ # We do need the non-neg check
85
+ intersection(non_neg_int, bounded_type)
86
+ end
87
+
88
+ options[:name] ||= "Length<#{ bounded_type.name }>"
89
+
90
+ attrs({ length: length_type }, options)
91
+ end # #length
92
+
66
93
 
67
- attrs({length: intersection(non_neg_int, bounded(bounds))}, options)
68
- end
94
+ end # class << self (Eigenclass)
95
+
69
96
  end # NRSER::Types
@@ -5,9 +5,26 @@ using NRSER
5
5
 
6
6
  module NRSER::Types
7
7
  class Bounded < NRSER::Types::Type
8
- def initialize **options
9
- @min = options[:min]
10
- @max = options[:max]
8
+
9
+ # @!attribute [r] min
10
+ # @return [Number]
11
+ # Minimum value.
12
+ attr_reader :min
13
+
14
+
15
+ # @!attribute [r] max
16
+ # @return [Number]
17
+ # Minimum value.
18
+ attr_reader :max
19
+
20
+
21
+ def initialize min: nil,
22
+ max: nil,
23
+ **options
24
+ super **options
25
+
26
+ @min = min
27
+ @max = max
11
28
  end
12
29
 
13
30
  def test value
@@ -25,7 +42,7 @@ module NRSER::Types
25
42
  "#{ name }=#{ value }"
26
43
  }.join(', ')
27
44
 
28
- "#{ self.class.short_name }(#{ attrs_str })"
45
+ "#{ self.class.short_name } #{ attrs_str }"
29
46
  end
30
47
  end # Bounded
31
48
 
@@ -13,11 +13,11 @@ module NRSER::Types
13
13
  @types = types.map {|type| NRSER::Types.make type}
14
14
  end
15
15
 
16
- def name
16
+ def default_name
17
17
  @name || (
18
- "#{ self.class.short_name }(" +
18
+ "#{ self.class.short_name }<" +
19
19
  @types.map {|type| type.name }.join(',') +
20
- ")"
20
+ ">"
21
21
  )
22
22
  end
23
23
 
@@ -42,7 +42,7 @@ module NRSER::Types
42
42
  }
43
43
 
44
44
  raise TypeError,
45
- "none of union types could convert #{ string.inspect }"
45
+ "none of combinator #{ self.to_s } types could convert #{ s.inspect }"
46
46
  end
47
47
 
48
48
  def == other
@@ -69,6 +69,10 @@ module NRSER::Types
69
69
  def test value
70
70
  @types.all? {|type| type.test value}
71
71
  end
72
+
73
+ def default_name
74
+ "( #{ @types.map { |t| t.name }.join ' | ' } )"
75
+ end
72
76
  end
73
77
 
74
78
  # match all of the types
@@ -12,7 +12,7 @@ module NRSER::Types
12
12
  @value = value
13
13
  end
14
14
 
15
- def name
15
+ def default_name
16
16
  "Is(#{ @value.inspect })"
17
17
  end
18
18
 
@@ -0,0 +1,110 @@
1
+
2
+ # @todo document NRSER::Types module.
3
+ module NRSER::Types
4
+
5
+ # @todo document Responds class.
6
+ class Responds < NRSER::Types::Type
7
+
8
+ # Constants
9
+ # ======================================================================
10
+
11
+
12
+ # Class Methods
13
+ # ======================================================================
14
+
15
+
16
+ # Attributes
17
+ # ======================================================================
18
+
19
+
20
+ # Constructor
21
+ # ======================================================================
22
+
23
+ # Instantiate a new `Responds`.
24
+ def initialize map,
25
+ public: true,
26
+ **options
27
+ @map = NRSER.map_values(map) { |args, type|
28
+ NRSER::Types.make type
29
+ }
30
+ end # #initialize
31
+
32
+
33
+ # Instance Methods
34
+ # ======================================================================
35
+
36
+
37
+ def default_name
38
+ attrs_str = @map.map { |args, type|
39
+ args_str = args[1..-1].map(&:inspect).join ', '
40
+ "#{ args[0] }(#{ args_str })=#{ type.name }"
41
+ }.join(', ')
42
+
43
+ "#{ self.class.short_name } #{ attrs_str }"
44
+ end
45
+
46
+
47
+ # @todo Document test method.
48
+ #
49
+ # @param [type] arg_name
50
+ # @todo Add name param description.
51
+ #
52
+ # @return [return_type]
53
+ # @todo Document return value.
54
+ #
55
+ def test value
56
+ @map.all? { |args, type|
57
+ response = if @public
58
+ value.public_send *args
59
+ else
60
+ value.send *args
61
+ end
62
+
63
+ type.test response
64
+ }
65
+ end # #test
66
+
67
+
68
+ end # class Responds
69
+
70
+
71
+ # Eigenclass (Singleton Class)
72
+ # ========================================================================
73
+ #
74
+ class << self
75
+
76
+
77
+ # @todo Document responds method.
78
+ #
79
+ # @param [type] arg_name
80
+ # @todo Add name param description.
81
+ #
82
+ # @return [return_type]
83
+ # @todo Document return value.
84
+ #
85
+ def responds *args
86
+ Responds.new *args
87
+ end # #responds
88
+
89
+
90
+
91
+ # @todo Document respond_to Responds.
92
+ #
93
+ # @param [type] arg_name
94
+ # @todo Add name param description.
95
+ #
96
+ # @return [return_type]
97
+ # @todo Document return value.
98
+ #
99
+ def respond_to name, **options
100
+ responds(
101
+ {[:respond_to?, name] => NRSER::Types::TRUE},
102
+ **options
103
+ )
104
+ end # #respond_to
105
+
106
+
107
+ end # class << self (Eigenclass)
108
+
109
+
110
+ end # module NRSER::Types
@@ -31,4 +31,10 @@ module NRSER::Types
31
31
 
32
32
  NON_EMPTY_STR = str length: {min: 1}, name: "NonEmptyStr"
33
33
 
34
+
35
+ def self.non_empty_str
36
+ NON_EMPTY_STR
37
+ end # .non_empty_str
38
+
39
+
34
40
  end # NRSER::Types
@@ -24,14 +24,19 @@ module NRSER::Types
24
24
  raise NotImplementedError
25
25
  end
26
26
 
27
- def check value
28
- unless test value
29
- raise TypeError.new NRSER.squish <<-END
30
- value #{ value.inspect } failed check #{ self.inspect }
27
+ def check value, &make_fail_message
28
+ # success case
29
+ return value if test value
30
+
31
+ msg = if make_fail_message
32
+ make_fail_message.call type: self, value: value
33
+ else
34
+ NRSER.squish <<-END
35
+ value #{ value.inspect } failed check #{ self.to_s }
31
36
  END
32
37
  end
33
38
 
34
- value
39
+ raise TypeError.new msg
35
40
  end
36
41
 
37
42
  def respond_to? name, include_all = false
@@ -55,7 +60,7 @@ module NRSER::Types
55
60
  end
56
61
 
57
62
  def to_s
58
- "<Type:#{ name }>"
63
+ "`Type: #{ name }`"
59
64
  end
60
65
  end # Type
61
66
  end # NRSER::Types
data/lib/nrser/types.rb CHANGED
@@ -8,6 +8,7 @@ require 'nrser/types/where'
8
8
  require 'nrser/types/combinators'
9
9
  require 'nrser/types/maybe'
10
10
  require 'nrser/types/attrs'
11
+ require 'nrser/types/responds'
11
12
 
12
13
  using NRSER
13
14
 
@@ -32,21 +33,55 @@ module NRSER::Types
32
33
  make(type).test value
33
34
  end
34
35
 
35
- def self.match value, type_map
36
- type_map.each {|type, block|
36
+ def self.match value, *clauses
37
+ if clauses.empty?
38
+ raise ArgumentError.new NRSER.dedent <<-END
39
+ Must supply either a single {type => expression} hash argument or a
40
+ even amount of arguments representing (type, expression) pairs after
41
+ `value`.
42
+
43
+ #{ NRSER::Version.doc_url 'NRSER/Types#match-class_method' }
44
+ END
45
+ end
46
+
47
+ enum = if clauses.length == 1 && clauses.first.respond_to?(:each_pair)
48
+ clauses.first.each_pair
49
+ else
50
+ unless clauses.length % 2 == 0
51
+ raise TypeError.new NRSER.dedent <<-END
52
+ When passing a list of clauses, it must be an even length
53
+ representing (type, expression) pairs.
54
+
55
+ Found an argument list with length #{ clauses.length }:
56
+
57
+ #{ clauses }
58
+ END
59
+ end
60
+
61
+ clauses.each_slice(2)
62
+ end
63
+
64
+ enum.each { |type, expression|
37
65
  if test value, type
38
- return block.call value
66
+ # OK, we matched! Is the corresponding expression callable?
67
+ if expression.respond_to? :call
68
+ # It is; invoke and return result.
69
+ return expression.call value
70
+ else
71
+ # It's not; assume it's a value and return it.
72
+ return expression
73
+ end
39
74
  end
40
75
  }
41
76
 
42
77
  raise TypeError, <<-END.dedent
43
- could not match value
78
+ Could not match value
44
79
 
45
- #{ value.inspect }
80
+ #{ value.inspect }
46
81
 
47
82
  to any of types
48
83
 
49
- #{ type_map.keys.map {|type| "\n #{ type.inspect }"} }
84
+ #{ enum.map {|type, expression| "\n #{ type.inspect }"} }
50
85
 
51
86
  END
52
87
  end
data/lib/nrser/version.rb CHANGED
@@ -1,3 +1,52 @@
1
1
  module NRSER
2
- VERSION = "0.0.20"
2
+ VERSION = "0.0.21"
3
+
4
+ module Version
5
+
6
+ # @return [Gem::Version]
7
+ # Parse of {NRSER::VERSION}.
8
+ #
9
+ def self.gem_version
10
+ Gem::Version.new VERSION
11
+ end # .gem_version
12
+
13
+
14
+ # The `Gem::Version` "release" for {NRSER::VERSION} - everything before
15
+ # any `-<alpha-numeric>` prerelease part (like `-dev`).
16
+ #
17
+ # @see https://ruby-doc.org/stdlib-2.4.1/libdoc/rubygems/rdoc/Gem/Version.html#method-i-release
18
+ #
19
+ # @example
20
+ #
21
+ # NRSER::VERSION
22
+ # # => '0.0.21.dev'
23
+ #
24
+ # NRSER::Version.release
25
+ # # => #<Gem::Version "0.0.21">
26
+ #
27
+ # @return [Gem::Version]
28
+ #
29
+ def self.release
30
+ gem_version.release
31
+ end # .release
32
+
33
+
34
+ # Get a URL to a place in the current version's docs on ruby-docs.org.
35
+ #
36
+ # @param [String] rel_path
37
+ # Relative path.
38
+ #
39
+ # @return [String]
40
+ # The RubyDocs URL.
41
+ #
42
+ def self.doc_url rel_path
43
+ File.join(
44
+ "http://www.rubydoc.info/gems/nrser",
45
+ NRSER::Version.release.to_s,
46
+ rel_path
47
+ )
48
+ end # .doc_url
49
+
50
+
51
+ end
3
52
  end
data/lib/nrser.rb CHANGED
@@ -12,3 +12,4 @@ require_relative './nrser/hash'
12
12
  require_relative './nrser/array'
13
13
  require_relative './nrser/types'
14
14
  require_relative './nrser/meta'
15
+ require_relative './nrser/open_struct'
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ describe "NRSER Enumerable Methods" do
4
+
5
+ describe NRSER.method(:find_bounded) do
6
+
7
+ context "when just :length bounds arg is provided" do
8
+ it "returns found elements when length is correct" do
9
+ expect(
10
+ subject.([1, 2, 3], length: 1) { |i| i == 2 }
11
+ ).to eq [2]
12
+ end
13
+
14
+ it "raises TypeError when length in incorrect" do
15
+ expect {
16
+ subject.([1, 2, 3], length: 2) { |i| i == 2 }
17
+ }.to raise_error TypeError
18
+ end
19
+ end # when just :length bounds arg is provided
20
+
21
+ context "when just :min bounds arg is provided" do
22
+ it "returns found elements when min is correct" do
23
+ expect(
24
+ subject.([1, 2, 3], min: 1) { |i| i == 2 }
25
+ ).to eq [2]
26
+ end
27
+
28
+ it "raises TypeError when min in incorrect" do
29
+ expect {
30
+ subject.([1, 2, 3], min: 2) { |i| i == 2 }
31
+ }.to raise_error TypeError
32
+ end
33
+ end # when just :min bounds arg is provided
34
+
35
+ context "when just :max bounds arg is provided" do
36
+ it "returns found elements when max is correct" do
37
+ expect(
38
+ subject.([1, 2, 3], max: 2) { |i| i >= 2 }
39
+ ).to eq [2, 3]
40
+ end
41
+
42
+ it "raises TypeError when max in incorrect" do
43
+ expect {
44
+ subject.([1, 2, 3], max: 1) { |i| i >= 2 }
45
+ }.to raise_error TypeError
46
+ end
47
+ end # when just :max bounds arg is provided
48
+
49
+
50
+ context "when :min and :max bounds args are both provided" do
51
+ it "returns found elements when min and max are correct" do
52
+ expect(
53
+ subject.([1, 2, 3], min: 1, max: 2) { |i| i >= 2 }
54
+ ).to eq [2, 3]
55
+ end
56
+
57
+ it "raises TypeError when min is incorrect" do
58
+ expect {
59
+ subject.([1, 2, 3], min: 1, max: 2) { |i| false }
60
+ }.to raise_error TypeError
61
+ end
62
+
63
+ it "raises TypeError when max is incorrect" do
64
+ expect {
65
+ subject.([1, 2, 3], min: 1, max: 2) { |i| true }
66
+ }.to raise_error TypeError
67
+ end
68
+ end # when :min and :max bounds args are both provided
69
+
70
+ end # NRSER.method(:find_bounded)
71
+
72
+
73
+ describe NRSER.method(:find_only) do
74
+
75
+ it "returns the element when only one is found" do
76
+ expect(
77
+ subject.call [1, 2, 3] { |i| i == 2 }
78
+ ).to be 2
79
+ end
80
+
81
+ it "raises TypeError when more than one element is found" do
82
+ expect {
83
+ subject.call [1, 2, 3] { |i| i >= 2 }
84
+ }.to raise_error TypeError
85
+ end
86
+
87
+ it "raises TypeError when no elements are found" do
88
+ expect {
89
+ subject.call [1, 2, 3] { |i| false }
90
+ }.to raise_error TypeError
91
+ end
92
+
93
+ end # NRSER.method(:find_only)
94
+
95
+
96
+ end # NRSER Enumerable Methods
97
+
@@ -1,8 +1,11 @@
1
+ require 'set'
2
+ require 'ostruct'
3
+
1
4
  require 'spec_helper'
2
5
 
3
6
  using NRSER
4
7
 
5
- describe NRSER.method(:truncate) do
8
+ describe NRSER.method(:leaves) do
6
9
  it do
7
10
  expect(NRSER.leaves({a: 1, b: 2})).to eq ({[:a] => 1, [:b] => 2})
8
11
  expect(
@@ -21,4 +24,45 @@ describe NRSER.method(:truncate) do
21
24
  [:b] => 'bee',
22
25
  })
23
26
  end
24
- end # truncate
27
+ end # NRSER.leaves
28
+
29
+ describe NRSER.method(:map_values) do
30
+
31
+ it "handles hashes" do
32
+ expect(
33
+ NRSER.map_values({a: 1, b: 2}) { |k, v| v * 3 }
34
+ ).to eq(
35
+ {a: 3, b: 6}
36
+ )
37
+ end # handles hashes
38
+
39
+
40
+ it "handles arrays" do
41
+ expect(
42
+ NRSER.map_values([:a, :b, :c]) { |k, v| "#{ k } is ok!" }
43
+ ).to eq(
44
+ {a: "a is ok!", b: "b is ok!", c: "c is ok!"}
45
+ )
46
+ end # handles arrays
47
+
48
+
49
+ it "handles sets" do
50
+ expect(
51
+ NRSER.map_values(Set.new [:a, :b, :c]) { |k, v| "#{ k } is ok!" }
52
+ ).to eq(
53
+ {a: "a is ok!", b: "b is ok!", c: "c is ok!"}
54
+ )
55
+ end # handles sets
56
+
57
+
58
+ it "handles OpenStruct instances" do
59
+ expect(
60
+ NRSER.map_values(OpenStruct.new a: 1, b: 2) { |k, v| v * 3 }
61
+ ).to eq(
62
+ {a: 3, b: 6}
63
+ )
64
+ end # handles OpenStruct instances
65
+
66
+
67
+ end # NRSER.map_values
68
+