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 +4 -4
- data/README.md +87 -5
- data/lib/eqq.rb +11 -7
- data/lib/eqq/buildable.rb +122 -43
- data/lib/eqq/version.rb +1 -1
- data/sig/eqq.rbs +39 -18
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22307661e329771fea7efcd7c282d17eda0130d0edb7aec3088257b21b73e4ba
|
4
|
+
data.tar.gz: ebc3138ade59e38f9bc34a42a93654d3a6ab1564a48b47df13d65ed7227c7488
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16ae2b50b3c81291028946ec6ddb8753c06bc7fc472f29ab00b3f69b9136540f26079b8c23f983d0bf1482f93543744f2fae557bb0ef71f6fdcc73f115df9455
|
7
|
+
data.tar.gz: eced8529614ce3b1b8a4cc4087d58943bdbaabd0637a86bb10bcafe472ccdece82bae71d390fde84e95176318961e4ef74207ab4debc6e117411608f2e074176
|
data/README.md
CHANGED
@@ -3,9 +3,7 @@
|
|
3
3
|

|
4
4
|
[](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.
|
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
|
-
|
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
|
-
|
15
|
-
|
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
|
-
|
34
|
-
|
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
|
51
|
-
|
52
|
-
|
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
|
-
|
105
|
+
Buildable.set_inspect(name: 'XOR', product: product, arguments: patterns)
|
55
106
|
|
56
|
-
|
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
|
-
|
113
|
+
Buildable.validate_patterns(pattern)
|
114
|
+
|
115
|
+
product = ->v { !(pattern === v) }
|
68
116
|
|
69
|
-
|
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 =
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
161
|
-
BasicObject
|
240
|
+
ANYTHING
|
162
241
|
end
|
163
242
|
|
164
243
|
BOOLEAN = OR(SAME(true), SAME(false))
|
data/lib/eqq/version.rb
CHANGED
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
|
-
|
12
|
-
|
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) ->
|
16
|
-
def AND: (_Patternable, _Patternable, *_Patternable) ->
|
17
|
-
def NAND: (_Patternable, _Patternable, *_Patternable) ->
|
18
|
-
def NOR: (_Patternable, _Patternable, *_Patternable) ->
|
19
|
-
def XOR: (_Patternable, _Patternable
|
20
|
-
def
|
21
|
-
def
|
22
|
-
def
|
23
|
-
def
|
24
|
-
def
|
25
|
-
def
|
26
|
-
def
|
27
|
-
def
|
28
|
-
def
|
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
|
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.
|
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-
|
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:
|