grayskull 0.1.2
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/LICENSE +20 -0
- data/README +0 -0
- data/bin/grayskull +6 -0
- data/lib/grayskull.rb +8 -0
- data/lib/grayskull/cli.rb +27 -0
- data/lib/grayskull/formats.rb +29 -0
- data/lib/grayskull/formats/json_handler.rb +23 -0
- data/lib/grayskull/formats/yaml_handler.rb +21 -0
- data/lib/grayskull/validator.rb +184 -0
- data/lib/grayskull/version.rb +3 -0
- metadata +99 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 <Your Name Here>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
File without changes
|
data/bin/grayskull
ADDED
data/lib/grayskull.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Grayskull
|
4
|
+
|
5
|
+
# The *CLI* class provides an interface for the command line functions
|
6
|
+
class CLI < Thor
|
7
|
+
|
8
|
+
desc "validate FILE SCHEMA" ,"Checks TEMPLATE is a valid file and matches SCHEMA."
|
9
|
+
# Creates a new *Validator* and validates the file
|
10
|
+
def validate(file,schema)
|
11
|
+
validator = Grayskull::Validator.new(file,schema)
|
12
|
+
results = validator.validate
|
13
|
+
|
14
|
+
if !results['result']
|
15
|
+
puts 'Validation Failed!'
|
16
|
+
results['errors'].each{
|
17
|
+
|error|
|
18
|
+
puts error
|
19
|
+
}
|
20
|
+
else
|
21
|
+
puts 'Validated Successfully!'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Grayskull
|
2
|
+
|
3
|
+
module Formats
|
4
|
+
|
5
|
+
autoload :YAMLHandler, 'grayskull/formats/yaml_handler'
|
6
|
+
autoload :JSONHandler, 'grayskull/formats/json_handler'
|
7
|
+
|
8
|
+
FILENAME_PATTERN = /(.+)\.([^\/\.]+)/
|
9
|
+
|
10
|
+
def self.detect_format(filename)
|
11
|
+
matches = FILENAME_PATTERN.match(filename.downcase).to_a
|
12
|
+
if matches
|
13
|
+
ext = matches[2]
|
14
|
+
case ext
|
15
|
+
when 'json'
|
16
|
+
return 'json'
|
17
|
+
when 'yml'
|
18
|
+
return 'yaml'
|
19
|
+
else
|
20
|
+
raise TypeError, "Not a supported file format: " + ext
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise LoadError, "Not a valid filename: " + filename
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Grayskull
|
4
|
+
|
5
|
+
module Formats
|
6
|
+
|
7
|
+
class JSONHandler
|
8
|
+
|
9
|
+
def self.load(file)
|
10
|
+
loaded = File.open(file)
|
11
|
+
content = loaded.gets nil
|
12
|
+
begin
|
13
|
+
return JSON.parse(content)
|
14
|
+
rescue Exception => e
|
15
|
+
raise e.class, 'File could not be parsed as valid JSON'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Grayskull
|
4
|
+
|
5
|
+
module Formats
|
6
|
+
|
7
|
+
class YAMLHandler
|
8
|
+
|
9
|
+
def self.load(file)
|
10
|
+
begin
|
11
|
+
return YAML.load_file(file)
|
12
|
+
rescue Exception => e
|
13
|
+
raise e.class, 'File could not be parsed as valid YAML'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module Grayskull
|
2
|
+
|
3
|
+
class Validator
|
4
|
+
|
5
|
+
def initialize(file,schema)
|
6
|
+
|
7
|
+
@file = file
|
8
|
+
@schema = schema
|
9
|
+
|
10
|
+
@loaded_file = self.load(file)
|
11
|
+
@loaded_schema = self.load(schema)
|
12
|
+
@types = @loaded_schema['types'] || {}
|
13
|
+
|
14
|
+
@errors = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def load(file)
|
18
|
+
|
19
|
+
format = Formats::detect_format File.basename(file)
|
20
|
+
|
21
|
+
case format
|
22
|
+
when 'yaml'
|
23
|
+
return Formats::YAMLHandler.load(file)
|
24
|
+
when 'json'
|
25
|
+
return Formats::JSONHandler.load(file)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate()
|
31
|
+
failed = []
|
32
|
+
|
33
|
+
@loaded_schema['sections'].each{
|
34
|
+
|section|
|
35
|
+
|
36
|
+
#check required sections are there
|
37
|
+
if (section['required'] && !@loaded_file.has_key?(section['name']))
|
38
|
+
@errors.push('Error: missing required section - ' + section['name'])
|
39
|
+
failed.push(section['name'])
|
40
|
+
elsif @loaded_file.has_key?(section['name'])
|
41
|
+
node = @loaded_file[section['name']]
|
42
|
+
validated = match_node(node,section,section['name'])
|
43
|
+
if(!validated)
|
44
|
+
failed.push(section['name'])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
}
|
49
|
+
|
50
|
+
result = {}
|
51
|
+
result["result"] = failed.empty?
|
52
|
+
|
53
|
+
if !failed.empty? && !@errors.empty?
|
54
|
+
result["errors"] = @errors
|
55
|
+
end
|
56
|
+
|
57
|
+
return result
|
58
|
+
end
|
59
|
+
|
60
|
+
# Checks template node matches the schema.
|
61
|
+
#
|
62
|
+
# Checks type of node against expected type and
|
63
|
+
# checks any children are of the accepted types.
|
64
|
+
def match_node(node,expected,label)
|
65
|
+
|
66
|
+
#check type
|
67
|
+
if !check_type(node,expected['type'],label,expected['ok_empty'])
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
|
71
|
+
if (node.kind_of?(Hash) || node.kind_of?(Array))
|
72
|
+
|
73
|
+
if node.empty? && !expected['ok_empty']
|
74
|
+
@errors.push('Error: node ' + label + ' cannot be empty')
|
75
|
+
return false
|
76
|
+
elsif !node.empty? && expected.has_key?('accepts')
|
77
|
+
valid_content = false
|
78
|
+
|
79
|
+
if node.kind_of?(Hash)
|
80
|
+
matched = []
|
81
|
+
unmatched = []
|
82
|
+
node.each_pair{
|
83
|
+
|key,value|
|
84
|
+
|
85
|
+
expected['accepts'].each{
|
86
|
+
|accepts|
|
87
|
+
|
88
|
+
result = check_type(value,accepts,key)
|
89
|
+
|
90
|
+
if result
|
91
|
+
matched.push(key)
|
92
|
+
if !unmatched.find_index(key).nil?
|
93
|
+
unmatched.slice(unmatched.find_index(key))
|
94
|
+
end
|
95
|
+
break
|
96
|
+
else
|
97
|
+
unmatched.push(key)
|
98
|
+
end
|
99
|
+
|
100
|
+
}
|
101
|
+
|
102
|
+
}
|
103
|
+
|
104
|
+
if(matched.count==node.count)
|
105
|
+
valid_content = true
|
106
|
+
else
|
107
|
+
unmatched.each{
|
108
|
+
|node|
|
109
|
+
|
110
|
+
@errors.push('Error: node ' + node + ' is not of an accepted type. Should be one of ' + expected['accepts'].join(', '))
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
elsif node.kind_of?(Array)
|
115
|
+
matched = []
|
116
|
+
unmatched = []
|
117
|
+
node.each_index{
|
118
|
+
|n|
|
119
|
+
|
120
|
+
expected['accepts'].each{
|
121
|
+
|accepts|
|
122
|
+
|
123
|
+
key = label + '[' + n.to_s + ']'
|
124
|
+
result = check_type(node[n],accepts,key)
|
125
|
+
|
126
|
+
if result
|
127
|
+
|
128
|
+
matched.push(key)
|
129
|
+
if !unmatched.find_index(key).nil?
|
130
|
+
unmatched.slice(unmatched.find_index(key))
|
131
|
+
end
|
132
|
+
break
|
133
|
+
else
|
134
|
+
unmatched.push(key)
|
135
|
+
end
|
136
|
+
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
if(matched.count==node.count)
|
141
|
+
valid_content = true
|
142
|
+
else
|
143
|
+
unmatched.each{
|
144
|
+
|node|
|
145
|
+
|
146
|
+
@errors.push('Error: node ' + node + ' is not of an accepted type. Should be one of ' + expected['accepts'].join(', '))
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
if !valid_content
|
153
|
+
@errors.push('Error: node ' + label + ' contains an unaccepted type.')
|
154
|
+
return false
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
return true
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
# Checks that the node is of the correct type
|
165
|
+
#
|
166
|
+
# If the expected node is a custom node type as defined in the schema
|
167
|
+
# It will run `match_node` to check that the node schema matches the
|
168
|
+
# custom type.
|
169
|
+
def check_type(node,expected_type,label,accept_nil = false)
|
170
|
+
|
171
|
+
valid_type = true;
|
172
|
+
if(@types.has_key?(expected_type))
|
173
|
+
valid_type = match_node(node,@types[expected_type],label)
|
174
|
+
elsif node.class.to_s != expected_type && !(node.kind_of?(NilClass) && (expected_type=='empty' || accept_nil))
|
175
|
+
valid_type = false
|
176
|
+
end
|
177
|
+
|
178
|
+
return valid_type
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grayskull
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 2
|
9
|
+
version: 0.1.2
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Will McKenzie
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-10-01 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: thor
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: json
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
description: Will validate YAML and JSON files based on a provided schema
|
47
|
+
email:
|
48
|
+
- will@oinutter.co.uk
|
49
|
+
executables:
|
50
|
+
- grayskull
|
51
|
+
extensions: []
|
52
|
+
|
53
|
+
extra_rdoc_files: []
|
54
|
+
|
55
|
+
files:
|
56
|
+
- README
|
57
|
+
- LICENSE
|
58
|
+
- lib/grayskull/cli.rb
|
59
|
+
- lib/grayskull/formats/json_handler.rb
|
60
|
+
- lib/grayskull/formats/yaml_handler.rb
|
61
|
+
- lib/grayskull/formats.rb
|
62
|
+
- lib/grayskull/validator.rb
|
63
|
+
- lib/grayskull/version.rb
|
64
|
+
- lib/grayskull.rb
|
65
|
+
- bin/grayskull
|
66
|
+
has_rdoc: true
|
67
|
+
homepage: http://github.com/OiNutter/grayskull
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
requirements: []
|
92
|
+
|
93
|
+
rubyforge_project: grayskull
|
94
|
+
rubygems_version: 1.3.7
|
95
|
+
signing_key:
|
96
|
+
specification_version: 3
|
97
|
+
summary: Validates data files based on a provided schema
|
98
|
+
test_files: []
|
99
|
+
|