nrser 0.0.15 → 0.0.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +11 -0
- data/lib/nrser.rb +2 -0
- data/lib/nrser/refinements.rb +36 -0
- data/lib/nrser/types.rb +75 -0
- data/lib/nrser/types/any.rb +13 -0
- data/lib/nrser/types/array.rb +61 -0
- data/lib/nrser/types/attrs.rb +69 -0
- data/lib/nrser/types/booleans.rb +47 -0
- data/lib/nrser/types/bounded.rb +36 -0
- data/lib/nrser/types/combinators.rb +86 -0
- data/lib/nrser/types/hash.rb +15 -0
- data/lib/nrser/types/is.rb +33 -0
- data/lib/nrser/types/is_a.rb +27 -0
- data/lib/nrser/types/maybe.rb +11 -0
- data/lib/nrser/types/numbers.rb +100 -0
- data/lib/nrser/types/strings.rb +34 -0
- data/lib/nrser/types/type.rb +61 -0
- data/lib/nrser/types/where.rb +22 -0
- data/lib/nrser/version.rb +1 -1
- data/spec/nrser/logger/die_spec.rb +35 -35
- data/spec/nrser/refinements/pathname_spec.rb +40 -0
- data/spec/nrser/types/combinators_spec.rb +11 -0
- data/spec/nrser/types/is_spec.rb +12 -0
- data/spec/nrser/types_spec.rb +197 -0
- metadata +26 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4bdcf46a3991ba3ea386aa2599d03dd3f9bbbfff
|
4
|
+
data.tar.gz: d59e2df7bcead37764dbe4d167eeed44f2e0e22f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c4985fc5450eaa3c2989b813b83d477e28b3a07dedecb2c0158ca182f484a9106edfe26d56957188829d642dba614ad75510851125be124732b280ebb0ba66e
|
7
|
+
data.tar.gz: 4898d4bd6023429747b7e657fbecb9dc614daa1ba7a7dfe39c419b36f9714c75ce44d18a4f6144e51f02592b6e11d6bb20619d916b3b684a22b7c1a86addecf1
|
data/.gitignore
CHANGED
@@ -21,3 +21,14 @@ tmp
|
|
21
21
|
*.a
|
22
22
|
mkmf.log
|
23
23
|
tmp/
|
24
|
+
##############################################################################
|
25
|
+
# BEGIN Qb.gitingore
|
26
|
+
#
|
27
|
+
# generated qb playbooks
|
28
|
+
.qb-playbook.yml
|
29
|
+
|
30
|
+
# ansible retry files
|
31
|
+
.qb-playbook.retry
|
32
|
+
#
|
33
|
+
# END Qb.gitingore
|
34
|
+
##############################################################################
|
data/lib/nrser.rb
CHANGED
data/lib/nrser/refinements.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
1
3
|
module NRSER
|
2
4
|
refine Object do
|
3
5
|
def pipe
|
@@ -38,4 +40,38 @@ module NRSER
|
|
38
40
|
NRSER.template self, str
|
39
41
|
end
|
40
42
|
end
|
43
|
+
|
44
|
+
refine Pathname do
|
45
|
+
# override to accept Pathname instances.
|
46
|
+
#
|
47
|
+
# @param [String] *prefixes
|
48
|
+
# the prefixes to see if the Pathname starts with.
|
49
|
+
#
|
50
|
+
# @return [Boolean]
|
51
|
+
# true if the Pathname starts with any of the prefixes.
|
52
|
+
#
|
53
|
+
def start_with? *prefixes
|
54
|
+
to_s.start_with? *prefixes.map(&:to_s)
|
55
|
+
end
|
56
|
+
|
57
|
+
# override sub to support Pathname instances as patterns.
|
58
|
+
#
|
59
|
+
# @param [String | Regexp | Pathname] pattern
|
60
|
+
# thing to replace.
|
61
|
+
#
|
62
|
+
# @param [String | Hash] replacement
|
63
|
+
# thing to replace it with.
|
64
|
+
#
|
65
|
+
# @return [Pathname]
|
66
|
+
# new Pathname.
|
67
|
+
#
|
68
|
+
def sub pattern, replacement
|
69
|
+
case pattern
|
70
|
+
when Pathname
|
71
|
+
super pattern.to_s, replacement
|
72
|
+
else
|
73
|
+
super pattern, replacement
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end # Pathname
|
41
77
|
end # NRSER
|
data/lib/nrser/types.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
require 'nrser/refinements'
|
4
|
+
require 'nrser/types/type'
|
5
|
+
require 'nrser/types/is'
|
6
|
+
require 'nrser/types/is_a'
|
7
|
+
require 'nrser/types/where'
|
8
|
+
require 'nrser/types/combinators'
|
9
|
+
require 'nrser/types/maybe'
|
10
|
+
require 'nrser/types/attrs'
|
11
|
+
|
12
|
+
using NRSER
|
13
|
+
|
14
|
+
module NRSER::Types
|
15
|
+
# make a type.
|
16
|
+
def self.make value
|
17
|
+
if value.is_a? NRSER::Types::Type
|
18
|
+
value
|
19
|
+
elsif value.is_a? ::Class
|
20
|
+
IsA.new value
|
21
|
+
else
|
22
|
+
Is.new value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# raise an error if value doesn't match type.
|
27
|
+
def self.check value, type
|
28
|
+
make(type).check value
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.test value, type
|
32
|
+
make(type).test value
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.match value, type_map
|
36
|
+
type_map.each {|type, block|
|
37
|
+
if test value, type
|
38
|
+
return block.call value
|
39
|
+
end
|
40
|
+
}
|
41
|
+
|
42
|
+
raise TypeError, <<-END.dedent
|
43
|
+
could not match value
|
44
|
+
|
45
|
+
#{ value.inspect }
|
46
|
+
|
47
|
+
to any of types
|
48
|
+
|
49
|
+
#{ type_map.keys.map {|type| "\n #{ type.inspect }"} }
|
50
|
+
|
51
|
+
END
|
52
|
+
end
|
53
|
+
|
54
|
+
# make a type instance from a object representation that can come from
|
55
|
+
# a YAML or JSON declaration.
|
56
|
+
def self.from_repr repr
|
57
|
+
match repr, {
|
58
|
+
str => ->(string) {
|
59
|
+
NRSER::Types.method(string.downcase).call
|
60
|
+
},
|
61
|
+
|
62
|
+
Hash => ->(hash) {
|
63
|
+
|
64
|
+
},
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end # NRSER::Types
|
68
|
+
|
69
|
+
# things that define values, which may need to call the functions defined
|
70
|
+
# above
|
71
|
+
require 'nrser/types/any'
|
72
|
+
require 'nrser/types/booleans'
|
73
|
+
require 'nrser/types/numbers'
|
74
|
+
require 'nrser/types/strings'
|
75
|
+
require 'nrser/types/array'
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
require 'nrser/types/type'
|
3
|
+
require 'nrser/types/is_a'
|
4
|
+
|
5
|
+
using NRSER
|
6
|
+
|
7
|
+
module NRSER::Types
|
8
|
+
class Array < IsA
|
9
|
+
SEP = /\,\s+/
|
10
|
+
|
11
|
+
attr_reader :item_type
|
12
|
+
|
13
|
+
def initialize item_type = NRSER::Types.any, **options
|
14
|
+
super ::Array, **options
|
15
|
+
@item_type = NRSER::Types.make item_type
|
16
|
+
end
|
17
|
+
|
18
|
+
def test value
|
19
|
+
super(value) && if @item_type == NRSER::Types.any
|
20
|
+
true
|
21
|
+
else
|
22
|
+
value.all? {|v| @item_type.test v}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_name
|
27
|
+
"#{ self.class.short_name }<#{ @item_type }>"
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_from_s?
|
31
|
+
@item_class.has_from_s?
|
32
|
+
end
|
33
|
+
|
34
|
+
def from_s s
|
35
|
+
# does it looks like json?
|
36
|
+
if s.start_with? '['
|
37
|
+
begin
|
38
|
+
return JSON.load s
|
39
|
+
rescue
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
s.split SEP
|
44
|
+
end
|
45
|
+
|
46
|
+
def == other
|
47
|
+
equal?(other) || (
|
48
|
+
other.class == self.class && @item_type == other.item_type
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end # Array
|
52
|
+
|
53
|
+
# array
|
54
|
+
def self.array *args
|
55
|
+
Array.new *args
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.list
|
59
|
+
array
|
60
|
+
end
|
61
|
+
end # NRSER::Types
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
require 'nrser/types/type'
|
3
|
+
require 'nrser/types/combinators'
|
4
|
+
|
5
|
+
using NRSER
|
6
|
+
|
7
|
+
module NRSER::Types
|
8
|
+
class Attrs < NRSER::Types::Type
|
9
|
+
def initialize attrs, **options
|
10
|
+
super **options
|
11
|
+
@attrs = attrs
|
12
|
+
end
|
13
|
+
|
14
|
+
def default_name
|
15
|
+
attrs_str = @attrs.map {|name, type|
|
16
|
+
"#{ name }=#{ type.name }"
|
17
|
+
}.join(', ')
|
18
|
+
|
19
|
+
"#{ self.class.short_name }(#{ attrs_str })"
|
20
|
+
end
|
21
|
+
|
22
|
+
def test value
|
23
|
+
@attrs.all? {|name, type|
|
24
|
+
value.respond_to?(name) && type.test(value.method(name).call)
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end # Attrs
|
28
|
+
|
29
|
+
def self.attrs attrs, options = {}
|
30
|
+
Attrs.new attrs, **options
|
31
|
+
end
|
32
|
+
|
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)
|
47
|
+
end
|
48
|
+
}
|
49
|
+
|
50
|
+
else
|
51
|
+
raise ArgumentError, <<-END.squish
|
52
|
+
arg must be positive integer or option hash, found:
|
53
|
+
#{ args[0].inspect } of type #{ args[0].class }
|
54
|
+
END
|
55
|
+
end
|
56
|
+
|
57
|
+
when 2
|
58
|
+
bounds[:min] = bounds[:max] = non_neg_int.check(args[0])
|
59
|
+
options = args[1]
|
60
|
+
|
61
|
+
else
|
62
|
+
raise ArgumentError, <<-END.squish
|
63
|
+
must provided 1 or 2 args.
|
64
|
+
END
|
65
|
+
end
|
66
|
+
|
67
|
+
attrs({length: intersection(non_neg_int, bounded(bounds))}, options)
|
68
|
+
end
|
69
|
+
end # NRSER::Types
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
require 'nrser/types/type'
|
3
|
+
require 'nrser/types/is'
|
4
|
+
require 'nrser/types/combinators'
|
5
|
+
|
6
|
+
using NRSER
|
7
|
+
|
8
|
+
module NRSER::Types
|
9
|
+
# booleans
|
10
|
+
# ========
|
11
|
+
|
12
|
+
TRUE = is true, name: 'true', from_s: ->(string) {
|
13
|
+
if ['true', 't', '1', 'yes', 'y', 'on'].include? string.downcase
|
14
|
+
true
|
15
|
+
else
|
16
|
+
raise TypeError, "can not convert to true: #{ string.inspect }"
|
17
|
+
end
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.true
|
21
|
+
TRUE
|
22
|
+
end
|
23
|
+
|
24
|
+
FALSE = is false, name: 'false', from_s: ->(string) {
|
25
|
+
if ['false', 'f', '0', 'no', 'n', 'off'].include? string.downcase
|
26
|
+
false
|
27
|
+
else
|
28
|
+
raise TypeError, "can not convert to true: #{ string.inspect }"
|
29
|
+
end
|
30
|
+
}
|
31
|
+
|
32
|
+
def self.false
|
33
|
+
FALSE
|
34
|
+
end
|
35
|
+
|
36
|
+
BOOL = union TRUE, FALSE
|
37
|
+
|
38
|
+
# true or false
|
39
|
+
def self.bool
|
40
|
+
BOOL
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.boolean
|
44
|
+
bool
|
45
|
+
end
|
46
|
+
|
47
|
+
end # NRSER::Types
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
require 'nrser/types/type'
|
3
|
+
|
4
|
+
using NRSER
|
5
|
+
|
6
|
+
module NRSER::Types
|
7
|
+
class Bounded < NRSER::Types::Type
|
8
|
+
def initialize **options
|
9
|
+
@min = options[:min]
|
10
|
+
@max = options[:max]
|
11
|
+
end
|
12
|
+
|
13
|
+
def test value
|
14
|
+
return false if @min && value < @min
|
15
|
+
return false if @max && value > @max
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_name
|
20
|
+
attrs_str = ['min', 'max'].map {|name|
|
21
|
+
[name, instance_variable_get("@#{ name }")]
|
22
|
+
}.reject {|name, value|
|
23
|
+
value.nil?
|
24
|
+
}.map {|name, value|
|
25
|
+
"#{ name }=#{ value }"
|
26
|
+
}.join(', ')
|
27
|
+
|
28
|
+
"#{ self.class.short_name }(#{ attrs_str })"
|
29
|
+
end
|
30
|
+
end # Bounded
|
31
|
+
|
32
|
+
def self.bounded **options
|
33
|
+
Bounded.new **options
|
34
|
+
end
|
35
|
+
|
36
|
+
end # NRSER::Types
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
require 'nrser/types/type'
|
3
|
+
|
4
|
+
using NRSER
|
5
|
+
|
6
|
+
# base class for Union and Intersection which combine over a set of types.
|
7
|
+
module NRSER::Types
|
8
|
+
class Combinator < NRSER::Types::Type
|
9
|
+
attr_reader :types
|
10
|
+
|
11
|
+
def initialize *types, **options
|
12
|
+
super **options
|
13
|
+
@types = types.map {|type| NRSER::Types.make type}
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
@name || (
|
18
|
+
"#{ self.class.short_name }(" +
|
19
|
+
@types.map {|type| type.name }.join(',') +
|
20
|
+
")"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
# a combinator may attempt to parse from a string if any of it's types
|
25
|
+
# can do so
|
26
|
+
def has_from_s?
|
27
|
+
@types.any? {|type| type.has_from_s?}
|
28
|
+
end
|
29
|
+
|
30
|
+
# a combinator iterates through each of it's types, trying the
|
31
|
+
# conversion and seeing if the result satisfies the combinator type
|
32
|
+
# itself. the first such value found is returned.
|
33
|
+
def from_s s
|
34
|
+
@types.each {|type|
|
35
|
+
if type.respond_to? :from_s
|
36
|
+
begin
|
37
|
+
return check type.from_s(s)
|
38
|
+
rescue TypeError => e
|
39
|
+
# pass
|
40
|
+
end
|
41
|
+
end
|
42
|
+
}
|
43
|
+
|
44
|
+
raise TypeError,
|
45
|
+
"none of union types could convert #{ string.inspect }"
|
46
|
+
end
|
47
|
+
|
48
|
+
def == other
|
49
|
+
equal?(other) || (
|
50
|
+
other.class == self.class && other.types == @types
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Union < Combinator
|
56
|
+
def test value
|
57
|
+
@types.any? {|type| type.test value}
|
58
|
+
end
|
59
|
+
end # Union
|
60
|
+
|
61
|
+
# match any of the types
|
62
|
+
def self.union *types, **options
|
63
|
+
NRSER::Types::Union.new *types, **options
|
64
|
+
end
|
65
|
+
|
66
|
+
# match any of the types
|
67
|
+
def self.one_of *types, **options
|
68
|
+
union *types, **options
|
69
|
+
end
|
70
|
+
|
71
|
+
class Intersection < Combinator
|
72
|
+
def test value
|
73
|
+
@types.all? {|type| type.test value}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# match all of the types
|
78
|
+
def self.intersection *types, **options
|
79
|
+
Intersection.new *types, **options
|
80
|
+
end
|
81
|
+
|
82
|
+
# match all of the types
|
83
|
+
def self.all_of *types, **options
|
84
|
+
intersection *types, **options
|
85
|
+
end
|
86
|
+
end # NRSER::Types
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
require 'nrser/types/type'
|
3
|
+
|
4
|
+
using NRSER
|
5
|
+
|
6
|
+
module NRSER::Types
|
7
|
+
|
8
|
+
class Hash < NRSER::Types::Type
|
9
|
+
attr_reader :keys, :values, :including, :exactly, :min, :max
|
10
|
+
|
11
|
+
def initialize options = {}
|
12
|
+
|
13
|
+
end
|
14
|
+
end # Hash
|
15
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
require 'nrser/types/type'
|
3
|
+
using NRSER
|
4
|
+
|
5
|
+
module NRSER::Types
|
6
|
+
class Is < NRSER::Types::Type
|
7
|
+
attr_reader :value
|
8
|
+
|
9
|
+
def initialize value, **options
|
10
|
+
super **options
|
11
|
+
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
"Is(#{ @value.inspect })"
|
17
|
+
end
|
18
|
+
|
19
|
+
def test value
|
20
|
+
@value.equal? value
|
21
|
+
end
|
22
|
+
|
23
|
+
def == other
|
24
|
+
equal?(other) || @value === other.value
|
25
|
+
end
|
26
|
+
end # Is
|
27
|
+
|
28
|
+
# an exact value (using ===)
|
29
|
+
def self.is value, **options
|
30
|
+
Is.new value, **options
|
31
|
+
end
|
32
|
+
|
33
|
+
end # NRSER::Types
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
require 'nrser/types/type'
|
3
|
+
using NRSER
|
4
|
+
|
5
|
+
module NRSER::Types
|
6
|
+
class IsA < NRSER::Types::Type
|
7
|
+
attr_reader :klass
|
8
|
+
|
9
|
+
def initialize klass, **options
|
10
|
+
super **options
|
11
|
+
@klass = klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
@name || "#{ self.class.short_name }(#{ @klass })"
|
16
|
+
end
|
17
|
+
|
18
|
+
def test value
|
19
|
+
value.is_a? @klass
|
20
|
+
end
|
21
|
+
end # IsA
|
22
|
+
|
23
|
+
# class membership
|
24
|
+
def self.is_a klass
|
25
|
+
IsA.new klass
|
26
|
+
end
|
27
|
+
end # NRSER::Types
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
require 'nrser/types/type'
|
3
|
+
require 'nrser/types/is_a'
|
4
|
+
require 'nrser/types/combinators'
|
5
|
+
require 'nrser/types/bounded'
|
6
|
+
|
7
|
+
using NRSER
|
8
|
+
|
9
|
+
module NRSER::Types
|
10
|
+
def self.parse_number s
|
11
|
+
float = s.to_f
|
12
|
+
int = float.to_i
|
13
|
+
if float == int then int else float end
|
14
|
+
end
|
15
|
+
|
16
|
+
# zero
|
17
|
+
# ====
|
18
|
+
|
19
|
+
ZERO = is 0, name: 'zero', from_s: method(:parse_number)
|
20
|
+
|
21
|
+
def self.zero
|
22
|
+
ZERO
|
23
|
+
end
|
24
|
+
|
25
|
+
# number (Numeric)
|
26
|
+
# ================
|
27
|
+
|
28
|
+
NUM = IsA.new Numeric, name: 'Num', from_s: method(:parse_number)
|
29
|
+
|
30
|
+
def self.num
|
31
|
+
NUM
|
32
|
+
end
|
33
|
+
|
34
|
+
# integers
|
35
|
+
# ========
|
36
|
+
|
37
|
+
INT = IsA.new Integer, name: 'Int', from_s: method(:parse_number)
|
38
|
+
|
39
|
+
def self.int
|
40
|
+
INT
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.integer
|
44
|
+
int
|
45
|
+
end
|
46
|
+
|
47
|
+
# bounded integers
|
48
|
+
# ================
|
49
|
+
#
|
50
|
+
|
51
|
+
# positive integer
|
52
|
+
# ----------------
|
53
|
+
#
|
54
|
+
# integer greater than zero.
|
55
|
+
#
|
56
|
+
|
57
|
+
POS_INT = intersection INT, bounded(min: 1), name: 'PosInt'
|
58
|
+
|
59
|
+
def self.pos_int
|
60
|
+
POS_INT
|
61
|
+
end
|
62
|
+
|
63
|
+
# negative integer
|
64
|
+
# ----------------
|
65
|
+
#
|
66
|
+
# integer less than zero
|
67
|
+
#
|
68
|
+
|
69
|
+
NEG_INT = intersection INT, bounded(max: -1), name: 'NegInt'
|
70
|
+
|
71
|
+
def self.neg_int
|
72
|
+
NEG_INT
|
73
|
+
end
|
74
|
+
|
75
|
+
# non-negative integer
|
76
|
+
# --------------------
|
77
|
+
#
|
78
|
+
# positive integers and zero... but it seems more efficient to define these
|
79
|
+
# as bounded instead of a union.
|
80
|
+
#
|
81
|
+
|
82
|
+
NON_NEG_INT = intersection INT, bounded(min: 0), name: 'NonNegInt'
|
83
|
+
|
84
|
+
def self.non_neg_int
|
85
|
+
NON_NEG_INT
|
86
|
+
end
|
87
|
+
|
88
|
+
# non-positive integer
|
89
|
+
# --------------------
|
90
|
+
#
|
91
|
+
# negative integers and zero.
|
92
|
+
#
|
93
|
+
|
94
|
+
NON_POS_INT = intersection INT, bounded(max: 0), name: 'NonPosInt'
|
95
|
+
|
96
|
+
def self.non_pos_int
|
97
|
+
NON_POS_INT
|
98
|
+
end
|
99
|
+
|
100
|
+
end # NRSER::Types
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
require 'nrser/types/is'
|
3
|
+
require 'nrser/types/is_a'
|
4
|
+
require 'nrser/types/attrs'
|
5
|
+
|
6
|
+
using NRSER
|
7
|
+
|
8
|
+
module NRSER::Types
|
9
|
+
STR = IsA.new String, name: 'Str', from_s: ->(s) { s }
|
10
|
+
|
11
|
+
def self.str **options
|
12
|
+
if options.empty?
|
13
|
+
# if there are no options can point to the constant for efficiency
|
14
|
+
STR
|
15
|
+
else
|
16
|
+
types = []
|
17
|
+
|
18
|
+
if options[:length]
|
19
|
+
types << length(options[:length])
|
20
|
+
end
|
21
|
+
|
22
|
+
intersection STR, *types
|
23
|
+
end
|
24
|
+
end # string
|
25
|
+
|
26
|
+
def self.string
|
27
|
+
str
|
28
|
+
end
|
29
|
+
|
30
|
+
EMPTY_STR = Is.new ''
|
31
|
+
|
32
|
+
NON_EMPTY_STR = str length: {min: 1}, name: "NonEmptyStr"
|
33
|
+
|
34
|
+
end # NRSER::Types
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
using NRSER
|
3
|
+
|
4
|
+
module NRSER::Types
|
5
|
+
class Type
|
6
|
+
def self.short_name
|
7
|
+
name.split('::').last
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize **options
|
11
|
+
@name = options[:name]
|
12
|
+
@from_s = options[:from_s]
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
@name || default_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_name
|
20
|
+
self.class.short_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def test value
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def check value
|
28
|
+
unless test value
|
29
|
+
raise TypeError.new NRSER.squish <<-END
|
30
|
+
value #{ value.inspect } failed check #{ self.inspect }
|
31
|
+
END
|
32
|
+
end
|
33
|
+
|
34
|
+
value
|
35
|
+
end
|
36
|
+
|
37
|
+
def respond_to? name, include_all = false
|
38
|
+
if name == :from_s || name == 'from_s'
|
39
|
+
has_from_s?
|
40
|
+
else
|
41
|
+
super name, include_all
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def from_s s
|
46
|
+
if @from_s.nil?
|
47
|
+
raise NoMethodError, "#from_s not defined"
|
48
|
+
end
|
49
|
+
|
50
|
+
check @from_s.(s)
|
51
|
+
end
|
52
|
+
|
53
|
+
def has_from_s?
|
54
|
+
! @from_s.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
"<Type:#{ name }>"
|
59
|
+
end
|
60
|
+
end # Type
|
61
|
+
end # NRSER::Types
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'nrser/refinements'
|
2
|
+
using NRSER
|
3
|
+
|
4
|
+
module NRSER::Types
|
5
|
+
class Where < NRSER::Types::Type
|
6
|
+
attr_reader :predicate
|
7
|
+
|
8
|
+
def initialize predicate, **options
|
9
|
+
super **options
|
10
|
+
@predicate = predicate
|
11
|
+
end
|
12
|
+
|
13
|
+
def test value
|
14
|
+
!!@predicate.call(value)
|
15
|
+
end
|
16
|
+
end # Where
|
17
|
+
|
18
|
+
# create a type based on a predicate
|
19
|
+
def self.where **options, &block
|
20
|
+
Where.new block, **options
|
21
|
+
end
|
22
|
+
end # NRSER::Types
|
data/lib/nrser/version.rb
CHANGED
@@ -3,39 +3,39 @@ require 'spec_helper'
|
|
3
3
|
using NRSER
|
4
4
|
|
5
5
|
describe 'NRSER::Logger#die' do
|
6
|
-
it "prints the log message when logger is off" do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
it(
|
23
|
-
|
24
|
-
|
25
|
-
) do
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
6
|
+
# it "prints the log message when logger is off" do
|
7
|
+
# src = <<-END.dedent
|
8
|
+
# require 'nrser/logger'
|
9
|
+
#
|
10
|
+
# NRSER::Logger.install self
|
11
|
+
#
|
12
|
+
# die "die!", cause: "just 'cause"
|
13
|
+
# END
|
14
|
+
#
|
15
|
+
# err = Cmds.err("bundle exec ruby"){ src }
|
16
|
+
# data = YAML.load(err)['FATAL']
|
17
|
+
#
|
18
|
+
# expect(data['msg']).to eq 'die!'
|
19
|
+
# expect(data['values']).to eq({'cause' => "just 'cause"})
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# it(
|
23
|
+
# "prints only to the log when it's on and the log writes to " +
|
24
|
+
# "$stderr"
|
25
|
+
# ) do
|
26
|
+
# src = <<-END
|
27
|
+
# require 'nrser/logger'
|
28
|
+
#
|
29
|
+
# NRSER::Logger.install self, on: true, say_hi: false
|
30
|
+
#
|
31
|
+
# die "die!", cause: "just 'cause"
|
32
|
+
# END
|
33
|
+
#
|
34
|
+
# err = Cmds.err("bundle exec ruby"){ src }
|
35
|
+
#
|
36
|
+
# data = YAML.load(err)['FATAL']
|
37
|
+
#
|
38
|
+
# expect(data['msg']).to eq 'die!'
|
39
|
+
# expect(data['values']).to eq({'cause' => "just 'cause"})
|
40
|
+
# end
|
41
41
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nrser/refinements'
|
3
|
+
|
4
|
+
using NRSER
|
5
|
+
|
6
|
+
describe 'Pathname' do
|
7
|
+
|
8
|
+
describe '#start_with?' do
|
9
|
+
it "works with other Pathname instances" do
|
10
|
+
expect(
|
11
|
+
Pathname.new('a/b/c').start_with? Pathname.new('a/b')
|
12
|
+
).to be true
|
13
|
+
|
14
|
+
expect(
|
15
|
+
Pathname.new('a/b/c').start_with? Pathname.new('a/b/')
|
16
|
+
).to be true
|
17
|
+
|
18
|
+
expect(
|
19
|
+
Pathname.new('/a/b/c').start_with? Pathname.new('/')
|
20
|
+
).to be true
|
21
|
+
end
|
22
|
+
end # #start_with?
|
23
|
+
|
24
|
+
describe '#sub' do
|
25
|
+
context "other pathnames" do
|
26
|
+
it "works for basic replacement" do
|
27
|
+
expect(
|
28
|
+
Pathname.new('a/b/c').sub Pathname.new('a/b'), 'c/d'
|
29
|
+
).to eq Pathname.new('c/d/c')
|
30
|
+
end
|
31
|
+
|
32
|
+
it "only substitutes the first occurrence" do
|
33
|
+
expect(
|
34
|
+
Pathname.new('c/c/c').sub Pathname.new('c'), 'a'
|
35
|
+
).to eq Pathname.new('a/c/c')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end # Pathname
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NRSER::Types::Is do
|
4
|
+
t = NRSER::Types
|
5
|
+
|
6
|
+
describe '==' do
|
7
|
+
it "equates two different instances with equal types" do
|
8
|
+
expect(t.is 1).to eq t.is(1)
|
9
|
+
expect([t.is(1)]).to eq [t.is(1)]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end # NRSER::Types::Union
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NRSER::Types do
|
4
|
+
t = NRSER::Types
|
5
|
+
|
6
|
+
describe 'basic types #test and #check' do
|
7
|
+
{
|
8
|
+
t.any => {
|
9
|
+
pass: [nil, 'blah', 1, 3.14, [], {}, Object.new],
|
10
|
+
from_s: {
|
11
|
+
pass: ['hey', 'ho', "let's go"],
|
12
|
+
},
|
13
|
+
},
|
14
|
+
|
15
|
+
t.is(1) => {
|
16
|
+
pass: [1],
|
17
|
+
fail: [2, '1', nil, true],
|
18
|
+
},
|
19
|
+
|
20
|
+
t.is_a(Array) => {
|
21
|
+
pass: [[]],
|
22
|
+
fail: [{}],
|
23
|
+
},
|
24
|
+
|
25
|
+
t.true => {
|
26
|
+
pass: [true],
|
27
|
+
fail: [1, 'true'],
|
28
|
+
},
|
29
|
+
|
30
|
+
t.false => {
|
31
|
+
pass: [false],
|
32
|
+
fail: [true, 0, nil, 'false'],
|
33
|
+
},
|
34
|
+
|
35
|
+
t.bool => {
|
36
|
+
pass: [true, false],
|
37
|
+
fail: [0, 1, nil],
|
38
|
+
},
|
39
|
+
|
40
|
+
t.int => {
|
41
|
+
pass: [0, 1, -1, 888],
|
42
|
+
fail: ['0', 3.14],
|
43
|
+
from_s: {
|
44
|
+
pass: ['1', '10', '0', '-1', '1000000000000000000000'],
|
45
|
+
fail: ['3.14', '1more', 'hey'],
|
46
|
+
}
|
47
|
+
},
|
48
|
+
|
49
|
+
t.pos_int => {
|
50
|
+
pass: [1, 10, 1000000000000000000000],
|
51
|
+
fail: [0, -1],
|
52
|
+
from_s: {
|
53
|
+
pass: ['1', '10', '1000000000000000000000'],
|
54
|
+
fail: ['0', '-1', '3.14'],
|
55
|
+
}
|
56
|
+
},
|
57
|
+
|
58
|
+
t.neg_int => {
|
59
|
+
pass: [-1, -10, -1000000000000000000000],
|
60
|
+
fail: [0, 1],
|
61
|
+
from_s: {
|
62
|
+
pass: ['-1', '-10', '-1000000000000000000000'],
|
63
|
+
fail: ['0', '1', '3.14'],
|
64
|
+
}
|
65
|
+
},
|
66
|
+
|
67
|
+
t.non_neg_int => {
|
68
|
+
pass: [0, 1],
|
69
|
+
fail: [nil, -1, false],
|
70
|
+
from_s: {
|
71
|
+
pass: ['0', '1'],
|
72
|
+
fail: ['-1'],
|
73
|
+
},
|
74
|
+
},
|
75
|
+
|
76
|
+
t.non_pos_int => {
|
77
|
+
pass: [0, -1],
|
78
|
+
fail: [1],
|
79
|
+
from_s: {
|
80
|
+
pass: ['0', '-1'],
|
81
|
+
fail: ['1', 'blah'],
|
82
|
+
},
|
83
|
+
},
|
84
|
+
|
85
|
+
t.str => {
|
86
|
+
pass: ['hey', ''],
|
87
|
+
fail: [1, {}, nil],
|
88
|
+
from_s: {
|
89
|
+
pass: ['hey', ''],
|
90
|
+
}
|
91
|
+
},
|
92
|
+
|
93
|
+
t.attrs(to_s: t.str) => {
|
94
|
+
pass: [''],
|
95
|
+
},
|
96
|
+
|
97
|
+
t.length(min: 0, max: 0) => {
|
98
|
+
pass: ['', [], {}],
|
99
|
+
fail: ['x', [1], {x: 1}],
|
100
|
+
},
|
101
|
+
|
102
|
+
t.length(0) => {
|
103
|
+
pass: ['', [], {}],
|
104
|
+
fail: ['x', [1], {x: 1}],
|
105
|
+
},
|
106
|
+
|
107
|
+
t.bounded(min: 0, max: 0) => {
|
108
|
+
pass: [0],
|
109
|
+
},
|
110
|
+
|
111
|
+
t.union(t.non_neg_int, t.bounded(min: 0, max: 0)) => {
|
112
|
+
pass: [0],
|
113
|
+
},
|
114
|
+
|
115
|
+
t.attrs(length: t.bounded(min: 0, max: 0)) => {
|
116
|
+
pass: ['', []],
|
117
|
+
fail: ['hey'],
|
118
|
+
},
|
119
|
+
|
120
|
+
t.str(length: 0) => {
|
121
|
+
pass: [''],
|
122
|
+
fail: ['hey'],
|
123
|
+
},
|
124
|
+
|
125
|
+
t.array => {
|
126
|
+
pass: [[], [1, 2, 3]],
|
127
|
+
fail: [nil, {}, '1,2,3'],
|
128
|
+
from_s: {
|
129
|
+
pass: ['1,2,3', '', '[1,2,3]', 'a, b, c'],
|
130
|
+
}
|
131
|
+
},
|
132
|
+
|
133
|
+
t.array(t.int) => {
|
134
|
+
pass: [[], [1, 2, 3]],
|
135
|
+
fail: [['1']],
|
136
|
+
from_s: {
|
137
|
+
pass: ['1,2,3', '', '[1,2,3]'],
|
138
|
+
fail: ['a,b,c'],
|
139
|
+
}
|
140
|
+
},
|
141
|
+
|
142
|
+
}.each do |type, tests|
|
143
|
+
if tests[:pass]
|
144
|
+
tests[:pass].each do |value|
|
145
|
+
it "ACCEPS #{ value.inspect } as #{ type }" do
|
146
|
+
expect(type.test value).to be true
|
147
|
+
expect {type.check value}.not_to raise_error
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
if tests[:fail]
|
153
|
+
tests[:fail].each do |value|
|
154
|
+
it "REJECTS #{ value.inspect } as #{ type }" do
|
155
|
+
expect(type.test value).to be false
|
156
|
+
expect {type.check value}.to raise_error TypeError
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
if tests[:from_s]
|
162
|
+
if tests[:from_s][:pass]
|
163
|
+
tests[:from_s][:pass].each do |string|
|
164
|
+
unless string.is_a? String
|
165
|
+
raise "must be string: #{ string.inspect }"
|
166
|
+
end
|
167
|
+
|
168
|
+
it "#{ type }#from_s ACCEPTS string #{ string.inspect }" do
|
169
|
+
expect{ type.from_s string }.not_to raise_error
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end # from_s
|
174
|
+
end # each
|
175
|
+
end # basic types #test and #check
|
176
|
+
|
177
|
+
describe ".from_repr" do
|
178
|
+
{
|
179
|
+
t.str => ['string', 'str', 'String', 'STRING', 'STR'],
|
180
|
+
t.int => ['int', 'integer', 'Integer', 'INT'],
|
181
|
+
t.bool => ['bool', 'boolean', 'Boolean', 'BOOL'],
|
182
|
+
t.array => ['array', 'list', 'Array'],
|
183
|
+
# t.union('a', 'b', 'c') => [
|
184
|
+
# {
|
185
|
+
# 'one_of': ['a', 'b', 'c'],
|
186
|
+
# }
|
187
|
+
# ]
|
188
|
+
}.each do |type, inputs|
|
189
|
+
inputs.each do |input|
|
190
|
+
it "converts #{ input.inspect } to #{ type }" do
|
191
|
+
expect(NRSER::Types.from_repr input).to eq type
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end # #from_repr
|
196
|
+
|
197
|
+
end # QB::Types
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nrser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- nrser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-04-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -98,6 +98,21 @@ files:
|
|
98
98
|
- lib/nrser/collection.rb
|
99
99
|
- lib/nrser/logger.rb
|
100
100
|
- lib/nrser/refinements.rb
|
101
|
+
- lib/nrser/types.rb
|
102
|
+
- lib/nrser/types/any.rb
|
103
|
+
- lib/nrser/types/array.rb
|
104
|
+
- lib/nrser/types/attrs.rb
|
105
|
+
- lib/nrser/types/booleans.rb
|
106
|
+
- lib/nrser/types/bounded.rb
|
107
|
+
- lib/nrser/types/combinators.rb
|
108
|
+
- lib/nrser/types/hash.rb
|
109
|
+
- lib/nrser/types/is.rb
|
110
|
+
- lib/nrser/types/is_a.rb
|
111
|
+
- lib/nrser/types/maybe.rb
|
112
|
+
- lib/nrser/types/numbers.rb
|
113
|
+
- lib/nrser/types/strings.rb
|
114
|
+
- lib/nrser/types/type.rb
|
115
|
+
- lib/nrser/types/where.rb
|
101
116
|
- lib/nrser/version.rb
|
102
117
|
- nrser.gemspec
|
103
118
|
- spec/nrser/collection/each_spec.rb
|
@@ -117,9 +132,13 @@ files:
|
|
117
132
|
- spec/nrser/refinements/erb_spec.rb
|
118
133
|
- spec/nrser/refinements/format_exception_spec.rb
|
119
134
|
- spec/nrser/refinements/indent_spec.rb
|
135
|
+
- spec/nrser/refinements/pathname_spec.rb
|
120
136
|
- spec/nrser/refinements/truncate_spec.rb
|
121
137
|
- spec/nrser/template_spec.rb
|
122
138
|
- spec/nrser/truncate_spec.rb
|
139
|
+
- spec/nrser/types/combinators_spec.rb
|
140
|
+
- spec/nrser/types/is_spec.rb
|
141
|
+
- spec/nrser/types_spec.rb
|
123
142
|
- spec/nrser_spec.rb
|
124
143
|
- spec/spec_helper.rb
|
125
144
|
- tmp/.gitkeep
|
@@ -143,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
143
162
|
version: '0'
|
144
163
|
requirements: []
|
145
164
|
rubyforge_project:
|
146
|
-
rubygems_version: 2.
|
165
|
+
rubygems_version: 2.6.11
|
147
166
|
signing_key:
|
148
167
|
specification_version: 4
|
149
168
|
summary: basic ruby utils i use in a lot of stuff.
|
@@ -165,9 +184,12 @@ test_files:
|
|
165
184
|
- spec/nrser/refinements/erb_spec.rb
|
166
185
|
- spec/nrser/refinements/format_exception_spec.rb
|
167
186
|
- spec/nrser/refinements/indent_spec.rb
|
187
|
+
- spec/nrser/refinements/pathname_spec.rb
|
168
188
|
- spec/nrser/refinements/truncate_spec.rb
|
169
189
|
- spec/nrser/template_spec.rb
|
170
190
|
- spec/nrser/truncate_spec.rb
|
191
|
+
- spec/nrser/types/combinators_spec.rb
|
192
|
+
- spec/nrser/types/is_spec.rb
|
193
|
+
- spec/nrser/types_spec.rb
|
171
194
|
- spec/nrser_spec.rb
|
172
195
|
- spec/spec_helper.rb
|
173
|
-
has_rdoc:
|