ficus 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/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +76 -0
- data/Rakefile +1 -0
- data/ficus.gemspec +26 -0
- data/lib/ficus.rb +47 -0
- data/lib/recursive-open-struct.rb +93 -0
- data/spec/ficus_spec.rb +81 -0
- data/spec/spec_helper.rb +17 -0
- metadata +128 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 afradette
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Ficus
|
2
|
+
|
3
|
+
A simple YAML configuration DSL that does runtime validation.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'ficus'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install ficus
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Here is an example YAML config file:
|
22
|
+
|
23
|
+
```yaml
|
24
|
+
# config.yml
|
25
|
+
---
|
26
|
+
section_1:
|
27
|
+
key1: value1
|
28
|
+
key2: value2
|
29
|
+
|
30
|
+
section_2:
|
31
|
+
key4: value4
|
32
|
+
|
33
|
+
optional_section:
|
34
|
+
key5: value5
|
35
|
+
```
|
36
|
+
|
37
|
+
And now we can use Ficus to load the config and validate it at the same time.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
require 'ficus'
|
41
|
+
|
42
|
+
config = Ficus.load 'config.yml' do
|
43
|
+
section 'section_1' do
|
44
|
+
required 'key1'
|
45
|
+
required 'key2'
|
46
|
+
|
47
|
+
optional 'key3', 'value3'
|
48
|
+
end
|
49
|
+
|
50
|
+
section 'section_2' do
|
51
|
+
required 'key4'
|
52
|
+
end
|
53
|
+
|
54
|
+
section 'optional_section', :optional => true do
|
55
|
+
required 'key5'
|
56
|
+
end
|
57
|
+
|
58
|
+
section 'not_defined', :optional => true do
|
59
|
+
require 'key6'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
config.section_1.key1 # value1
|
64
|
+
config.section_1.key2 # value2
|
65
|
+
config.section_1.key3 # value3
|
66
|
+
config.section_2.key4 # value4
|
67
|
+
config.not_defined # nil
|
68
|
+
```
|
69
|
+
|
70
|
+
## Contributing
|
71
|
+
|
72
|
+
1. Fork it
|
73
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
74
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
75
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
76
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/ficus.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "ficus"
|
7
|
+
spec.version = '0.0.1'
|
8
|
+
spec.authors = ["Drew Fradette"]
|
9
|
+
spec.email = ["drew.fradette@gmail.com"]
|
10
|
+
spec.description = 'A runtime validation configuration DSL'
|
11
|
+
spec.summary = ''
|
12
|
+
spec.homepage = "https://github.com/drewfradette/ficus"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency 'rspec'
|
23
|
+
spec.add_development_dependency 'simplecov' if RUBY_VERSION >= '1.9'
|
24
|
+
|
25
|
+
#spec.add_dependency 'recursive-open-struct'
|
26
|
+
end
|
data/lib/ficus.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'recursive-open-struct'
|
3
|
+
|
4
|
+
class Ficus < RecursiveOpenStruct
|
5
|
+
class ConfigError < StandardError; end
|
6
|
+
class << self
|
7
|
+
attr_accessor :log, :verbose
|
8
|
+
|
9
|
+
def load(file, &block)
|
10
|
+
@log = []
|
11
|
+
yaml = YAML.load File.read(file)
|
12
|
+
config = Ficus.new(yaml, :recurse_over_arrays => true)
|
13
|
+
config.instance_eval(&block) if block_given?
|
14
|
+
|
15
|
+
errors = log.select{|v| v =~ /^\[ERR\]/}
|
16
|
+
if errors.size > 0
|
17
|
+
log.each{|v| puts v} if ENV['DEBUG']
|
18
|
+
raise ConfigError.new("Unable to start due to invalid settings")
|
19
|
+
end
|
20
|
+
config
|
21
|
+
end
|
22
|
+
|
23
|
+
def log(log = {})
|
24
|
+
@log << log
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def section(name, args = {}, &block)
|
29
|
+
section = self.send(name)
|
30
|
+
if section.nil?
|
31
|
+
level = args[:optional] ? 'WARN' : 'ERR'
|
32
|
+
Ficus.log "[#{level}] Section #{name} is not defined"
|
33
|
+
else
|
34
|
+
section.parent = self.parent ? "#{self.parent}.#{name}" : name
|
35
|
+
section.instance_eval &block if block_given?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def optional(name, default)
|
40
|
+
self.send("#{name}=", default) if self.send(name).nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def required(name)
|
44
|
+
prefix = self.parent ? "#{self.parent}." : nil
|
45
|
+
Ficus.log "[ERR] Option #{prefix}#{name} is not defined" if self.send(name).nil?
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
class RecursiveOpenStruct < OpenStruct
|
4
|
+
VERSION = "0.4.3"
|
5
|
+
|
6
|
+
def initialize(h=nil, args={})
|
7
|
+
@recurse_over_arrays = args.fetch(:recurse_over_arrays,false)
|
8
|
+
super(h)
|
9
|
+
@sub_elements = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_h
|
13
|
+
@table.dup.update(@sub_elements) do |k, oldval, newval|
|
14
|
+
if newval.kind_of?(self.class)
|
15
|
+
newval.to_h
|
16
|
+
elsif newval.kind_of?(Array)
|
17
|
+
newval.map { |a| a.kind_of?(self.class) ? a.to_h : a }
|
18
|
+
else
|
19
|
+
raise "Cached value of unsupported type: #{newval.inspect}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def new_ostruct_member(name)
|
25
|
+
name = name.to_sym
|
26
|
+
unless self.respond_to?(name)
|
27
|
+
class << self; self; end.class_eval do
|
28
|
+
define_method(name) do
|
29
|
+
v = @table[name]
|
30
|
+
if v.is_a?(Hash)
|
31
|
+
@sub_elements[name] ||= self.class.new(v, :recurse_over_arrays => @recurse_over_arrays)
|
32
|
+
elsif v.is_a?(Array) and @recurse_over_arrays
|
33
|
+
@sub_elements[name] ||= recurse_over_array v
|
34
|
+
else
|
35
|
+
v
|
36
|
+
end
|
37
|
+
end
|
38
|
+
define_method("#{name}=") { |x| modifiable[name] = x }
|
39
|
+
define_method("#{name}_as_a_hash") { @table[name] }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
name
|
43
|
+
end
|
44
|
+
|
45
|
+
def recurse_over_array array
|
46
|
+
array.map do |a|
|
47
|
+
if a.is_a? Hash
|
48
|
+
self.class.new(a, :recurse_over_arrays => true)
|
49
|
+
elsif a.is_a? Array
|
50
|
+
recurse_over_array a
|
51
|
+
else
|
52
|
+
a
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def debug_inspect(io = STDOUT, indent_level = 0, recursion_limit = 12)
|
58
|
+
display_recursive_open_struct(io, @table, indent_level, recursion_limit)
|
59
|
+
end
|
60
|
+
|
61
|
+
def display_recursive_open_struct(io, ostrct_or_hash, indent_level, recursion_limit)
|
62
|
+
|
63
|
+
if recursion_limit <= 0 then
|
64
|
+
# protection against recursive structure (like in the tests)
|
65
|
+
io.puts ' '*indent_level + '(recursion limit reached)'
|
66
|
+
else
|
67
|
+
#puts ostrct_or_hash.inspect
|
68
|
+
if ostrct_or_hash.is_a?(self.class) then
|
69
|
+
ostrct_or_hash = ostrct_or_hash.marshal_dump
|
70
|
+
end
|
71
|
+
|
72
|
+
# We'll display the key values like this : key = value
|
73
|
+
# to align display, we look for the maximum key length of the data that will be displayed
|
74
|
+
# (everything except hashes)
|
75
|
+
data_indent = ostrct_or_hash \
|
76
|
+
.reject { |k, v| v.is_a?(self.class) || v.is_a?(Hash) } \
|
77
|
+
.max {|a,b| a[0].to_s.length <=> b[0].to_s.length}[0].to_s.length
|
78
|
+
# puts "max length = #{data_indent}"
|
79
|
+
|
80
|
+
ostrct_or_hash.each do |key, value|
|
81
|
+
if (value.is_a?(self.class) || value.is_a?(Hash)) then
|
82
|
+
io.puts ' '*indent_level + key.to_s + '.'
|
83
|
+
display_recursive_open_struct(io, value, indent_level + 1, recursion_limit - 1)
|
84
|
+
else
|
85
|
+
io.puts ' '*indent_level + key.to_s + ' '*(data_indent - key.to_s.length) + ' = ' + value.inspect
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/spec/ficus_spec.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require 'ficus'
|
7
|
+
|
8
|
+
describe Ficus do
|
9
|
+
before :each do
|
10
|
+
@config = {
|
11
|
+
:general => {:key1=>'value1',:key2=>'value2',:key3=>'value3'},
|
12
|
+
:misc => {
|
13
|
+
:key4 => 'value4',
|
14
|
+
:list => {:item1 => 'value1', :item2 => 'value2'}
|
15
|
+
}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should load the config without any validation' do
|
20
|
+
config_file do |config|
|
21
|
+
config = Ficus.load(config)
|
22
|
+
|
23
|
+
config.to_h.should eq @config
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should load the config with a warning about a missing section' do
|
28
|
+
config_file do |config|
|
29
|
+
config = Ficus.load(config) do
|
30
|
+
section 'not_real', :optional => true
|
31
|
+
end
|
32
|
+
Ficus.log.count{|v| v =~ /^\[WARN\]/}.should eq 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should load the config with a error about a missing section' do
|
37
|
+
config_file do |config|
|
38
|
+
expect {
|
39
|
+
config = Ficus.load(config) do
|
40
|
+
section 'not_real'
|
41
|
+
end
|
42
|
+
}.to raise_error Ficus::ConfigError
|
43
|
+
|
44
|
+
Ficus.log.count{|v| v =~ /^\[ERR\]/}.should eq 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should load the config but fail to validate' do
|
49
|
+
config_file do |config|
|
50
|
+
expect {
|
51
|
+
config = Ficus.load(config) do
|
52
|
+
section 'general', :optional => true do
|
53
|
+
required 'fake_param'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
}.to raise_error Ficus::ConfigError
|
57
|
+
Ficus.log.count{|v| v =~ /^\[ERR\]/}.should eq 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should load the config but fill in the optional value' do
|
62
|
+
config_file do |config|
|
63
|
+
config = Ficus.load(config) do
|
64
|
+
section 'general' do
|
65
|
+
optional 'newparam', 'value2'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
@config.fetch(:general).fetch('newparam', nil).should eq nil
|
69
|
+
config.general.newparam.should eq 'value2'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def config_file
|
74
|
+
Tempfile.open('config.yml') do |config|
|
75
|
+
config.write @config.to_yaml
|
76
|
+
config.close
|
77
|
+
|
78
|
+
yield config.path
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), '..')
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
if RUBY_VERSION.to_f >= 1.9
|
6
|
+
puts 'Enabling coverage'
|
7
|
+
require 'simplecov'
|
8
|
+
SimpleCov.add_filter 'vendor'
|
9
|
+
SimpleCov.add_filter 'spec'
|
10
|
+
SimpleCov.start
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
require 'lib/ficus'
|
15
|
+
end
|
16
|
+
|
17
|
+
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ficus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Drew Fradette
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: simplecov
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: A runtime validation configuration DSL
|
79
|
+
email:
|
80
|
+
- drew.fradette@gmail.com
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- .gitignore
|
86
|
+
- Gemfile
|
87
|
+
- LICENSE.txt
|
88
|
+
- README.md
|
89
|
+
- Rakefile
|
90
|
+
- ficus.gemspec
|
91
|
+
- lib/ficus.rb
|
92
|
+
- lib/recursive-open-struct.rb
|
93
|
+
- spec/ficus_spec.rb
|
94
|
+
- spec/spec_helper.rb
|
95
|
+
homepage: https://github.com/drewfradette/ficus
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
hash: 996828060778593471
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
hash: 996828060778593471
|
120
|
+
requirements: []
|
121
|
+
rubyforge_project:
|
122
|
+
rubygems_version: 1.8.25
|
123
|
+
signing_key:
|
124
|
+
specification_version: 3
|
125
|
+
summary: ''
|
126
|
+
test_files:
|
127
|
+
- spec/ficus_spec.rb
|
128
|
+
- spec/spec_helper.rb
|