yes 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +41 -0
- data/.yardopts +8 -0
- data/HISTORY.rdoc +31 -0
- data/LICENSE.rdoc +35 -0
- data/QED.rdoc +1036 -0
- data/README.rdoc +144 -0
- data/THANKS.rdoc +10 -0
- data/bin/yes-lint +4 -0
- data/data/yes/yes.yes +21 -0
- data/lib/yes.rb +38 -0
- data/lib/yes/cli.rb +20 -0
- data/lib/yes/constraints/abstract_constraint.rb +121 -0
- data/lib/yes/constraints/choice.rb +39 -0
- data/lib/yes/constraints/count.rb +38 -0
- data/lib/yes/constraints/exclusive.rb +48 -0
- data/lib/yes/constraints/fnmatch.rb +42 -0
- data/lib/yes/constraints/inclusive.rb +50 -0
- data/lib/yes/constraints/key.rb +55 -0
- data/lib/yes/constraints/kind.rb +34 -0
- data/lib/yes/constraints/length.rb +46 -0
- data/lib/yes/constraints/node_constraint.rb +55 -0
- data/lib/yes/constraints/range.rb +43 -0
- data/lib/yes/constraints/regexp.rb +52 -0
- data/lib/yes/constraints/required.rb +45 -0
- data/lib/yes/constraints/requires.rb +57 -0
- data/lib/yes/constraints/tag.rb +55 -0
- data/lib/yes/constraints/tree_constraint.rb +14 -0
- data/lib/yes/constraints/type.rb +91 -0
- data/lib/yes/constraints/value.rb +62 -0
- data/lib/yes/genclass.rb +2 -0
- data/lib/yes/lint.rb +101 -0
- data/lib/yes/logical_and.rb +13 -0
- metadata +110 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
module YES
|
2
|
+
|
3
|
+
module Constraints
|
4
|
+
|
5
|
+
# Validate file glob match. This uess standard unix-style file matching,
|
6
|
+
# primarily '*` and `?`, to detrmine a mathing node value.
|
7
|
+
# All values are converted to strings (using #to_s) for comparison.
|
8
|
+
#--
|
9
|
+
# TODO: better name then `FNMatch`?
|
10
|
+
#++
|
11
|
+
class FNMatch < NodeConstraint
|
12
|
+
|
13
|
+
#
|
14
|
+
# @return [Array<Constraint>]
|
15
|
+
def self.checklist(spec, tree, nodes)
|
16
|
+
return [] unless applicable?(spec)
|
17
|
+
nodes.map do |node|
|
18
|
+
new(spec, tree, node)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Only applicable if `fnmatch` field is in spec.
|
23
|
+
def self.applicable?(spec)
|
24
|
+
spec['fnmatch']
|
25
|
+
end
|
26
|
+
|
27
|
+
# Validate file glob match. This uess standard unix-style file matching,
|
28
|
+
# primarily '*` and `?`, to detrmine a mathing node value.
|
29
|
+
# All values are converted to strings (using #to_s) for comparison.
|
30
|
+
#
|
31
|
+
# @return [Boolean] validity
|
32
|
+
def validate(spec)
|
33
|
+
fnmatch = spec['fnmatch']
|
34
|
+
|
35
|
+
File.fnmatch(fnmatch, node.value)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module YES
|
2
|
+
|
3
|
+
module Constraints
|
4
|
+
|
5
|
+
# Inclusion can either be a boolean expression in
|
6
|
+
# which case it validates that there is at least one matching
|
7
|
+
# node. Otherwise, the value is taken to be a ypath and validates
|
8
|
+
# that there are matching paths if the main selection is present.
|
9
|
+
#--
|
10
|
+
# TODO: Provide $parent$ path substitution ?
|
11
|
+
#++
|
12
|
+
class Inclusive < TreeConstraint
|
13
|
+
|
14
|
+
#
|
15
|
+
# @return [Array<Constraint>]
|
16
|
+
def self.checklist(spec, tree, nodes)
|
17
|
+
return [] unless applicable?(spec)
|
18
|
+
[new(spec, tree, nodes)]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Only applicable if `inclusive` field in in the spec.
|
22
|
+
def self.applicable?(spec)
|
23
|
+
spec['inclusive']
|
24
|
+
end
|
25
|
+
|
26
|
+
# Validate inclusion - This can either be a boolean expression in
|
27
|
+
# which case it validates that there is at least one matching
|
28
|
+
# node. Otherwise, the value is taken to be a ypath and validates
|
29
|
+
# that there are matching paths if the main selection is present.
|
30
|
+
#
|
31
|
+
# @return [Boolean] validity
|
32
|
+
def valid?
|
33
|
+
return true unless applicable?
|
34
|
+
|
35
|
+
inclusive = spec['inclusive']
|
36
|
+
|
37
|
+
case inclusive
|
38
|
+
when true, false
|
39
|
+
nodes.size > 0
|
40
|
+
else
|
41
|
+
in_nodes = tree.select(inclusive)
|
42
|
+
nodes.size == 0 or in_nodes.size > 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module YES
|
2
|
+
|
3
|
+
module Constraints
|
4
|
+
|
5
|
+
# Validate if a mapping node's _keys_ conforms to a constraint.
|
6
|
+
#
|
7
|
+
# //authors:
|
8
|
+
# type: map
|
9
|
+
# key:
|
10
|
+
# type: str
|
11
|
+
#
|
12
|
+
class Key < NodeConstraint
|
13
|
+
|
14
|
+
# For key constraint, the work is all handled by the
|
15
|
+
# checklist method.
|
16
|
+
#
|
17
|
+
# @return [Array<Constraint>]
|
18
|
+
def self.checklist(spec, tree, nodes)
|
19
|
+
return [] unless applicable?(spec)
|
20
|
+
|
21
|
+
key_spec = spec['key']
|
22
|
+
list = []
|
23
|
+
|
24
|
+
nodes.each do |node|
|
25
|
+
case node.kind
|
26
|
+
when :map
|
27
|
+
YES.constraints.each do |c|
|
28
|
+
list.concat(c.checklist(key_spec, tree, node.value.keys))
|
29
|
+
end
|
30
|
+
else
|
31
|
+
raise "key constraint applies only to mappings"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
list
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
#
|
40
|
+
def self.applicable?(spec)
|
41
|
+
spec['key']
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# no-op
|
46
|
+
#
|
47
|
+
# @return [Boolean] validity
|
48
|
+
def validate(spec)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module YES
|
2
|
+
|
3
|
+
module Constraints
|
4
|
+
|
5
|
+
# Validate the <i>kind of node</i>. There are only three kinds
|
6
|
+
# of nodes: `scalar`, `map` and `seq`.
|
7
|
+
#
|
8
|
+
class Kind < NodeConstraint
|
9
|
+
|
10
|
+
#
|
11
|
+
# @return [Array<Constraint>]
|
12
|
+
def self.checklist(spec, tree, nodes)
|
13
|
+
return [] unless applicable?(spec)
|
14
|
+
nodes.map do |node|
|
15
|
+
new(spec, tree, node)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
def self.applicable?(spec)
|
21
|
+
spec['kind']
|
22
|
+
end
|
23
|
+
|
24
|
+
# Validate type.
|
25
|
+
#
|
26
|
+
def validate(spec)
|
27
|
+
node.kind.to_s == spec['kind']
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module YES
|
2
|
+
|
3
|
+
module Constraints
|
4
|
+
|
5
|
+
# Validate if a node's value is within a certain length.
|
6
|
+
# The value is converted to a string using #to_s for the
|
7
|
+
# comparison.
|
8
|
+
#
|
9
|
+
# //code:
|
10
|
+
# length: 3
|
11
|
+
#
|
12
|
+
# A valid code value could then have no more than three characters.
|
13
|
+
class Length < NodeConstraint
|
14
|
+
|
15
|
+
#
|
16
|
+
#
|
17
|
+
# @return [Array<Constraint>]
|
18
|
+
def self.checklist(spec, tree, nodes)
|
19
|
+
return [] unless applicable?(spec)
|
20
|
+
nodes.map do |node|
|
21
|
+
new(spec, tree, node)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
#
|
27
|
+
def self.applicable?(spec)
|
28
|
+
spec['length']
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Validate if a node value is within a certain length.
|
33
|
+
# The value is converted to a string using #to_s for the
|
34
|
+
# comparison.
|
35
|
+
#
|
36
|
+
# @return [Boolean] validity
|
37
|
+
def validate(spec)
|
38
|
+
length = spec['length']
|
39
|
+
match_delta(length, node.value.to_s.size)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module YES
|
2
|
+
|
3
|
+
module Constraints
|
4
|
+
|
5
|
+
# The NodeConstraint class is an abstract class
|
6
|
+
# and used for create constraint subclasses that
|
7
|
+
# apply constraints on a sigle node.
|
8
|
+
#
|
9
|
+
class NodeConstraint < AbstractConstraint
|
10
|
+
|
11
|
+
# Like {Abstract#initialize} but takes a `node` qas well.
|
12
|
+
def initialize(spec, tree, node)
|
13
|
+
super(spec, tree, [node])
|
14
|
+
@node = node
|
15
|
+
end
|
16
|
+
|
17
|
+
public
|
18
|
+
|
19
|
+
# YAML Node.
|
20
|
+
attr :node
|
21
|
+
|
22
|
+
# Get the applicable node's tag.
|
23
|
+
def tag
|
24
|
+
node.type_id
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the applicable node's value.
|
28
|
+
def value
|
29
|
+
node.value
|
30
|
+
end
|
31
|
+
|
32
|
+
# # Covert a YAML node (Syck) node into a generic representation.
|
33
|
+
# #
|
34
|
+
# # TODO: Should `style` be part of this? Also, is `kind` the proper term?
|
35
|
+
# def catholic_node(node)
|
36
|
+
# n = {}
|
37
|
+
# n['tag'] = node.type_id
|
38
|
+
# #n['type'] = #base_type_id(node)
|
39
|
+
# n['kind'] = node.kind.to_s
|
40
|
+
# n['value'] = node.value
|
41
|
+
# n['style'] = node.instance_variable_get('@style').to_s
|
42
|
+
# n
|
43
|
+
# end
|
44
|
+
|
45
|
+
#
|
46
|
+
#
|
47
|
+
def self.applicable?(spec)
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module YES
|
2
|
+
|
3
|
+
module Constraints
|
4
|
+
|
5
|
+
# Validate the a nodes value is within a certain range.
|
6
|
+
# Primarily this works for numeric values, but it can
|
7
|
+
# also work for string in ASCII/UTF-8 order, by using
|
8
|
+
# a 2-element array for comparison.
|
9
|
+
#
|
10
|
+
# //note:
|
11
|
+
# range: ['A','G']
|
12
|
+
#
|
13
|
+
# Valid values for are then only A, B, C, D, E, F and G.
|
14
|
+
class Range < NodeConstraint
|
15
|
+
|
16
|
+
#
|
17
|
+
# @return [Array<Validaiton>]
|
18
|
+
def self.checklist(spec, tree, nodes)
|
19
|
+
return [] unless applicable?(spec)
|
20
|
+
nodes.map do |node|
|
21
|
+
new(spec, tree, node)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
def self.applicable?(spec)
|
27
|
+
spec['range']
|
28
|
+
end
|
29
|
+
|
30
|
+
# Validate if a node is the only one of it's value in a sequence
|
31
|
+
# or mapping.
|
32
|
+
#
|
33
|
+
# @return [Boolean] validity
|
34
|
+
def validate(spec)
|
35
|
+
range = spec['range']
|
36
|
+
match_delta(range, node.transform) ? true : false
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module YES
|
2
|
+
|
3
|
+
module Constraints
|
4
|
+
|
5
|
+
# Validate matching values against a regular expression.
|
6
|
+
# All values are converted to strings (using #to_s) for comparison.
|
7
|
+
#
|
8
|
+
# //pin:
|
9
|
+
# regexp: /^\d\s\d\d$/
|
10
|
+
#
|
11
|
+
class Regexp < NodeConstraint
|
12
|
+
|
13
|
+
#
|
14
|
+
# @return [Array<Validaiton>]
|
15
|
+
def self.checklist(spec, tree, nodes)
|
16
|
+
return [] unless applicable?(spec)
|
17
|
+
nodes.map do |node|
|
18
|
+
new(spec, tree, node)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
def self.applicable?(spec)
|
24
|
+
spec['regexp']
|
25
|
+
end
|
26
|
+
|
27
|
+
# Validate matching values against a regular expression.
|
28
|
+
# All values are converted to strings (using #to_s) for comparison.
|
29
|
+
#
|
30
|
+
# @return [Boolean] validity
|
31
|
+
def validate(spec)
|
32
|
+
regexp = parse_regexp(spec['regexp'])
|
33
|
+
regexp =~ node.value ? true : false
|
34
|
+
end
|
35
|
+
|
36
|
+
# The regular expression from `spec`.
|
37
|
+
#
|
38
|
+
# @return [Regexp] spec's regular expression
|
39
|
+
def parse_regexp(re)
|
40
|
+
case re
|
41
|
+
when /^\/(.*?)\/(\w*)$/
|
42
|
+
::Regexp.new($1) # TODO: modifiers
|
43
|
+
else
|
44
|
+
::Regexp.new(re)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module YES
|
2
|
+
|
3
|
+
module Constraints
|
4
|
+
|
5
|
+
#
|
6
|
+
# TODO: For the moment this is the same as Inclusive.
|
7
|
+
# It was originall inteded to work like {RequiresValidation}
|
8
|
+
# but it rpoved hard to work out the validation procedure
|
9
|
+
# when matching to the subfield. If we can fix it maybe we will
|
10
|
+
# keep, but for now THIS IS NOT USED.
|
11
|
+
#
|
12
|
+
class Required < TreeConstraint
|
13
|
+
|
14
|
+
#
|
15
|
+
# @return [Array<Constraint>]
|
16
|
+
def self.checklist(spec, tree, nodes)
|
17
|
+
return [] unless applicable?(spec)
|
18
|
+
[new(spec, tree, nodes)]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Only applicable if `required` field appears in spec.
|
22
|
+
def self.applicable?(spec)
|
23
|
+
spec['required']
|
24
|
+
end
|
25
|
+
|
26
|
+
# Validates whether a matching node must be present within it's parent.
|
27
|
+
#
|
28
|
+
# @return [Boolean] validity
|
29
|
+
def validate(spec)
|
30
|
+
required = spec['required']
|
31
|
+
|
32
|
+
case required
|
33
|
+
when true, false
|
34
|
+
nodes.size > 0
|
35
|
+
else
|
36
|
+
in_nodes = tree.select(required)
|
37
|
+
nodes.size == 0 or in_nodes.size > 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module YES
|
2
|
+
|
3
|
+
module Constraints
|
4
|
+
|
5
|
+
# Takes a list of relative YPaths and ensures they are present.
|
6
|
+
# This is most useful for ensuring the existance of mapping fields.
|
7
|
+
#
|
8
|
+
# foo:
|
9
|
+
# requires:
|
10
|
+
# - bar
|
11
|
+
#
|
12
|
+
# A valid document would be:
|
13
|
+
#
|
14
|
+
# foo:
|
15
|
+
# bar: true
|
16
|
+
#
|
17
|
+
# The literal meaing of this example is "if `foo` exists, the make sure
|
18
|
+
# `foo/bar` also exists.
|
19
|
+
class Requires < NodeConstraint
|
20
|
+
|
21
|
+
#
|
22
|
+
# @return [Array<Constraint>]
|
23
|
+
def self.checklist(spec, tree, nodes)
|
24
|
+
return [] unless applicable?(spec)
|
25
|
+
nodes.map do |node|
|
26
|
+
new(spec, tree, node)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
def self.applicable?(spec)
|
32
|
+
spec['requires']
|
33
|
+
end
|
34
|
+
|
35
|
+
# Validates whether a matching node must be present within it's parent.
|
36
|
+
#
|
37
|
+
# @return [Boolen] validity
|
38
|
+
def validate(spec)
|
39
|
+
requires = Array(spec['requires'])
|
40
|
+
|
41
|
+
requires.each do |rq|
|
42
|
+
case rq
|
43
|
+
when /^\// # absolute path
|
44
|
+
rq_nodes = tree.select(rq)
|
45
|
+
else
|
46
|
+
rq_nodes = node.select(rq)
|
47
|
+
end
|
48
|
+
return false unless rq_nodes.size > 0
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|