eqq 0.0.2 → 0.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd093a7aa8c034327d463c56bfe120a8ecc48b52649ba7900491f05cc130cc38
4
- data.tar.gz: 13f7ca2f2f40d9be41abd4408f5486b892f345347eb07eeb3f4d936c47cc92ab
3
+ metadata.gz: 22307661e329771fea7efcd7c282d17eda0130d0edb7aec3088257b21b73e4ba
4
+ data.tar.gz: ebc3138ade59e38f9bc34a42a93654d3a6ab1564a48b47df13d65ed7227c7488
5
5
  SHA512:
6
- metadata.gz: 2e510f5313e0f890bf3c8277a413f937b0a26e2ba58e501591288556783bc5fffc944c23a1216ddf95308a111b06c6ac82557526d9f7de49ef9dbe7806f93bb6
7
- data.tar.gz: 719905cbd2f71a388e13ca0427b7db49115513ce11395a436d8ef9fc43397c9c77cc408135862147bbfc3bac3fb36615332752f56ab28e4136bc74b8db3f97dd
6
+ metadata.gz: 16ae2b50b3c81291028946ec6ddb8753c06bc7fc472f29ab00b3f69b9136540f26079b8c23f983d0bf1482f93543744f2fae557bb0ef71f6fdcc73f115df9455
7
+ data.tar.gz: eced8529614ce3b1b8a4cc4087d58943bdbaabd0637a86bb10bcafe472ccdece82bae71d390fde84e95176318961e4ef74207ab4debc6e117411608f2e074176
data/README.md CHANGED
@@ -3,9 +3,7 @@
3
3
  ![Build Status](https://github.com/kachick/eqq/actions/workflows/test_behaviors.yml/badge.svg?branch=main)
4
4
  [![Gem Version](https://badge.fury.io/rb/eqq.png)](http://badge.fury.io/rb/eqq)
5
5
 
6
- Pattern objects builder.
7
-
8
- `eqq` means `#===`
6
+ Pattern objects builder
9
7
 
10
8
  ## Usage
11
9
 
@@ -14,7 +12,7 @@ Require Ruby 2.6 or later
14
12
  Add below code into your Gemfile
15
13
 
16
14
  ```ruby
17
- gem 'eqq', '0.0.2'
15
+ gem 'eqq', '0.0.3'
18
16
  ```
19
17
 
20
18
  ### Overview
@@ -29,10 +27,94 @@ require 'eqq'
29
27
  pattern = Eqq.define do
30
28
  OR(AND(Float, 20..50), Integer)
31
29
  end
30
+
31
+ p pattern #=> "OR(AND(Float, 20..50), Integer)"
32
32
  [4.2, 42, 42.0, 420].grep(pattern) #=> [42, 42.0, 420]
33
+
34
+ inverted = Eqq.NOT(pattern)
35
+ p inverted #=> "NOT(OR(AND(Float, 20..50), Integer))"
36
+ [4.2, 42, 42.0, 420].grep(inverted) #=> [4.2]
37
+
38
+ Eqq.SEND(:all?, pattern) === [4.2, 42, 42.0, 420] #=> false
39
+ Eqq.SEND(:any?, pattern) === [4.2, 42, 42.0, 420] #=> true
40
+
41
+ ret_in_case = (
42
+ case 42
43
+ when pattern
44
+ 'Should be matched here! :)'
45
+ when inverted
46
+ 'Should not be matched here! :<'
47
+ else
48
+ 'Should not be matched here too! :<'
49
+ end
50
+ )
51
+
52
+ p ret_in_case #=> Should be matched here! :)
53
+
54
+ ret_in_case = (
55
+ case 4.2
56
+ when pattern
57
+ 'Should not be matched here! :<'
58
+ when inverted
59
+ 'Should be matched here! :)'
60
+ else
61
+ 'Should not be matched here too! :<'
62
+ end
63
+ )
64
+
65
+ p ret_in_case #=> Should be matched here! :)
33
66
  ```
34
67
 
68
+ ### Explanation
69
+
70
+ All products can be called as `pattern === other`.
71
+
72
+ This signature will fit in most Ruby code.
73
+
74
+ * `case ~ when` syntax
75
+ * Enumerable#grep
76
+ * Enumerable#grep_v
77
+ * Enumerable#all?
78
+ * Enumerable#any?
79
+ * Enumerable#none?
80
+ * Enumerable#one?
81
+ * Enumerable#slice_after
82
+ * Enumerable#slice_before
83
+
84
+ They can take this interface as the `pattern`.
85
+
86
+ And you already saw. All of patterns can be mixed with other patterns as a parts.
87
+ Reuse as you wish!
88
+
89
+ Major builders as below
90
+
91
+ * OR(*patterns) - Product returns true when matched even one pattern
92
+ * AND(*patterns) - Product returns true when matched all patterns
93
+ * NOT(pattern) - Product returns true when not matched the pattern
94
+ * CAN(*method_names) - Product returns true when it has all of the methods (checked with `respond_to?`)
95
+ * RESCUE(exception_class/module, pattern) - Product returns true when the pattern raises the exception
96
+ * QUIET(*patterns) - Product returns true when all patterns did not raise any exception
97
+ * EQ(object) - Product returns true when matched with `#==`
98
+ * SAME(object) - Product returns true when matched with `#equal?`
99
+ * SEND(name, pattern) - Basically provided for Enumerable
100
+ * BOOLEAN() - Product returns true when matched to true or false
101
+ * ANYTHING() - Product returns true, always true
102
+
103
+ Minor builders as below, please see [API documents](https://kachick.github.io/eqq) for them.
104
+
105
+ * NAND
106
+ * NOR
107
+ * XOR
108
+
109
+ When you feel annoy to write `Eqq` in many place, please use `Eqq.define`.
110
+ In the block scope, all builder methods can be used without receiver specifying.
111
+
112
+ This gem provide [ruby/rbs](https://github.com/ruby/rbs) signature
113
+
35
114
  ## Links
36
115
 
37
116
  * [Repository](https://github.com/kachick/eqq)
38
- * [API documents](https://kachick.github.io/eqq)
117
+
118
+ ## NOTE
119
+
120
+ * [`eqq` is the implementation name of `#===` in CRuby](https://github.com/ruby/ruby/blob/2a685da1fcd928530509e99f5edb4117bc377994/range.c#L1859)
data/lib/eqq.rb CHANGED
@@ -4,19 +4,12 @@
4
4
  # Copyright (c) 2011 Kenichi Kamiya
5
5
  # Forked from https://github.com/kachick/validation at 2021
6
6
 
7
- require_relative 'eqq/buildable'
8
7
  require_relative 'eqq/version'
9
8
 
10
9
  module Eqq
11
- extend Buildable
12
-
13
10
  class Error < StandardError; end
14
11
  class InvalidProductError < Error; end
15
12
 
16
- class DSLScope
17
- include Buildable
18
- end
19
-
20
13
  class << self
21
14
  def valid?(object)
22
15
  case object
@@ -31,6 +24,7 @@ module Eqq
31
24
  end
32
25
  end
33
26
 
27
+ # @return [#===]
34
28
  def define(&block)
35
29
  pattern = DSLScope.new.instance_exec(&block)
36
30
  raise InvalidProductError unless valid?(pattern)
@@ -39,3 +33,13 @@ module Eqq
39
33
  end
40
34
  end
41
35
  end
36
+
37
+ require_relative 'eqq/buildable'
38
+
39
+ module Eqq
40
+ extend Buildable
41
+
42
+ class DSLScope
43
+ include Buildable
44
+ end
45
+ end
data/lib/eqq/buildable.rb CHANGED
@@ -5,15 +5,59 @@ module Eqq
5
5
  module Buildable
6
6
  extend self
7
7
 
8
+ class << self
9
+ INSPECTION_FALLBACK = 'UninspectableObject'
10
+
11
+ # @api private
12
+ # @return [String]
13
+ def safe_inspect(object)
14
+ String.try_convert(object.inspect) || INSPECTION_FALLBACK
15
+ rescue Exception
16
+ # This implementation used `RSpec::Support::ObjectFormatter::UninspectableObjectInspector` as a reference, thank you!
17
+ # ref: https://github.com/kachick/times_kachick/issues/97
18
+ singleton_class = class << object; self; end
19
+ begin
20
+ klass = singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) }
21
+ native_object_id = '%#016x' % (object.__id__ << 1)
22
+ "#<#{klass}:#{native_object_id}>"
23
+ rescue Exception
24
+ INSPECTION_FALLBACK
25
+ end
26
+ end
27
+
28
+ # @api private
29
+ # @return [void]
30
+ def set_inspect(name:, product:, arguments:)
31
+ inspect = "#{name}(#{arguments.map { |argument| safe_inspect(argument) }.join(', ')})".freeze
32
+ product.define_singleton_method(:inspect) do
33
+ inspect
34
+ end
35
+ end
36
+
37
+ # @api private
38
+ # @return [void]
39
+ def validate_patterns(*patterns)
40
+ invalids = patterns.reject { |pattern| Eqq.valid?(pattern) }
41
+ invalid_inspections = invalids.map { |invalid| safe_inspect(invalid) }.join(', ')
42
+ raise ArgumentError, "given `#{invalid_inspections}` are invalid as pattern objects" unless invalids.empty?
43
+ end
44
+ end
45
+
8
46
  # @param pattern1 [Proc, Method, #===]
9
47
  # @param pattern2 [Proc, Method, #===]
10
48
  # @param patterns [Array<Proc, Method, #===>]
11
49
  # @return [Proc]
12
- # this lambda return true if match all patterns
13
50
  def AND(pattern1, pattern2, *patterns)
14
- ->v {
15
- [pattern1, pattern2, *patterns].all? { |pattern| pattern === v }
51
+ patterns = [pattern1, pattern2, *patterns].freeze
52
+ Buildable.validate_patterns(*patterns)
53
+
54
+ product = ->v {
55
+ patterns.all? { |pattern| pattern === v }
16
56
  }
57
+
58
+ Buildable.set_inspect(name: 'AND', product: product, arguments: patterns)
59
+
60
+ product
17
61
  end
18
62
 
19
63
  # @param pattern1 [Proc, Method, #===]
@@ -28,11 +72,16 @@ module Eqq
28
72
  # @param pattern2 [Proc, Method, #===]
29
73
  # @param patterns [Array<Proc, Method, #===>]
30
74
  # @return [Proc]
31
- # this lambda return true if match a any pattern
32
75
  def OR(pattern1, pattern2, *patterns)
33
- ->v {
34
- [pattern1, pattern2, *patterns].any? { |pattern| pattern === v }
76
+ patterns = [pattern1, pattern2, *patterns].freeze
77
+ Buildable.validate_patterns(*patterns)
78
+
79
+ product = ->v {
80
+ patterns.any? { |pattern| pattern === v }
35
81
  }
82
+ Buildable.set_inspect(name: 'OR', product: product, arguments: patterns)
83
+
84
+ product
36
85
  end
37
86
 
38
87
  # @param pattern1 [Proc, Method, #===]
@@ -45,54 +94,60 @@ module Eqq
45
94
 
46
95
  # @param pattern1 [Proc, Method, #===]
47
96
  # @param pattern2 [Proc, Method, #===]
48
- # @param patterns [Array<Proc, Method, #===>]
49
97
  # @return [Proc]
50
- def XOR(pattern1, pattern2, *patterns)
51
- ->v {
52
- [pattern1, pattern2, *patterns].one? { |pattern| pattern === v }
98
+ def XOR(pattern1, pattern2)
99
+ patterns = [pattern1, pattern2].freeze
100
+ Buildable.validate_patterns(*patterns)
101
+
102
+ product = ->v {
103
+ patterns.one? { |pattern| pattern === v }
53
104
  }
54
- end
105
+ Buildable.set_inspect(name: 'XOR', product: product, arguments: patterns)
55
106
 
56
- # @param pattern1 [Proc, Method, #===]
57
- # @param pattern2 [Proc, Method, #===]
58
- # @param patterns [Array<Proc, Method, #===>]
59
- # @return [Proc]
60
- def XNOR(pattern1, pattern2, *patterns)
61
- NOT(XOR(pattern1, pattern2, *patterns))
107
+ product
62
108
  end
63
109
 
64
110
  # @param pattern [Proc, Method, #===]
65
111
  # @return [Proc]
66
112
  def NOT(pattern)
67
- raise ArgumentError, 'wrong object for pattern' unless Eqq.valid?(pattern)
113
+ Buildable.validate_patterns(pattern)
114
+
115
+ product = ->v { !(pattern === v) }
68
116
 
69
- ->v { !(pattern === v) }
117
+ Buildable.set_inspect(name: 'NOT', product: product, arguments: [pattern])
118
+
119
+ product
70
120
  end
71
121
 
72
- # A pattern builder.
73
122
  # @param obj [#==]
74
123
  # @return [Proc]
75
124
  def EQ(obj)
76
- ->v { obj == v }
125
+ ->v { obj == v }.tap do |product|
126
+ Buildable.set_inspect(name: 'EQ', product: product, arguments: [obj])
127
+ end
77
128
  end
78
129
 
79
130
  # @param obj [#equal?]
80
131
  # @return [Proc]
81
132
  def SAME(obj)
82
- ->v { obj.equal?(v) }
133
+ ->v { obj.equal?(v) }.tap do |product|
134
+ Buildable.set_inspect(name: 'SAME', product: product, arguments: [obj])
135
+ end
83
136
  end
84
137
 
85
- # @param message1 [Symbol, String]
86
- # @param messages [Array<Symbol, String>]
138
+ # @param message1 [Symbol, String, #to_sym]
139
+ # @param messages [Array<Symbol, String, #to_sym>]
87
140
  # @return [Proc]
88
141
  def CAN(message1, *messages)
89
- messages = begin
90
- [message1, *messages].map(&:to_sym)
91
- rescue NoMethodError
92
- raise ArgumentError
93
- end
142
+ messages = (
143
+ begin
144
+ [message1, *messages].map(&:to_sym).freeze
145
+ rescue NoMethodError
146
+ raise ArgumentError
147
+ end
148
+ )
94
149
 
95
- ->v {
150
+ product = ->v {
96
151
  messages.all? { |message|
97
152
  begin
98
153
  v.respond_to?(message)
@@ -101,18 +156,20 @@ module Eqq
101
156
  end
102
157
  }
103
158
  }
159
+
160
+ Buildable.set_inspect(name: 'CAN', product: product, arguments: messages)
161
+
162
+ product
104
163
  end
105
164
 
106
165
  # @param pattern1 [Proc, Method, #===]
107
166
  # @param patterns [Array<Proc, Method, #===>]
108
167
  # @return [Proc]
109
168
  def QUIET(pattern1, *patterns)
110
- patterns = [pattern1, *patterns]
111
- unless patterns.all? { |pattern| Eqq.valid?(pattern) }
112
- raise ArgumentError, 'wrong object for pattern'
113
- end
169
+ patterns = [pattern1, *patterns].freeze
170
+ Buildable.validate_patterns(*patterns)
114
171
 
115
- ->v {
172
+ product = ->v {
116
173
  patterns.all? { |pattern|
117
174
  begin
118
175
  pattern === v
@@ -123,16 +180,20 @@ module Eqq
123
180
  end
124
181
  }
125
182
  }
183
+
184
+ Buildable.set_inspect(name: 'QUIET', product: product, arguments: patterns)
185
+
186
+ product
126
187
  end
127
188
 
128
189
  # @param mod [Module]
129
190
  # @param pattern [Proc, Method, #===]
130
191
  # @return [Proc]
131
192
  def RESCUE(mod, pattern)
132
- raise ArgumentError unless Eqq.valid?(pattern)
193
+ Buildable.validate_patterns(pattern)
133
194
  raise ArgumentError unless Module === mod
134
195
 
135
- ->v {
196
+ product = ->v {
136
197
  begin
137
198
  pattern === v
138
199
  false
@@ -142,23 +203,41 @@ module Eqq
142
203
  false
143
204
  end
144
205
  }
206
+
207
+ Buildable.set_inspect(name: 'RESCUE', product: product, arguments: [mod, pattern])
208
+
209
+ product
145
210
  end
146
211
 
147
- # @param name [Symbol, #to_sym]
212
+ # @param name [Symbol, String, #to_sym]
148
213
  # @param pattern [Proc, Method, #===]
149
214
  # @return [Proc]
150
215
  def SEND(name, pattern)
151
- raise InvalidProductError unless Eqq.valid?(pattern)
216
+ name = (
217
+ begin
218
+ name.to_sym
219
+ rescue NoMethodError
220
+ raise ArgumentError
221
+ end
222
+ )
223
+ Buildable.validate_patterns(pattern)
152
224
 
153
- ->v {
225
+ product = ->v {
154
226
  v.__send__(name, pattern)
155
227
  }
228
+
229
+ Buildable.set_inspect(name: 'SEND', product: product, arguments: [name, pattern])
230
+
231
+ product
156
232
  end
157
233
 
158
- # @return [BasicObject]
234
+ ANYTHING = ->_v { true }
235
+ Buildable.set_inspect(name: 'ANYTHING', product: ANYTHING, arguments: [])
236
+ private_constant :ANYTHING
237
+
238
+ # @return [ANYTHING]
159
239
  def ANYTHING
160
- # BasicObject.=== always passing
161
- BasicObject
240
+ ANYTHING
162
241
  end
163
242
 
164
243
  BOOLEAN = OR(SAME(true), SAME(false))
data/lib/eqq/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Eqq
5
- VERSION = '0.0.2'
5
+ VERSION = '0.0.3'
6
6
  end
data/sig/eqq.rbs CHANGED
@@ -7,26 +7,44 @@ module Eqq
7
7
  def to_sym: -> Symbol
8
8
  end
9
9
 
10
+ interface _Inspectable
11
+ def inspect: () -> String
12
+ end
13
+
10
14
  module Buildable
11
- # A pribate constant. Should not be used in your code.
12
- BOOLEAN: ^(untyped object) -> bool
15
+ type patternable_lambda = ^(untyped object) -> bool
16
+ type product = patternable_lambda & _Inspectable
17
+
18
+ # A private constant. Should not be used in your code.
19
+ ANYTHING: product
20
+
21
+ # A private constant. Should not be used in your code.
22
+ BOOLEAN: product
23
+
24
+ # A private API. Should not be used in your code.
25
+ def self.safe_inspect: (untyped object)-> String
26
+
27
+ # A private API. Should not be used in your code.
28
+ def self.set_inspect: (name: String, product: patternable_lambda, arguments: Array[untyped])-> void
29
+
30
+ # A private API. Should not be used in your code.
31
+ def self.validate_patterns: (*untyped) -> void
13
32
 
14
33
  extend Buildable
15
- def OR: (_Patternable, _Patternable, *_Patternable) -> ^(untyped object) -> bool
16
- def AND: (_Patternable, _Patternable, *_Patternable) -> ^(untyped object) -> bool
17
- def NAND: (_Patternable, _Patternable, *_Patternable) -> ^(untyped object) -> bool
18
- def NOR: (_Patternable, _Patternable, *_Patternable) -> ^(untyped object) -> bool
19
- def XOR: (_Patternable, _Patternable, *_Patternable) -> ^(untyped object) -> bool
20
- def XNOR: (_Patternable, _Patternable, *_Patternable) -> ^(untyped object) -> bool
21
- def NOT: (_Patternable) -> ^(untyped object) -> bool
22
- def EQ: (untyped object) -> ^(untyped object) -> bool
23
- def SAME: (untyped object) -> ^(untyped object) -> bool
24
- def CAN: (_ToSym, *_ToSym) -> ^(untyped object) -> bool
25
- def RESCUE: (Module, _Patternable) -> ^(untyped object) -> bool
26
- def QUIET: (_Patternable, *_Patternable) -> ^(untyped object) -> bool
27
- def SEND: (Symbol | String name, _Patternable) -> ^(untyped object) -> bool
28
- def ANYTHING: () -> BasicObject
29
- def BOOLEAN: () -> ^(untyped object) -> bool
34
+ def OR: (_Patternable, _Patternable, *_Patternable) -> product
35
+ def AND: (_Patternable, _Patternable, *_Patternable) -> product
36
+ def NAND: (_Patternable, _Patternable, *_Patternable) -> product
37
+ def NOR: (_Patternable, _Patternable, *_Patternable) -> product
38
+ def XOR: (_Patternable, _Patternable) -> product
39
+ def NOT: (_Patternable) -> product
40
+ def EQ: (untyped object) -> product
41
+ def SAME: (untyped object) -> product
42
+ def CAN: (_ToSym, *_ToSym) -> product
43
+ def RESCUE: (Module, _Patternable) -> product
44
+ def QUIET: (_Patternable, *_Patternable) -> product
45
+ def SEND: (Symbol | String name, _Patternable) -> product
46
+ def ANYTHING: () -> product
47
+ def BOOLEAN: () -> product
30
48
  end
31
49
 
32
50
  extend Buildable
@@ -37,13 +55,16 @@ module Eqq
37
55
  class InvalidProductError < Error
38
56
  end
39
57
 
40
- # A pribate API. Should not be used in your code.
58
+ # A private API. Should not be used in your code.
41
59
  class DSLScope
42
60
  include Buildable
43
61
  end
44
62
 
45
63
  VERSION: String
46
64
 
65
+ # A private constant. Should not be used in your code.
66
+ INSPECTION_FALLBACK: String
67
+
47
68
  def self.valid?: (untyped object) -> bool
48
69
  def self.define: { () -> _Patternable } -> _Patternable
49
70
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eqq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Kamiya
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-01 00:00:00.000000000 Z
11
+ date: 2021-06-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: " [4.2, 42, 42.0, 420].grep(Eqq.AND(Integer, 20..50)) #=> [42]\n"
14
14
  email: