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.
@@ -0,0 +1,144 @@
1
+ = YES - YAML Easy Schema
2
+
3
+ Author:: Thomas Sawyer
4
+ License:: BSD-2-Clause
5
+ Copyright:: (c) 2011 Thomas Sawyer, Rubyworks
6
+
7
+
8
+ == SALES PITCH
9
+
10
+ It doesn't get any easier than this!
11
+
12
+ YES is a schema system for YAML that is intuitive and extremely powerful.
13
+
14
+ YES Schemas are also YAML documents, so they eat there own dog food.
15
+
16
+
17
+ == HOW IT WORKS
18
+
19
+ The design of YeS is as simple as it is powerful. A YeS schema is composed
20
+ of YPath selectors mapped to document constraints. YPath is a syntax
21
+ for selecting *nodes* from a YAML document.
22
+
23
+ When validating a YAML document against a YeS schema a "lint" program
24
+ simply collects all matching nodes with their applicable constraints into
25
+ a collection of *unit-validations*. Then this collection is filtered of
26
+ all *passing* validations. All that is left are the failures. If the filtered
27
+ list is empty the document is completely valid. If not empty, the lint
28
+ program can provide a detailed *editorial* list of the failures.
29
+
30
+ Constraints are given as a mappings of validation type to validation
31
+ parameters.
32
+
33
+ Lets take an example:
34
+
35
+ people/*/name:
36
+ regexp: '[^/n]'
37
+
38
+ This simple schema selects all nodes under a `people` sequence of
39
+ mappings with a name key, the value of which cannot contain newlines.
40
+ In other words:
41
+
42
+ --
43
+ people:
44
+ - name: Charlie Adams
45
+ - name: Julie Ann Rose
46
+
47
+ Would satisfy the schema. But,
48
+
49
+ --
50
+ people:
51
+ - name: |
52
+ Charlie
53
+ Adams
54
+
55
+ Would not.
56
+
57
+ TODO: Better Examples!!!
58
+
59
+ Sometimes multiple constraints of the same type need to be applied to
60
+ a set of nodes. This can be done by expressing the same YPath with
61
+ different constraints, for example:
62
+
63
+ people/*/name:
64
+ regexp: '[^/t]'
65
+ people/*/name:
66
+ regexp: '[^/n]'
67
+
68
+ But to make the intent more succinct a sequence of constraints can be give
69
+ along with the *logical-and* tag , `!!and`.
70
+
71
+ people/*/name: !!and
72
+ - regexp: '[^/t]'
73
+ - regexp: '[^/n]'
74
+
75
+ If the `!!and` tag is not given, then the default operator is used,
76
+ *logical-or*, which can also be explicitly stated as `!!or`:
77
+
78
+ people/*/name: !!or
79
+ - regexp: '[^/t]'
80
+ - regexp: '[^/n]'
81
+
82
+ In this way logical relationships of constraints can be created.
83
+
84
+ people/*/name: !!or
85
+ - !!and
86
+ - regexp: '[^/t]'
87
+ - regexp: '[^/n]'
88
+ - !!and
89
+ - regexp: '[^/t]'
90
+ - regexp: '[^/n]'
91
+
92
+
93
+ == COMMAND LINE
94
+
95
+ To use on the command line *lint* tool. Say we have a `schema.yes` file:
96
+
97
+ ---
98
+ name:
99
+ type: str
100
+ regexp: '[^\n]'
101
+ required: true
102
+ age:
103
+ type: int
104
+ birth:
105
+ type: date
106
+
107
+ Try it on `sample.yaml`.
108
+
109
+ ---
110
+ name: Thomas T. Thomas
111
+ age: 42
112
+ birth: 1976-07-04
113
+
114
+ Using the executable.
115
+
116
+ $ yes-lint schema.yes sample.yaml
117
+
118
+ In code that is:
119
+
120
+ require 'yes'
121
+
122
+ lint = YES::Lint.new(File.new('schema.yes'))
123
+
124
+ lint.validate(File.new('sample.yaml'))
125
+
126
+
127
+ == CONTRIBUTE
128
+
129
+ Come on folks! Let's get YAML up to snuff. A good Schema could really
130
+ help YAML go the distance and penetrate some of those "Enterprisey" worlds.
131
+
132
+ * Please read, write and comment on issues[https://github.com/rubyworks/yes/issues].
133
+ * Please critique the code.
134
+ * Please fork and submit patches in topic branches.
135
+
136
+ And please contribute to {Rubyworks Ruby Development Fund}[http://rubyworks.github.org]
137
+ so us poor Ruby OSS developers can eat :)
138
+
139
+
140
+ == COPYRIGHT
141
+
142
+ (BSD-2 license)
143
+
144
+ Copyright (c) 2011 Rubyworks, Thomas Sawyer
@@ -0,0 +1,10 @@
1
+ = SPECIAL THANKS
2
+
3
+ == Kwaify
4
+
5
+ Copyright:: (c) 2005-2010 Kuwata Lab. All rights reserved.
6
+ License:: MIT
7
+ Website:: http://www.kuwata-lab.com/kwalify
8
+
9
+ Although no Kwalify code was actually used to build YES, it provided
10
+ inspiration for some of the design choices.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'yes/cli'
3
+ YES.cli(*ARGV)
4
+
@@ -0,0 +1,21 @@
1
+ ---
2
+ /*:
3
+ type: map
4
+
5
+ /*/type:
6
+ type: str
7
+
8
+ /*/required:
9
+ type: bool
10
+ required: false
11
+
12
+ /*/range:
13
+ type: str
14
+ pattern: '^\d+\.\.(\d+|n)$'
15
+
16
+ /*/pattern:
17
+ type: str
18
+
19
+ /*/fnmatch:
20
+ type: str
21
+
@@ -0,0 +1,38 @@
1
+ module YES
2
+
3
+ # Simple validatily check.
4
+ #
5
+ # @return [Boolean]
6
+ def self.valid?(schema, yaml)
7
+ yes = YES::Lint.new(@schema)
8
+ yes.validate(@yaml).empty?
9
+ end
10
+
11
+ end
12
+
13
+ #
14
+ require 'yaml'
15
+
16
+ require 'yes/constraints/abstract_constraint'
17
+ require 'yes/constraints/node_constraint'
18
+ require 'yes/constraints/tree_constraint'
19
+
20
+ require 'yes/constraints/range'
21
+ require 'yes/constraints/regexp'
22
+ require 'yes/constraints/fnmatch'
23
+
24
+ require 'yes/constraints/tag'
25
+ require 'yes/constraints/type'
26
+
27
+ require 'yes/constraints/count'
28
+ require 'yes/constraints/length'
29
+ require 'yes/constraints/requires'
30
+ require 'yes/constraints/inclusive'
31
+ require 'yes/constraints/exclusive'
32
+
33
+ require 'yes/constraints/value'
34
+ require 'yes/constraints/key'
35
+
36
+ require 'yes/logical_and'
37
+ require 'yes/lint'
38
+
@@ -0,0 +1,20 @@
1
+ require 'yes'
2
+
3
+ module YES
4
+
5
+ def self.cli(*argv)
6
+ schema_file = argv[0]
7
+ target_file = argv[1]
8
+
9
+ lint = Lint.new(File.new(schema_file))
10
+ edit = lint.validate(File.new(target_file))
11
+
12
+ if edit.size == 0
13
+ #$stderr.puts "valid: #{target_file}"
14
+ else
15
+ $stderr.puts edit.to_yaml
16
+ exit -1
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,121 @@
1
+ module YES
2
+
3
+ #
4
+ def self.constraints
5
+ @constraints ||= []
6
+ end
7
+
8
+ #
9
+ module Constraints
10
+
11
+ # AbstractConstraint serves as the base class for
12
+ # all other constraints.
13
+ class AbstractConstraint
14
+
15
+ #
16
+ def self.inherited(base)
17
+ YES.constraints << base
18
+ end
19
+
20
+ # noop
21
+ def self.checklist(spec, tree, nodes)
22
+ []
23
+ end
24
+
25
+ #
26
+ def initialize(spec, tree, nodes)
27
+ @spec = spec
28
+ @tree = tree
29
+ @nodes = nodes
30
+ end
31
+
32
+ public
33
+
34
+ #
35
+ attr :nodes
36
+
37
+ #
38
+ attr :spec
39
+
40
+ #
41
+ attr :tree
42
+
43
+ # MUST OVERRIDE THIS METHOD IN SUBCLASSES
44
+ #
45
+ def validate(spec)
46
+ raise "undefined method -- `validate'"
47
+ end
48
+
49
+ # MUST OVERRIDE THIS METHOD IN SUBCLASSES
50
+ #
51
+ def self.applicable?(spec)
52
+ raise "undefined class method -- `applicable?'"
53
+ end
54
+
55
+ #
56
+ def valid?
57
+ return true unless applicable?
58
+ recurse_valid?(spec)
59
+ end
60
+
61
+ #
62
+ def applicable?
63
+ self.class.applicable?(spec)
64
+ end
65
+
66
+ private
67
+
68
+ #
69
+ def recurse_valid?(spec=nil)
70
+ spec ||= self.spec
71
+ case spec
72
+ when Array # logical-or
73
+ spec.any?{ |sub_spec| recurse_valid?(sub_spec) }
74
+ else
75
+ validate(spec)
76
+ end
77
+ end
78
+
79
+ # Range matching is used by a couple of validators.
80
+ #
81
+ # @param range [Array, String]
82
+ # The range representation.
83
+ #
84
+ # @example
85
+ # 1..1 =~ 1
86
+ # 1...2 =~ 1
87
+ # [1,1] =~ 1
88
+ # [1,2) =~ 1
89
+ # (1,2] =~ 2
90
+ # (1,3) =~ 2
91
+ #
92
+ def match_delta(range, value)
93
+ case range
94
+ when Array # array can do string comparisons
95
+ value > range.first && value < range.last
96
+ when /^(.*)\.\.\.(n|N)$/
97
+ value >= $1.to_f
98
+ when /^(.*)\.\.(n|N)$/
99
+ value >= $1.to_f
100
+ when /^(.*)\.\.\.(.*)$/
101
+ value >= $1.to_f && value < $2.to_f
102
+ when /^(.*)\.\.(.*)$/
103
+ value >= $1.to_f && value <= $2.to_f
104
+ when /^\[(.*)\,(.*)\]$/
105
+ value >= $1.to_f && value <= $2.to_f
106
+ when /^\[(.*)\,(.*)\)$/
107
+ value >= $1.to_f && value < $2.to_f
108
+ when /^\((.*)\,(.*)\]$/
109
+ value > $1.to_f && value <= $2.to_f
110
+ when /^\((.*)\,(.*)\)$/
111
+ value > $1.to_f && value < $2.to_f
112
+ else # assume range is just a number
113
+ range.to_f == value.to_f
114
+ end
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+
121
+ end
@@ -0,0 +1,39 @@
1
+ module YES
2
+
3
+ module Constraints
4
+
5
+ # Validate from a selction of choices.
6
+ #
7
+ # choice: [M,F]
8
+ #
9
+ class Choice < NodeConstraint
10
+
11
+ #
12
+ # @return [Array<Constraint>]
13
+ def self.checklist(spec, tree, nodes)
14
+ return [] unless applicable?(spec)
15
+ nodes.map do |node|
16
+ new(spec, tree, node)
17
+ end
18
+ end
19
+
20
+ # Only applicable if `choice` field is in spec.
21
+ def self.applicable?(spec)
22
+ spec['choice']
23
+ end
24
+
25
+ # Validate that a node's value is amoung a provided
26
+ # list of values.
27
+ #
28
+ # @return [Boolean] validity
29
+ def validate(spec)
30
+ choice = Array(spec['choice'])
31
+ choice.include?(node.transform)
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
@@ -0,0 +1,38 @@
1
+ module YES
2
+
3
+ module Constraints
4
+
5
+ # Count validation ensures there are a limited number of matching
6
+ # nodes in a document.
7
+ #
8
+ # //gold:
9
+ # count: 1..4
10
+ #
11
+ # Would mean the `gold` mapping key could only appear 1 to 4 times
12
+ # in the entire document.
13
+ class Count < TreeConstraint
14
+
15
+ #
16
+ # @return [Array<Constraint>]
17
+ def self.checklist(spec, tree, nodes)
18
+ return [] unless applicable?(spec)
19
+ [new(spec, tree, nodes)]
20
+ end
21
+
22
+ # Only applicable if `count` entry is in the spec.
23
+ def self.applicable?(spec)
24
+ spec['count']
25
+ end
26
+
27
+ # Validate count ensure there is a minimum and/or maximum
28
+ # number of matching nodes.
29
+ def validate(spec)
30
+ count = spec['count']
31
+ match_delta(count, nodes.size)
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,48 @@
1
+ module YES
2
+
3
+ module Constraints
4
+
5
+ # Exclusion - This can either be a boolean expression in
6
+ # which case it validates that there is no more than one matching
7
+ # node. Otherwise, the value is taken to be a ypath and validates
8
+ # that there are no matching paths if the main selection is present.
9
+ #--
10
+ # TODO: Provide $parent$ path substitution ?
11
+ #++
12
+ class Exclusive < 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 `exclusive` feild is in the spec.
22
+ def self.applicable?(spec)
23
+ spec['exclusive']
24
+ end
25
+
26
+ # Exclusion - This can either be a boolean expression in
27
+ # which case it validates that there is no more than one matching
28
+ # node. Otherwise, the value is taken to be a ypath and validates
29
+ # that there are no matching paths if the main selection is present.
30
+ #
31
+ # @return [Boolean] validity
32
+ def validate(spec)
33
+ exclusive = spec['exclusive']
34
+
35
+ case exclusive
36
+ when true, false
37
+ nodes.size <= 1
38
+ else
39
+ ex_nodes = tree.select(exclusive)
40
+ nodes.size == 0 or ex_nodes.size == 0
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ end