yes 0.0.1
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.
- 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
|