nrser 0.0.15 → 0.0.16
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/.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:
|