nrser 0.0.20 → 0.0.21

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