ficus 0.0.5 → 2.0.0
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 +1 -0
- data/.travis.yml +5 -0
- data/README.md +15 -10
- data/Rakefile +4 -1
- data/examples/regexp/regexp.rb +22 -0
- data/examples/regexp/sample.conf +12 -0
- data/examples/simple/sample.conf +5 -0
- data/examples/simple/simple.rb +24 -0
- data/examples/templates/sample.conf +9 -0
- data/examples/templates/templates.rb +22 -0
- data/examples/typechecking/sample.conf +5 -0
- data/examples/typechecking/typechecking.rb +17 -0
- data/ficus.gemspec +2 -4
- data/lib/ficus.rb +164 -5
- data/spec/ficus_spec.rb +213 -89
- metadata +11 -19
- data/lib/ficus/dsl.rb +0 -36
- data/lib/ficus/exceptions.rb +0 -4
- data/lib/ficus/loader.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 484115640e759fe32eeefa0d4ec9b65982ad3510
|
4
|
+
data.tar.gz: 88ec5550e290697fff154936d6fc1174955d305d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 09fc932f9eee192ae7155b944512f74e7ed083e4d04df176f25bc1137685e6910a03b910ebf4504a23ba5dbeb273e2be5dfe10ff30729fca967ff6c77216af49
|
7
|
+
data.tar.gz: 0ef2a6d334d58e138cd5542c5e219775e32b5f1fc61d2580c13a33ed8997e02669f35001d0f927e0b6202d6d0b7caaaa9661677cd213e7a4dbd72bb758a06387
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# Ficus
|
2
2
|
|
3
|
-
|
3
|
+
[](https://travis-ci.org/drewfradette/ruby-ficus)
|
4
4
|
|
5
|
-
|
6
|
-
This uses the [`recursive-open-struct`](https://github.com/aetherknight/recursive-open-struct) but due to gem missing a [crucial fix](https://github.com/aetherknight/recursive-open-struct/commit/0c16caa1b9a19d12e97829f02083f0b7d21f0100)
|
7
|
-
I have simply added the latest version with the fix to the `lib`. When it's fixed in rubygems, I will add it as a gem dependency.
|
5
|
+
A simple YAML configuration DSL that does runtime validation.
|
8
6
|
|
9
7
|
## Installation
|
10
8
|
|
@@ -50,7 +48,9 @@ And now we can use Ficus to load the config and validate it at the same time.
|
|
50
48
|
```ruby
|
51
49
|
require 'ficus'
|
52
50
|
|
53
|
-
|
51
|
+
ficus = Ficus.load_file 'config.yml'
|
52
|
+
|
53
|
+
ficus.validation do
|
54
54
|
section 'section_1' do
|
55
55
|
required 'key1'
|
56
56
|
required 'key2'
|
@@ -75,13 +75,18 @@ config = Ficus.load 'config.yml' do
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
-
|
79
|
-
config
|
80
|
-
config
|
81
|
-
config
|
82
|
-
config
|
78
|
+
if ficus.valid?
|
79
|
+
ficus.config['section_1']['key1'] # value1
|
80
|
+
ficus.config['section_1']['key2'] # value2
|
81
|
+
ficus.config['section_1']['key3'] # value3
|
82
|
+
ficus.config['section_2']['key4'] # value4
|
83
|
+
else
|
84
|
+
ficus.errors.each { |err| puts err.to_s }
|
85
|
+
end
|
83
86
|
```
|
84
87
|
|
88
|
+
For more examples, see the `examples` directory.
|
89
|
+
|
85
90
|
## Contributing
|
86
91
|
|
87
92
|
1. Fork it
|
data/Rakefile
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'ficus'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
ficus = Ficus.load_file('sample.conf').validation do
|
6
|
+
section /^server\d?$/ do
|
7
|
+
required 'active'
|
8
|
+
optional 'state', 'pending'
|
9
|
+
end
|
10
|
+
|
11
|
+
section /^server[A-Z]?$/i do
|
12
|
+
required 'active'
|
13
|
+
optional 'state', 'running'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
if ficus.valid?
|
18
|
+
pp ficus.config
|
19
|
+
else
|
20
|
+
puts "Invalid Configuration:"
|
21
|
+
ficus.errors.each {|err| puts err.to_s}
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'ficus'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
ficus = Ficus.load_file('sample.conf').validation do
|
6
|
+
|
7
|
+
required 'key1'
|
8
|
+
required 'key2'
|
9
|
+
|
10
|
+
section 'section1' do
|
11
|
+
required 'active'
|
12
|
+
optional 'url', 'http://drewfradette.ca'
|
13
|
+
end
|
14
|
+
|
15
|
+
section 'section2', optional: true do
|
16
|
+
required 'key3'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
if ficus.valid?
|
21
|
+
pp ficus.config
|
22
|
+
else
|
23
|
+
ficus.errors.each {|err| puts err.to_s}
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'ficus'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
ficus = Ficus.load_file('sample.conf').validation do
|
6
|
+
# Note: The DSL is evaluated in order so be sure to
|
7
|
+
# declare your templates before you use them.
|
8
|
+
template :server do
|
9
|
+
required 'ip_address'
|
10
|
+
optional 'state', 'pending'
|
11
|
+
end
|
12
|
+
|
13
|
+
section 'web', template: :server
|
14
|
+
section 'db', template: :server
|
15
|
+
section 'cache', template: :server
|
16
|
+
end
|
17
|
+
|
18
|
+
if ficus.valid?
|
19
|
+
pp ficus.config
|
20
|
+
else
|
21
|
+
ficus.errors.each {|err| puts err.to_s}
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'ficus'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
ficus = Ficus.load_file('sample.conf').validation do
|
6
|
+
required 'active', :boolean
|
7
|
+
required 'size', :number
|
8
|
+
required 'name', :string
|
9
|
+
|
10
|
+
optional 'grep', 'lorem ipsum', /est qui dolorem/
|
11
|
+
end
|
12
|
+
|
13
|
+
if ficus.valid?
|
14
|
+
pp ficus.config
|
15
|
+
else
|
16
|
+
ficus.errors.each {|err| puts err.to_s}
|
17
|
+
end
|
data/ficus.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "ficus"
|
7
|
-
spec.version = '0.0
|
7
|
+
spec.version = '2.0.0'
|
8
8
|
spec.authors = ["Drew Fradette"]
|
9
9
|
spec.email = ["drew.fradette@gmail.com"]
|
10
10
|
spec.description = 'A runtime validation configuration DSL'
|
@@ -20,7 +20,5 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.add_development_dependency "bundler", "~> 1.3"
|
21
21
|
spec.add_development_dependency "rake"
|
22
22
|
spec.add_development_dependency 'rspec'
|
23
|
-
spec.add_development_dependency 'simplecov'
|
24
|
-
|
25
|
-
spec.add_dependency 'recursive-open-struct', '>= 0.4.5'
|
23
|
+
spec.add_development_dependency 'simplecov'
|
26
24
|
end
|
data/lib/ficus.rb
CHANGED
@@ -1,7 +1,166 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
require 'logger'
|
3
|
-
require 'recursive-open-struct'
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
class Ficus
|
4
|
+
class MissingValidation < StandardError; end
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def load_file(filename)
|
8
|
+
Ficus.load(YAML.load_file(filename))
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(data)
|
12
|
+
Ficus.new data
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :config, :errors, :opts, :schema, :templates
|
17
|
+
|
18
|
+
TYPES = {
|
19
|
+
string: [String],
|
20
|
+
number: [Fixnum, Float],
|
21
|
+
boolean: [TrueClass, FalseClass],
|
22
|
+
array: [Array],
|
23
|
+
section: [Hash],
|
24
|
+
}
|
25
|
+
|
26
|
+
def initialize(config, opts = {})
|
27
|
+
self.config = config
|
28
|
+
self.opts = opts
|
29
|
+
self.templates = {}
|
30
|
+
self.errors = []
|
31
|
+
end
|
32
|
+
|
33
|
+
def validation(&block)
|
34
|
+
self.schema = block
|
35
|
+
return self
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid?
|
39
|
+
raise MissingValidation.new('no validation block specified') if self.schema.nil?
|
40
|
+
self.errors = []
|
41
|
+
instance_eval(&self.schema)
|
42
|
+
return self.errors.empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
def config
|
46
|
+
@config ||= {}
|
47
|
+
end
|
48
|
+
|
49
|
+
def template(name, &block)
|
50
|
+
self.templates[name] = block
|
51
|
+
end
|
52
|
+
|
53
|
+
def section(name, section_opts = {}, &block)
|
54
|
+
if sections(name).nil?
|
55
|
+
return if section_opts.fetch(:optional, false)
|
56
|
+
msg = (name.class == Regexp) ? "no matches in #{heritage}" : "undefined"
|
57
|
+
error heritage(name), msg
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
sections(name).each do |key|
|
62
|
+
# Ensure that the section is valid
|
63
|
+
if !self.valid_type?(self.get(key), :section)
|
64
|
+
error heritage(key), "must be section"
|
65
|
+
next
|
66
|
+
end
|
67
|
+
|
68
|
+
# Set the validation block
|
69
|
+
validation_block = block if block_given?
|
70
|
+
template_name = section_opts.fetch(:template, false)
|
71
|
+
if template_name
|
72
|
+
if !self.templates.key?(template_name)
|
73
|
+
error heritage(key), "undefined template #{template_name}"
|
74
|
+
next
|
75
|
+
end
|
76
|
+
validation_block = self.templates[template_name]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get to work
|
80
|
+
leaf = Ficus.new self.get(key), heritage: heritage(key)
|
81
|
+
leaf.validation(&validation_block) unless validation_block.nil?
|
82
|
+
if leaf.valid?
|
83
|
+
# Update the data for the leaf section as 'optional' can modify the data.
|
84
|
+
self.config[key] = leaf.config
|
85
|
+
else
|
86
|
+
# Update the errors raise by the leaf
|
87
|
+
self.errors += leaf.errors
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def optional(name, default, type = nil)
|
93
|
+
value = exists?(name) ? get(name) : default
|
94
|
+
if !valid_type?(value, type)
|
95
|
+
error heritage(name), "must be #{type}"
|
96
|
+
else
|
97
|
+
self.config[name] = value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def required(name, type = nil)
|
102
|
+
if self.config.key?(name)
|
103
|
+
error heritage(name), "must be #{type}" unless self.valid_type? get(name), type
|
104
|
+
else
|
105
|
+
error heritage(name), "undefined"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
def error(name, message)
|
112
|
+
self.errors << Error.new(name, message)
|
113
|
+
end
|
114
|
+
|
115
|
+
def exists?(key)
|
116
|
+
self.config.key?(key)
|
117
|
+
end
|
118
|
+
|
119
|
+
def get(key)
|
120
|
+
self.config.fetch(key)
|
121
|
+
end
|
122
|
+
|
123
|
+
def heritage(postfix = nil)
|
124
|
+
if postfix.nil?
|
125
|
+
self.opts.fetch(:heritage, [])
|
126
|
+
else
|
127
|
+
self.opts.fetch(:heritage, []) + [postfix.to_s]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def sections(name)
|
132
|
+
# Collect the sections
|
133
|
+
if name == :all
|
134
|
+
self.config.keys
|
135
|
+
elsif name.class == Regexp
|
136
|
+
self.config.select{|k,v| k =~ name}.keys
|
137
|
+
elsif self.config.key?(name)
|
138
|
+
{name => self.config[name]}.keys
|
139
|
+
else
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Ensure value is a valid type.
|
145
|
+
def valid_type?(value, type)
|
146
|
+
if type.nil?
|
147
|
+
return true
|
148
|
+
elsif type.class == Regexp
|
149
|
+
!!(type =~ value.to_s)
|
150
|
+
else
|
151
|
+
TYPES[type].include?(value.class)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class Error
|
156
|
+
attr_accessor :name, :message
|
157
|
+
def initialize(name, message)
|
158
|
+
self.name = name
|
159
|
+
self.message = message
|
160
|
+
end
|
161
|
+
|
162
|
+
def to_s
|
163
|
+
"#{name.join('.')}: #{message}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/spec/ficus_spec.rb
CHANGED
@@ -1,145 +1,269 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'tempfile'
|
3
|
-
|
3
|
+
|
4
4
|
require 'ficus'
|
5
5
|
|
6
6
|
describe Ficus do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
}
|
7
|
+
def config
|
8
|
+
{
|
9
|
+
'general' => {'key1' => 'value1', 'key2' => 'value2', 'key3' =>'value3'},
|
10
|
+
'misc' => {'key4' => 'value4', 'list' => {
|
11
|
+
'item1' => 'value1',
|
12
|
+
'item2' => 'value2'
|
13
|
+
}}
|
15
14
|
}
|
16
15
|
end
|
17
16
|
|
18
|
-
it '
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
it 'will load the config from a string' do
|
18
|
+
ficus = Ficus.load(config)
|
19
|
+
expect(ficus.config).to eq config
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'will load the config from a file' do
|
23
|
+
Tempfile.open('config.yml') do |file|
|
24
|
+
file.write config.to_yaml
|
25
|
+
file.close
|
26
|
+
|
27
|
+
ficus = Ficus.load_file file.path
|
28
|
+
expect(ficus.config).to eq config
|
22
29
|
end
|
23
30
|
end
|
24
31
|
|
25
|
-
it '
|
26
|
-
|
27
|
-
|
28
|
-
section 'not_real', :
|
32
|
+
it 'will validate the config with an optional missing section' do
|
33
|
+
Ficus.load(config).tap do |ficus|
|
34
|
+
ficus.validation do
|
35
|
+
section 'not_real', optional: true
|
29
36
|
end
|
30
|
-
|
37
|
+
expect(ficus.valid?).to eq true
|
31
38
|
end
|
32
39
|
end
|
33
40
|
|
34
|
-
it '
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
end.to raise_error Ficus::ConfigError
|
41
|
+
it 'will fail to validate the config due to a missing section' do
|
42
|
+
Ficus.load(config).tap do |ficus|
|
43
|
+
ficus.validation do
|
44
|
+
section 'not_real'
|
45
|
+
end
|
41
46
|
|
42
|
-
|
47
|
+
expect(ficus.valid?).to eq false
|
48
|
+
expect(ficus.errors.size).to eq 1
|
49
|
+
expect(ficus.errors.first.to_s).to match(/not_real/)
|
43
50
|
end
|
44
51
|
end
|
45
52
|
|
46
|
-
it '
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
required 'fake_param'
|
52
|
-
end
|
53
|
+
it 'will fail to validate the config due to a required parameter' do
|
54
|
+
Ficus.load(config).tap do |ficus|
|
55
|
+
ficus.validation do
|
56
|
+
section 'general', optional: true do
|
57
|
+
required 'fake_param'
|
53
58
|
end
|
54
|
-
end
|
55
|
-
|
59
|
+
end
|
60
|
+
|
61
|
+
expect(ficus.valid?).to eq false
|
62
|
+
expect(ficus.errors.size).to eq 1
|
63
|
+
expect(ficus.errors.first.to_s).to match(/general.fake_param/)
|
56
64
|
end
|
57
65
|
end
|
58
66
|
|
59
|
-
it '
|
60
|
-
|
61
|
-
|
67
|
+
it 'will validate the config and fill in the optional value' do
|
68
|
+
Ficus.load(config).tap do |ficus|
|
69
|
+
ficus.validation do
|
62
70
|
section 'general' do
|
63
71
|
optional 'newparam', 'value2'
|
64
72
|
end
|
65
73
|
end
|
66
|
-
|
67
|
-
|
74
|
+
|
75
|
+
expect(ficus.valid?).to eq true
|
76
|
+
expect(ficus.config['general']['newparam']).to eq 'value2'
|
68
77
|
end
|
69
78
|
end
|
70
79
|
|
71
|
-
it '
|
72
|
-
hash = {
|
73
|
-
:subsections => {
|
74
|
-
:section1 => {:key1=>'value1'},
|
75
|
-
:section2 => {:key1=>'value1'},
|
76
|
-
}
|
77
|
-
}
|
80
|
+
it 'will validate conformant subsections' do
|
81
|
+
hash = {'subsections' => {'section1' => {'key1' => 'value1'}, 'section2' => {'key1' => 'value1'}}}
|
78
82
|
|
79
|
-
|
80
|
-
|
83
|
+
Ficus.load(hash).tap do |ficus|
|
84
|
+
ficus.validation do
|
81
85
|
section 'subsections' do
|
82
86
|
section /^section/ do
|
83
|
-
required
|
87
|
+
required 'key1'
|
84
88
|
end
|
85
89
|
end
|
86
90
|
end
|
87
|
-
|
88
|
-
|
91
|
+
|
92
|
+
expect(ficus.valid?).to eq true
|
93
|
+
expect(ficus.config['subsections']['section1']['key1']).to eq 'value1'
|
94
|
+
expect(ficus.config['subsections']['section2']['key1']).to eq 'value1'
|
89
95
|
end
|
90
96
|
end
|
91
97
|
|
92
|
-
it '
|
93
|
-
hash = {
|
94
|
-
:subsections => {
|
95
|
-
:section1 => {:key1=>'value1'},
|
96
|
-
:section2 => {},
|
97
|
-
}
|
98
|
-
}
|
98
|
+
it 'will fail to validate nonconformant subsections' do
|
99
|
+
hash = {'subsections' => {'section1' => {'key1' => 'value1'}, 'section2' => {}}}
|
99
100
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
section
|
104
|
-
|
105
|
-
required :key1
|
106
|
-
end
|
101
|
+
Ficus.load(hash).tap do |ficus|
|
102
|
+
ficus.validation do
|
103
|
+
section 'subsections' do
|
104
|
+
section /^section/ do
|
105
|
+
required 'key1'
|
107
106
|
end
|
108
107
|
end
|
109
|
-
end
|
108
|
+
end
|
109
|
+
|
110
|
+
expect(ficus.valid?).to eq false
|
111
|
+
expect(ficus.errors.size).to eq 1
|
112
|
+
expect(ficus.errors.first.to_s).to match(/subsections.section2.key1/)
|
110
113
|
end
|
111
114
|
end
|
112
115
|
|
113
|
-
it '
|
114
|
-
hash = {
|
115
|
-
:subsections => {
|
116
|
-
:section1 => {:key1=>'value1'},
|
117
|
-
:section2 => {},
|
118
|
-
}
|
119
|
-
}
|
116
|
+
it 'will fail validate all subsections' do
|
117
|
+
hash = {'subsections' => {'section1' => {'key1' => 'value1'},'section2' => {}}}
|
120
118
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
section
|
125
|
-
|
126
|
-
required :key1
|
127
|
-
end
|
119
|
+
Ficus.load(hash).tap do |ficus|
|
120
|
+
ficus.validation do
|
121
|
+
section 'subsections' do
|
122
|
+
section :all do
|
123
|
+
required 'key1'
|
128
124
|
end
|
129
125
|
end
|
130
|
-
end
|
126
|
+
end
|
127
|
+
|
128
|
+
expect(ficus.valid?).to eq false
|
129
|
+
expect(ficus.errors.size).to eq 1
|
130
|
+
expect(ficus.errors.first.to_s).to match(/subsections.section2.key1/)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'will validate based on a template' do
|
135
|
+
hash = {'section1' => {'key1' => 'value1'}}
|
136
|
+
|
137
|
+
Ficus.load(hash).tap do |ficus|
|
138
|
+
ficus.validation do
|
139
|
+
template 'template1' do
|
140
|
+
required 'key1'
|
141
|
+
end
|
142
|
+
section 'section1', template: 'template1'
|
143
|
+
end
|
144
|
+
expect(ficus.valid?).to eq true
|
145
|
+
expect(ficus.config['section1']['key1']).to eq 'value1'
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'will fail to validate due to failing to conform to a template' do
|
150
|
+
hash = {'section1' => {}}
|
151
|
+
|
152
|
+
Ficus.load(hash).tap do |ficus|
|
153
|
+
ficus.validation do
|
154
|
+
template 'template1' do
|
155
|
+
required 'key1'
|
156
|
+
end
|
157
|
+
section 'section1', :template=>'template1'
|
158
|
+
end
|
159
|
+
expect(ficus.valid?).to eq false
|
160
|
+
expect(ficus.errors.size).to eq 1
|
161
|
+
expect(ficus.errors.first.to_s).to match(/section1.key1/)
|
131
162
|
end
|
132
163
|
end
|
133
164
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
165
|
+
it 'will fail to validate a reference to an invalid template' do
|
166
|
+
hash = {'section1' => {}}
|
167
|
+
|
168
|
+
Ficus.load(hash).tap do |ficus|
|
169
|
+
ficus.validation do
|
170
|
+
section 'section1', template: 'template1'
|
171
|
+
end
|
172
|
+
|
173
|
+
expect(ficus.valid?).to eq false
|
174
|
+
expect(ficus.errors.first.to_s).to match(/undefined template template1/)
|
139
175
|
end
|
140
176
|
end
|
141
177
|
|
142
|
-
|
143
|
-
|
178
|
+
it 'will fail to validate due to a parameter definde as a section' do
|
179
|
+
hash = {'section1' => 'notarealsection'}
|
180
|
+
|
181
|
+
Ficus.load(hash).tap do |ficus|
|
182
|
+
ficus.validation do
|
183
|
+
section 'section1'
|
184
|
+
end
|
185
|
+
|
186
|
+
expect(ficus.valid?).to eq false
|
187
|
+
expect(ficus.errors.size).to eq 1
|
188
|
+
expect(ficus.errors.first.to_s).to match(/must be/i)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'will validate with type checking on optional parameters' do
|
193
|
+
hash = {'number' => 3}
|
194
|
+
|
195
|
+
Ficus.load(hash).tap do |ficus|
|
196
|
+
ficus.validation do
|
197
|
+
optional 'number', 1, :number
|
198
|
+
optional 'bool', true, :boolean
|
199
|
+
optional 'name', 'drew', :string
|
200
|
+
optional 'matcher', 'server5', /server\d/
|
201
|
+
end
|
202
|
+
|
203
|
+
expect(ficus.valid?).to eq true
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'will fail to validate with type checking on optional parameters' do
|
208
|
+
hash = {}
|
209
|
+
|
210
|
+
Ficus.load(hash).tap do |ficus|
|
211
|
+
ficus.validation do
|
212
|
+
optional 'number', false, :number
|
213
|
+
optional 'bool', 'drew', :boolean
|
214
|
+
optional 'name', 123, :string
|
215
|
+
optional 'matcher', 'server5', /zzzserver\d/
|
216
|
+
end
|
217
|
+
|
218
|
+
expect(ficus.valid?).to eq false
|
219
|
+
expect(ficus.errors.size).to eq 4
|
220
|
+
end
|
221
|
+
end
|
222
|
+
it 'will validate with type checking on required parameters' do
|
223
|
+
hash = {'number' => 1, 'bool' => false,'name' => "drew"}
|
224
|
+
|
225
|
+
Ficus.load(hash).tap do |ficus|
|
226
|
+
ficus.validation do
|
227
|
+
required 'number', :number
|
228
|
+
required 'bool', :boolean
|
229
|
+
required 'name', :string
|
230
|
+
end
|
231
|
+
|
232
|
+
expect(ficus.valid?).to eq true
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'will fail to validate due to type checking on required parameters' do
|
237
|
+
hash = {'number' => false, 'bool' => "nope",'name' => 1}
|
238
|
+
|
239
|
+
Ficus.load(hash).tap do |ficus|
|
240
|
+
ficus.validation do
|
241
|
+
required 'number', :number
|
242
|
+
required 'bool', :boolean
|
243
|
+
required 'name', :string
|
244
|
+
end
|
245
|
+
|
246
|
+
expect(ficus.valid?).to eq false
|
247
|
+
expect(ficus.errors.size).to eq 3
|
248
|
+
ficus.errors.each do |error|
|
249
|
+
expect(error.to_s).to match(/must be/)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'will raise an exception if no validation block is specified' do
|
255
|
+
Ficus.load({'key' => 'value'}).tap do |ficus|
|
256
|
+
expect(ficus.schema).to eq nil
|
257
|
+
expect{ficus.valid?}.to raise_error(::Ficus::MissingValidation)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'will allow you to chain the validation method' do
|
262
|
+
ficus = Ficus.load({'some' => 'data'}).validation do
|
263
|
+
required 'some', :string
|
264
|
+
end
|
265
|
+
|
266
|
+
expect(ficus.class).to eq Ficus
|
267
|
+
expect(ficus.schema).not_to be_nil
|
144
268
|
end
|
145
269
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ficus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Drew Fradette
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: recursive-open-struct
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - '>='
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: 0.4.5
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - '>='
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: 0.4.5
|
83
69
|
description: A runtime validation configuration DSL
|
84
70
|
email:
|
85
71
|
- drew.fradette@gmail.com
|
@@ -88,15 +74,21 @@ extensions: []
|
|
88
74
|
extra_rdoc_files: []
|
89
75
|
files:
|
90
76
|
- .gitignore
|
77
|
+
- .travis.yml
|
91
78
|
- Gemfile
|
92
79
|
- LICENSE.txt
|
93
80
|
- README.md
|
94
81
|
- Rakefile
|
82
|
+
- examples/regexp/regexp.rb
|
83
|
+
- examples/regexp/sample.conf
|
84
|
+
- examples/simple/sample.conf
|
85
|
+
- examples/simple/simple.rb
|
86
|
+
- examples/templates/sample.conf
|
87
|
+
- examples/templates/templates.rb
|
88
|
+
- examples/typechecking/sample.conf
|
89
|
+
- examples/typechecking/typechecking.rb
|
95
90
|
- ficus.gemspec
|
96
91
|
- lib/ficus.rb
|
97
|
-
- lib/ficus/dsl.rb
|
98
|
-
- lib/ficus/exceptions.rb
|
99
|
-
- lib/ficus/loader.rb
|
100
92
|
- spec/ficus_spec.rb
|
101
93
|
- spec/spec_helper.rb
|
102
94
|
homepage: https://github.com/drewfradette/ruby-ficus
|
data/lib/ficus/dsl.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
class Ficus < RecursiveOpenStruct
|
2
|
-
def section(name, args = {}, &block)
|
3
|
-
sections(name).each do |s|
|
4
|
-
s.parent = self.parent ? "#{self.parent}.#{name}" : name
|
5
|
-
s.instance_eval(&block) if block_given?
|
6
|
-
end
|
7
|
-
rescue NoSection
|
8
|
-
if args[:optional] == true
|
9
|
-
Ficus.warning("Section #{name} is not defined")
|
10
|
-
else
|
11
|
-
Ficus.error("Section #{name} is not defined")
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def sections(name)
|
16
|
-
if name == :all
|
17
|
-
sections(/.*/)
|
18
|
-
elsif name.is_a? Regexp
|
19
|
-
matches = self.marshal_dump.keys
|
20
|
-
matches.map { |k| self.send(k) unless k == :parent }.compact!
|
21
|
-
else
|
22
|
-
s = self.send(name)
|
23
|
-
raise NoSection.new if s.nil?
|
24
|
-
[s]
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def optional(name, default)
|
29
|
-
self.send("#{name}=", default) if self.send(name).nil?
|
30
|
-
end
|
31
|
-
|
32
|
-
def required(name)
|
33
|
-
prefix = self.parent ? "#{self.parent}." : nil
|
34
|
-
Ficus.error "Option #{prefix}#{name} is not defined" if self.send(name).nil?
|
35
|
-
end
|
36
|
-
end
|
data/lib/ficus/exceptions.rb
DELETED
data/lib/ficus/loader.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
class Ficus < RecursiveOpenStruct
|
2
|
-
class << self
|
3
|
-
attr_accessor :verbose, :logger, :errors, :warnings
|
4
|
-
|
5
|
-
# Load the configuration file and validate.
|
6
|
-
def load(file, &block)
|
7
|
-
@errors, @warnings, @config = [], [], nil
|
8
|
-
config(file).instance_eval(&block) if block_given?
|
9
|
-
|
10
|
-
warnings.each { |e| logger.warn e }
|
11
|
-
errors.each { |e| logger.error e }
|
12
|
-
raise ConfigError.new('Unable to start due to invalid settings') if errors.size > 0
|
13
|
-
config(file)
|
14
|
-
end
|
15
|
-
|
16
|
-
def error(msg)
|
17
|
-
@errors << msg
|
18
|
-
end
|
19
|
-
|
20
|
-
def warning(msg)
|
21
|
-
@warnings << msg
|
22
|
-
end
|
23
|
-
|
24
|
-
protected
|
25
|
-
def config(file)
|
26
|
-
if @config.nil?
|
27
|
-
yaml = YAML.load File.read(file)
|
28
|
-
@config = Ficus.new(yaml, :recurse_over_arrays => true)
|
29
|
-
end
|
30
|
-
@config
|
31
|
-
end
|
32
|
-
|
33
|
-
def logger
|
34
|
-
@logger ||= Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|