dry-matcher 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7b7b475f9c2a102c0da7f6cf3422b8553e0f5d2f
4
- data.tar.gz: 5c618057052448e4986233291fcadbe794c4b687
3
+ metadata.gz: 6d10419e259ad298f3f7406069c3abedaf6d0b3a
4
+ data.tar.gz: 73c64df0c8dcdbfa5729e1788af55c59c464d1ca
5
5
  SHA512:
6
- metadata.gz: 93a241b0c7fd705e703f6f3b021cbcf19b3ef1f88eeb45605dfcb815daf670ab547e9f306178c8fa8859c0d190de3c13c99d4d6a9a319ee09f84328d744630b5
7
- data.tar.gz: 0e3cc0b1df1587600fe864336d84fcbecf94432c363a279c0f0382c96dbd0542ddf0b1d9c5ba63d321e1bc450e44724e13db5dcb561f9ec227dbd154e33f1dda
6
+ metadata.gz: 7fe5b016af423a72b690fea03bc18f310fca34d580665345096a283cf937ee26c27bac3f0de69d2a279308700b1ddc85e478e70bb8518a194ae79ae2ac692f3b
7
+ data.tar.gz: 669f02bcb69d4f0c57fb38bdd0534561cf1924c4e2ecdf7cc4bf40fe928e7af585f3b5ee8c7d91799475e3b37565f609b1620c1d192538da3fb96987d42c797b
@@ -1,3 +1,24 @@
1
+ # 0.6.0 / 2016-12-19
2
+
3
+ ## Added
4
+
5
+ - API documentation for most methods (alsemyonov in [#8](https://github.com/dry-rb/dry-matcher/pull/8))
6
+
7
+ ## Changed
8
+
9
+ - Matches must now be exhaustive - when matching against a value, at least one match block must be provided for each of a matcher's cases (timriley in [#7](https://github.com/dry-rb/dry-matcher/pull/7))
10
+ - `Dry::Matcher::Case` objects can now be created without a `resolve:` proc. In this case, a default will be provided that passes the result value through (timriley in [#9](https://github.com/dry-rb/dry-matcher/pull/9))
11
+
12
+ [Compare v0.5.1...v0.6.0](https://github.com/dry-rb/dry-result_matcher/compare/v0.5.1...v0.6.0)
13
+
14
+ # 0.5.1 / 2016-07-06
15
+
16
+ ## Fixed
17
+
18
+ - Fixed handling of calls to non-existent cases within a matcher block (timriley)
19
+
20
+ [Compare v0.5.0...v0.5.1](https://github.com/dry-rb/dry-result_matcher/compare/v0.5.0...v0.5.1)
21
+
1
22
  # 0.5.0 / 2016-06-30
2
23
 
3
24
  ## Added
@@ -59,6 +80,8 @@
59
80
  ```
60
81
  - The previous `Dry::ResultMatcher.match` behaviour (for matching `Either` monads) has been moved to `Dry::Matcher::EitherMatcher.call`
61
82
 
83
+ [Compare v0.4.0...v0.5.0](https://github.com/dry-rb/dry-result_matcher/compare/v0.4.0...v0.5.0)
84
+
62
85
  # 0.4.0 / 2016-06-08
63
86
 
64
87
  ## Added
data/Gemfile CHANGED
@@ -3,7 +3,8 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  group :test do
6
- gem "codeclimate-test-reporter", "~> 0.5.0"
6
+ gem "simplecov"
7
+ gem "codeclimate-test-reporter"
7
8
  gem "dry-monads", "~> 0.0.2"
8
9
  end
9
10
 
data/Rakefile CHANGED
@@ -4,3 +4,7 @@ require "rspec/core/rake_task"
4
4
  RSpec::Core::RakeTask.new
5
5
 
6
6
  task default: :spec
7
+
8
+ require 'yard'
9
+ require 'yard/rake/yardoc_task'
10
+ YARD::Rake::YardocTask.new(:doc)
@@ -2,7 +2,30 @@ require "dry/matcher/case"
2
2
  require "dry/matcher/evaluator"
3
3
 
4
4
  module Dry
5
+ # @see http://dry-rb.org/gems/dry-matcher
5
6
  class Matcher
7
+ # Generates a module containing pattern matching for methods listed in
8
+ # `match_methods` argument with behavior defined by `with` matcher
9
+ #
10
+ # @param [<Symbol>] match_methods
11
+ # @param [Dry::Matcher] with
12
+ # @return [Module]
13
+ #
14
+ # @example Usage with `dry-monads`
15
+ # class MonadicOperation
16
+ # include Dry::Matcher.for(:call, with: Dry::Matcher::EitherMatcher)
17
+ #
18
+ # def call
19
+ # Dry::Monads::Either::Right.new('Success')
20
+ # end
21
+ # end
22
+ #
23
+ # operation = MonadicOperation.new
24
+ #
25
+ # operation.call do |m|
26
+ # m.success { |v| "#{v} was successful!"}
27
+ # m.failure { |v| "#{v} has failed!"}
28
+ # end #=> "Success was successful"
6
29
  def self.for(*match_methods, with:)
7
30
  matcher = with
8
31
 
@@ -29,12 +52,33 @@ module Dry
29
52
  end
30
53
  end
31
54
 
55
+ # @return [Hash{Symbol => Case}]
32
56
  attr_reader :cases
33
57
 
58
+ # @param [Hash{Symbol => Case}] cases
34
59
  def initialize(cases = {})
35
60
  @cases = cases
36
61
  end
37
62
 
63
+ # Evaluate {#cases}' matchers and returns a result of block given to
64
+ # corresponding case matcher
65
+ #
66
+ # @param [Object] result value that would be tested for matches with given {#cases}
67
+ # @param [Object] block
68
+ # @yieldparam [Evaluator] m
69
+ # @return [Object] value returned from the block given to method called
70
+ # after matched pattern
71
+ #
72
+ # @example Usage with `dry-monads`
73
+ # require 'dry-monads'
74
+ # require 'dry/matcher/either_matcher'
75
+ #
76
+ # value = Dry::Monads::Either::Left.new('failure!')
77
+ #
78
+ # Dry::Matcher::EitherMatcher.(value) do |m|
79
+ # m.success { |v| "Yay: #{v}" }
80
+ # m.failure { |v| "Boo: #{v}" }
81
+ # end #=> "Boo: failure!"
38
82
  def call(result, &block)
39
83
  Evaluator.new(result, cases).call(&block)
40
84
  end
@@ -1,15 +1,33 @@
1
1
  module Dry
2
2
  class Matcher
3
+ # {Case} object contains logic for pattern matching and resolving result
4
+ # from matched pattern
3
5
  class Case
4
- def initialize(match:, resolve:)
6
+ DEFAULT_RESOLVE = -> result { result }
7
+
8
+ # @param match [#call] callable used to test given pattern against value
9
+ # @param resolve [#call] callable used to resolve value into a result
10
+ def initialize(match:, resolve: DEFAULT_RESOLVE)
5
11
  @match = match
6
12
  @resolve = resolve
7
13
  end
8
14
 
15
+ # Tests whether `value` (with optional `*pattern`) matches pattern using
16
+ # callable given to {#initialize} as `match:` argument
17
+ #
18
+ # @param [Object] value
19
+ # @param [<Object>] pattern optional pattern given after the `value` to
20
+ # `match:` callable
21
+ # @return [Boolean]
9
22
  def matches?(value, *pattern)
10
23
  @match.(value, *pattern)
11
24
  end
12
25
 
26
+ # Resolves result from `value` using callable given to {#initialize}
27
+ # as `resolve:` argument
28
+ #
29
+ # @param [Object] value
30
+ # @return [Object] result resolved from given `value`
13
31
  def resolve(value)
14
32
  @resolve.(value)
15
33
  end
@@ -2,6 +2,52 @@ require "dry/matcher"
2
2
 
3
3
  module Dry
4
4
  class Matcher
5
+ # Built-in {Matcher} ready to use with `Either` or `Try` monads from
6
+ # [dry-monads](/gems/dry-monads) or any other compatible gems.
7
+ #
8
+ # Provides {Case}s for two matchers:
9
+ # * `:success` matches `Dry::Monads::Either::Right`
10
+ # and `Dry::Monads::Try::Success` (or any other monad that responds to
11
+ # `#to_either` returning either monad that is `#right?`)
12
+ # * `:failure` matches `Dry::Monads::Either::Left` and
13
+ # `Dry::Monads::Try::Failure` (or any other monad that responds to
14
+ # `#to_either` returning either monad that is `#left?`)
15
+ #
16
+ # @return [Dry::Matcher]
17
+ #
18
+ # @example Usage with `dry-monads`
19
+ # require 'dry-monads'
20
+ # require 'dry/matcher/either_matcher'
21
+ #
22
+ # value = Dry::Monads::Either::Right.new('success!')
23
+ #
24
+ # Dry::Matcher::EitherMatcher.(value) do |m|
25
+ # m.success do |v|
26
+ # "Yay: #{v}"
27
+ # end
28
+ #
29
+ # m.failure do |v|
30
+ # "Boo: #{v}"
31
+ # end
32
+ # end #=> "Yay: success!"
33
+ #
34
+ # @example Usage with custom monad
35
+ # require 'dry/matcher/either_matcher'
36
+ #
37
+ # class CustomBooleanMonad
38
+ # def initialize(value); @value = value; end
39
+ # attr_reader :value
40
+ # alias_method :right?, :value
41
+ # def left?; !right?; end
42
+ # def to_either; self; end
43
+ # end
44
+ #
45
+ # value = CustomBooleanMonad.new(nil)
46
+ #
47
+ # Dry::Matcher::EitherMatcher.(value) do |m|
48
+ # m.success { |v| "#{v.inspect} is truthy" }
49
+ # m.failure { |v| "#{v.inspect} is falsey" }
50
+ # end # => "nil is falsey"
5
51
  EitherMatcher = Dry::Matcher.new(
6
52
  success: Case.new(
7
53
  match: -> result, *pattern {
@@ -1,30 +1,67 @@
1
1
  module Dry
2
2
  class Matcher
3
+ NonExhaustiveMatchError = Class.new(StandardError)
4
+
5
+ # {Evaluator} is used in {Dry::Matcher#call Dry::Matcher#call} block to handle different {Case}s
3
6
  class Evaluator < BasicObject
7
+ # @param [Object] result
8
+ # @param [Hash{Symbol => Case}] cases
4
9
  def initialize(result, cases)
5
10
  @cases = cases
6
11
  @result = result
12
+
13
+ @unhandled_cases = @cases.keys.map(&:to_sym)
7
14
  @matched = false
8
15
  @output = nil
9
16
  end
10
17
 
11
18
  def call
12
19
  yield self
20
+
21
+ ensure_exhaustive_match
22
+
13
23
  @output
14
24
  end
15
25
 
26
+ # Checks whether `cases` given to {#initialize} contains one called `name`
27
+ # @param [String] name
28
+ # @param [Boolean] include_private
29
+ # @return [Boolean]
16
30
  def respond_to_missing?(name, include_private = false)
17
- @cases.key?(name) || super
31
+ @cases.key?(name)
18
32
  end
19
33
 
34
+ # Handles method `name` called after one of the keys in `cases` hash given
35
+ # to {#initialize}
36
+ #
37
+ # @param [String] name name of the case given to {#initialize} in `cases`
38
+ # argument
39
+ # @param [Array] args pattern that would be tested for match and used to
40
+ # resolve result
41
+ # @param [#call] block callable that will processes resolved value
42
+ # from matched pattern
43
+ # @yieldparam [Object] v resolved value
44
+ # @return [Object] result of calling `block` on value resolved from `args`
45
+ # if `args` pattern was matched by the given case called `name`
46
+ # @raise [NoMethodError] if there was no case called `name` given to
47
+ # {#initialize} in `cases` hash
20
48
  def method_missing(name, *args, &block)
21
49
  return super unless @cases.key?(name)
22
50
 
51
+ @unhandled_cases.delete name
23
52
  handle_case @cases[name], *args, &block
24
53
  end
25
54
 
26
55
  private
27
56
 
57
+ def ensure_exhaustive_match
58
+ if @unhandled_cases.any?
59
+ ::Kernel.raise NonExhaustiveMatchError, "cases +#{@unhandled_cases.join(', ')}+ not handled"
60
+ end
61
+ end
62
+
63
+ # @param [Case] kase
64
+ # @param [Array] pattern
28
65
  def handle_case(kase, *pattern)
29
66
  return @output if @matched
30
67
 
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  class Matcher
3
- VERSION = "0.5.0".freeze
3
+ VERSION = "0.6.0".freeze
4
4
  end
5
5
  end
@@ -1,14 +1,18 @@
1
1
  example_id | status | run_time |
2
2
  ----------------------------------------------------- | ------ | --------------- |
3
- ./spec/integration/class_enhancement_spec.rb[1:1:1:1] | passed | 0.00016 seconds |
4
- ./spec/integration/class_enhancement_spec.rb[1:1:2:1] | passed | 0.00013 seconds |
5
- ./spec/integration/class_enhancement_spec.rb[1:2:1:1] | passed | 0.00022 seconds |
6
- ./spec/integration/class_enhancement_spec.rb[1:2:2:1] | passed | 0.00141 seconds |
7
- ./spec/integration/either_matcher_spec.rb[1:1:1:1] | passed | 0.00008 seconds |
8
- ./spec/integration/either_matcher_spec.rb[1:1:2:1] | passed | 0.00008 seconds |
9
- ./spec/integration/either_matcher_spec.rb[1:1:3:1:1] | passed | 0.00015 seconds |
10
- ./spec/integration/either_matcher_spec.rb[1:1:3:2:1] | passed | 0.00016 seconds |
11
- ./spec/integration/matcher_spec.rb[1:1:1] | passed | 0.00013 seconds |
12
- ./spec/integration/matcher_spec.rb[1:1:2] | passed | 0.00012 seconds |
13
- ./spec/integration/matcher_spec.rb[1:1:3:1] | passed | 0.00015 seconds |
14
- ./spec/integration/matcher_spec.rb[1:1:3:2] | passed | 0.00224 seconds |
3
+ ./spec/integration/class_enhancement_spec.rb[1:1:1:1] | passed | 0.00015 seconds |
4
+ ./spec/integration/class_enhancement_spec.rb[1:1:2:1] | passed | 0.00014 seconds |
5
+ ./spec/integration/class_enhancement_spec.rb[1:2:1:1] | passed | 0.00088 seconds |
6
+ ./spec/integration/class_enhancement_spec.rb[1:2:2:1] | passed | 0.00013 seconds |
7
+ ./spec/integration/either_matcher_spec.rb[1:1:1:1] | passed | 0.00011 seconds |
8
+ ./spec/integration/either_matcher_spec.rb[1:1:2:1] | passed | 0.0001 seconds |
9
+ ./spec/integration/either_matcher_spec.rb[1:1:3:1:1] | passed | 0.00016 seconds |
10
+ ./spec/integration/either_matcher_spec.rb[1:1:3:2:1] | passed | 0.00015 seconds |
11
+ ./spec/integration/matcher_spec.rb[1:1:1] | passed | 0.00016 seconds |
12
+ ./spec/integration/matcher_spec.rb[1:1:2] | passed | 0.00015 seconds |
13
+ ./spec/integration/matcher_spec.rb[1:1:3] | passed | 0.00159 seconds |
14
+ ./spec/integration/matcher_spec.rb[1:1:4:1] | passed | 0.00018 seconds |
15
+ ./spec/integration/matcher_spec.rb[1:1:4:2] | passed | 0.00185 seconds |
16
+ ./spec/unit/case_spec.rb[1:1:1] | passed | 0.00054 seconds |
17
+ ./spec/unit/case_spec.rb[1:2:1] | passed | 0.00048 seconds |
18
+ ./spec/unit/case_spec.rb[1:2:2] | passed | 0.00008 seconds |
@@ -45,7 +45,24 @@ RSpec.describe Dry::Matcher do
45
45
  expect(call_match(input)).to eq "Failure: No!"
46
46
  end
47
47
 
48
+ it "requires an exhaustive match" do
49
+ input = Dry::Monads::Right("Yes!")
50
+
51
+ expect {
52
+ matcher.(input) do |m|
53
+ m.success { |v| "Success: #{v}" }
54
+ end
55
+ }.to raise_error Dry::Matcher::NonExhaustiveMatchError
56
+ end
57
+
48
58
  context "with patterns" do
59
+ let(:success_case) {
60
+ Dry::Matcher::Case.new(
61
+ match: -> result { result.first == :ok },
62
+ resolve: -> result { result.last },
63
+ )
64
+ }
65
+
49
66
  let(:failure_case) {
50
67
  Dry::Matcher::Case.new(
51
68
  match: -> result, failure_type {
@@ -57,6 +74,8 @@ RSpec.describe Dry::Matcher do
57
74
 
58
75
  def call_match(input)
59
76
  matcher.(input) do |m|
77
+ m.success
78
+
60
79
  m.failure :my_error do |v|
61
80
  "Pattern-matched failure: #{v}"
62
81
  end
@@ -1,12 +1,9 @@
1
- if RUBY_ENGINE == "ruby"
2
- require "codeclimate-test-reporter"
3
- CodeClimate::TestReporter.start
4
-
5
- SimpleCov.start do
6
- add_filter "/spec/"
7
- end
1
+ if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.3"
2
+ require "simplecov"
3
+ SimpleCov.start
8
4
  end
9
5
 
6
+
10
7
  begin
11
8
  require "byebug"
12
9
  rescue LoadError; end
@@ -0,0 +1,22 @@
1
+ RSpec.describe Dry::Matcher::Case do
2
+ describe "#matches?" do
3
+ it "calls the match proc with the value" do
4
+ kase = described_class.new(match: -> value { value.even? })
5
+ expect(kase.matches?(2)).to be true
6
+ expect(kase.matches?(1)).to be false
7
+ end
8
+ end
9
+
10
+ describe "#resolve" do
11
+ it "calls the resolve proc with the value" do
12
+ kase = described_class.new(match: -> * { true }, resolve: resolve = -> value { value.to_s })
13
+
14
+ expect(kase.resolve(123)).to eq "123"
15
+ end
16
+
17
+ it "defaults to passing through the value" do
18
+ kase = described_class.new(match: -> * { true })
19
+ expect(kase.resolve(123)).to eq 123
20
+ end
21
+ end
22
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-matcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Riley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-30 00:00:00.000000000 Z
11
+ date: 2016-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -103,6 +103,7 @@ files:
103
103
  - spec/integration/either_matcher_spec.rb
104
104
  - spec/integration/matcher_spec.rb
105
105
  - spec/spec_helper.rb
106
+ - spec/unit/case_spec.rb
106
107
  homepage: http://dry-rb.org/gems/dry-matcher
107
108
  licenses:
108
109
  - MIT