frise 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 41db8000684dfb7ee23cc7f26a790a57bb38ad76
4
+ data.tar.gz: 782cf2ff2d3866a754950fd8edd3735a1bb1f425
5
+ SHA512:
6
+ metadata.gz: c37c19e1f9edbad815b30d52f867d4344fd3b3da8a376e30236814a47ddd579b889459b1a702190fe5d1c1d2b99afdc3abef685f54a062616ea632872640a5e2
7
+ data.tar.gz: c9c87e93dbd3b361a5632431ac8f83650749c7d1de090c3397299b403cda7e1a986767356d8795dc3cf037f9461227a8287dd3870df79a573031ee5928d31931
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ *.iml
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ Metrics:
2
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1
4
+ - 2.2
5
+ - 2.3
6
+ - 2.4
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frise.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2017 ShiftForward, S.A. [http://www.shiftforward.eu]
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
4
+ use this file except in compliance with the License. You may obtain a copy of
5
+ the License at
6
+
7
+ [http://www.apache.org/licenses/LICENSE-2.0]
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+ License for the specific language governing permissions and limitations under
13
+ the License.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # Frise
2
+ [![Build Status](https://travis-ci.org/ShiftForward/frise.svg?branch=master)](https://travis-ci.org/ShiftForward/frise)
3
+ [![Coverage Status](https://coveralls.io/repos/github/ShiftForward/frise/badge.svg?branch=master)](https://coveralls.io/github/ShiftForward/frise?branch=master)
4
+
5
+ Frise is a library for loading configuration files as native Ruby structures. Besides reading and
6
+ parsing the files themselves, it also:
7
+
8
+ - Completes it with default values specified in another file or set of files;
9
+ - Interprets [Liquid](https://shopify.github.io/liquid) templates in configs and defaults;
10
+ - Validates the loaded config according to a schema file or set of files.
11
+
12
+ ## Install
13
+
14
+ ```
15
+ gem install frise
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### Basic configs
21
+
22
+ The simplest example would be to load [a simple configuration](example/config.yml) from a file:
23
+
24
+ ```ruby
25
+ require 'frise'
26
+ require 'pp'
27
+
28
+ loader = Frise::Loader.new
29
+ loader.load('example/config.yml')
30
+ # => {"movies"=>
31
+ # [{"title"=>"The Shawshank Redemption",
32
+ # "year"=>1994,
33
+ # "categories"=>["Crime", "Drama"],
34
+ # "rating"=>9.3},
35
+ # {"title"=>"The Godfather",
36
+ # "year"=>1972,
37
+ # "director"=>"Francis Ford Coppola",
38
+ # "categories"=>["Crime", "Drama"],
39
+ # "rating"=>9.2}]}
40
+ ```
41
+
42
+ Currently Frise only supports YAML files, but it may support JSON and other formats in the future.
43
+
44
+ ### Default values
45
+
46
+ By defining directories where the files with default values can be found (in this example,
47
+ [example/_defaults](example/_defaults)), Frise can handle its application internally on load time:
48
+
49
+ ```ruby
50
+ loader = Frise::Loader.new(defaults_load_paths: ['example/_defaults'])
51
+ loader.load('example/config.yml')
52
+ # => {"movies"=>
53
+ # [{"title"=>"The Shawshank Redemption",
54
+ # "year"=>1994,
55
+ # "categories"=>["Crime", "Drama"],
56
+ # "rating"=>9.3,
57
+ # "director"=>"N/A"},
58
+ # {"title"=>"The Godfather",
59
+ # "year"=>1972,
60
+ # "director"=>"Francis Ford Coppola",
61
+ # "categories"=>["Crime", "Drama"],
62
+ # "rating"=>9.2}],
63
+ # "ui"=>
64
+ # {"default_movie"=>"The Shawshank Redemption",
65
+ # "filter_from"=>1972,
66
+ # "filter_to"=>1994}}
67
+ ```
68
+
69
+ Note that files with default values follow exactly the same structure of the config file itself.
70
+ Special values such as `$all` allow users to define default values for all elements of an object or
71
+ array. Liquid templates are also used to define some defaults as a function of other objects of the
72
+ config.
73
+
74
+ ### Schemas
75
+
76
+ Additionally, configuration files can also be validated against a schema. By specifying
77
+ `schema_load_paths`, users can provide directories where schema files such as
78
+ [example/_schemas/config.yml](example/_schemas/config.yml) can be found:
79
+
80
+ ```ruby
81
+ loader = Frise::Loader.new(
82
+ schema_load_paths: ['example/_schemas'],
83
+ defaults_load_paths: ['example/_defaults'])
84
+
85
+ loader.load('example/config.yml')
86
+ # {"movies"=>
87
+ # [{"title"=>"The Shawshank Redemption",
88
+ # "year"=>1994,
89
+ # "categories"=>["Crime", "Drama"],
90
+ # "rating"=>9.3,
91
+ # "director"=>"N/A"},
92
+ # {"title"=>"The Godfather",
93
+ # "year"=>1972,
94
+ # "director"=>"Francis Ford Coppola",
95
+ # "categories"=>["Crime", "Drama"],
96
+ # "rating"=>9.2}],
97
+ # "ui"=>
98
+ # {"default_movie"=>"The Shawshank Redemption",
99
+ # "filter_from"=>1972,
100
+ # "filter_to"=>1994}}
101
+ ```
102
+
103
+ If this config is loaded without the defaults instead, there are now required values that are
104
+ missing and Frise by default prints a summary of the errors and terminates the program:
105
+
106
+
107
+ ```ruby
108
+ loader = Frise::Loader.new(schema_load_paths: ['example/_schemas'])
109
+
110
+ loader.load('example/config.yml')
111
+ # 2 config error(s) found:
112
+ # - At movies.0.director: missing required value
113
+ # - At ui: missing required value
114
+ ```
115
+
116
+ Once more, the structure of the schema mimics the structure of the config itself, making it easy to
117
+ write schemas and to create a config scaffold from its schema later.
118
+
119
+ Users can check a whole range of properties in config values besides their type: optional values,
120
+ hashes with validated keys, hashes with unknown keys and even custom validations are also supported.
121
+ The [specification](spec/frise/validator_spec.rb) of the validator provides various examples of
122
+ schemas and describes the behavior of each value (more documentation will be written soon).
123
+
124
+ ### Other features
125
+
126
+ Users can also define custom code to be run before defaults and schemas are applied and can even do
127
+ each of the steps separately. Additionally, defaults and schemas can be loaded at a specific path
128
+ inside an existing Ruby object. The [Loader](lib/frise/loader.rb) class provides high-level methods
129
+ to access those features, while lower-level functionality can be accessed through
130
+ [Parser](lib/frise/parser.rb), [DefaultsLoader](lib/frise/defaults_loader.rb) and
131
+ [Validator](lib/frise/validator.rb).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ def load_if_available(req_path)
4
+ require req_path
5
+ yield
6
+ rescue LoadError
7
+ false # req not available
8
+ end
9
+
10
+ load_if_available('rspec/core/rake_task') { RSpec::Core::RakeTask.new(:spec) }
11
+ load_if_available('rubocop/rake_task') { RuboCop::RakeTask.new(:rubocop) }
12
+
13
+ task default: %i[rubocop spec]
data/frise.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'frise/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'frise'
9
+ spec.version = Frise::VERSION
10
+ spec.authors = ['ShiftForward']
11
+ spec.email = ['info@shiftforward.eu']
12
+
13
+ spec.summary = 'Ruby config library with schema validation, default values and templating'
14
+ spec.homepage = 'https://github.com/ShiftForward/frise'
15
+ spec.license = 'Apache-2.0'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features|example)/})
19
+ end
20
+ spec.require_paths = ['lib']
21
+ spec.required_ruby_version = '>= 2.1.0'
22
+
23
+ spec.add_dependency 'liquid', '~> 3.0'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.14'
26
+ spec.add_development_dependency 'coveralls', '~> 0.8.21'
27
+ spec.add_development_dependency 'simplecov', '~> 0.14.1'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ spec.add_development_dependency 'rspec', '~> 3.4'
30
+ spec.add_development_dependency 'rubocop', '~> 0.49.1'
31
+ end
@@ -0,0 +1,69 @@
1
+ require 'frise/parser'
2
+
3
+ module Frise
4
+ # Provides the logic for merging config defaults into pre-loaded configuration objects.
5
+ #
6
+ # The merge_defaults and merge_defaults_at entrypoint methods provide ways to read files with
7
+ # defaults and apply them to configuration objects.
8
+ module DefaultsLoader
9
+ class << self
10
+ def widened_class(obj)
11
+ class_name = obj.class.to_s
12
+ return 'Boolean' if %w[TrueClass FalseClass].include? class_name
13
+ return 'Integer' if %w[Fixnum Bignum].include? class_name
14
+ class_name
15
+ end
16
+
17
+ def merge_defaults_obj(config, defaults)
18
+ if defaults.nil?
19
+ config
20
+
21
+ elsif config.nil?
22
+ if defaults.class != Hash then defaults
23
+ elsif defaults['$optional'] then nil
24
+ else merge_defaults_obj({}, defaults)
25
+ end
26
+
27
+ elsif defaults.class == Array && config.class == Array
28
+ defaults + config
29
+
30
+ elsif defaults.class == Hash && defaults['$all'] && config.class == Array
31
+ config.map { |elem| merge_defaults_obj(elem, defaults['$all']) }
32
+
33
+ elsif defaults.class == Hash && config.class == Hash
34
+ new_config = {}
35
+ (config.keys + defaults.keys).uniq.each do |key|
36
+ next if key.start_with?('$')
37
+ new_config[key] = config[key]
38
+ new_config[key] = merge_defaults_obj(new_config[key], defaults[key]) if defaults.key?(key)
39
+ new_config[key] = merge_defaults_obj(new_config[key], defaults['$all']) unless new_config[key].nil?
40
+ new_config.delete(key) if new_config[key].nil?
41
+ end
42
+ new_config
43
+
44
+ elsif widened_class(defaults) != widened_class(config)
45
+ raise "Cannot merge config #{config.inspect} (#{widened_class(config)}) " \
46
+ "with default #{defaults.inspect} (#{widened_class(defaults)})"
47
+
48
+ else
49
+ config
50
+ end
51
+ end
52
+
53
+ def merge_defaults_obj_at(config, at_path, defaults)
54
+ at_path.reverse.each { |key| defaults = { key => defaults } }
55
+ merge_defaults_obj(config, defaults)
56
+ end
57
+
58
+ def merge_defaults(config, defaults_file, symbol_table = config)
59
+ defaults = Parser.parse(defaults_file, symbol_table)
60
+ merge_defaults_obj(config, defaults)
61
+ end
62
+
63
+ def merge_defaults_at(config, at_path, defaults_file, symbol_table = config)
64
+ defaults = Parser.parse(defaults_file, symbol_table)
65
+ merge_defaults_obj_at(config, at_path, defaults)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,60 @@
1
+ require 'frise/defaults_loader'
2
+ require 'frise/parser'
3
+ require 'frise/validator'
4
+
5
+ module Frise
6
+ # The entrypoint for loading configs from files according to the conventions defined for Frise.
7
+ #
8
+ # The load method loads a configuration file, merges it with the applicable defaults and validates
9
+ # its schema. Other methods in Loader perform only parts of the process.
10
+ class Loader
11
+ def initialize(schema_load_paths: [], defaults_load_paths: [], pre_loaders: [], validators: nil)
12
+ @schema_load_paths = schema_load_paths
13
+ @defaults_load_paths = defaults_load_paths
14
+ @pre_loaders = pre_loaders
15
+ @validators = validators
16
+ end
17
+
18
+ def load(config_file, exit_on_fail = true, symbol_table = {})
19
+ config = Parser.parse(config_file, symbol_table)
20
+ config_name = File.basename(config_file)
21
+
22
+ @pre_loaders.each do |pre_loader|
23
+ config = pre_loader.call(config)
24
+ end
25
+
26
+ config = merge_defaults(config, config_name, symbol_table)
27
+ validate(config, config_name, exit_on_fail)
28
+ end
29
+
30
+ def merge_defaults(config, defaults_name, symbol_table = {})
31
+ merge_defaults_at(config, [], defaults_name, symbol_table)
32
+ end
33
+
34
+ def merge_defaults_at(config, at_path, defaults_name, symbol_table = {})
35
+ @defaults_load_paths.map do |defaults_dir|
36
+ defaults_file = File.join(defaults_dir, defaults_name)
37
+ config = DefaultsLoader.merge_defaults_at(
38
+ config, at_path, defaults_file, symbol_table.merge(config)
39
+ )
40
+ end
41
+ config
42
+ end
43
+
44
+ def validate(config, schema_name, exit_on_fail = true)
45
+ validate_at(config, [], schema_name, exit_on_fail)
46
+ end
47
+
48
+ def validate_at(config, at_path, schema_name, exit_on_fail = true)
49
+ @schema_load_paths.map do |schema_dir|
50
+ schema_file = File.join(schema_dir, schema_name)
51
+ errors = Validator.validate_at(config, at_path, schema_file,
52
+ validators: @validators,
53
+ print: exit_on_fail,
54
+ fatal: exit_on_fail)
55
+ return nil if errors.any?
56
+ end
57
+ config
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,17 @@
1
+ require 'liquid'
2
+ require 'yaml'
3
+
4
+ module Frise
5
+ # Provides a static parse method for reading a config from a YAML file applying the required
6
+ # transformations.
7
+ module Parser
8
+ class << self
9
+ def parse(file, symbol_table = nil)
10
+ return {} unless File.file? file
11
+ content = File.open(file).read
12
+ content = Liquid::Template.parse(content).render symbol_table if symbol_table
13
+ YAML.safe_load(content, [], [], true) || {}
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'frise/parser'
4
+ require 'set'
5
+
6
+ module Frise
7
+ # Checks if a pre-loaded config object conforms to a schema file.
8
+ #
9
+ # The validate and validate_at static methods read schema files and validates config objects
10
+ # against the parsed schema. They can optionally be initialized with a set of user-defined
11
+ # validators that can be used in the schema files for custom validations.
12
+ class Validator
13
+ attr_reader :errors
14
+
15
+ def initialize(root, validators = nil)
16
+ @root = root
17
+ @validators = validators
18
+ @errors = []
19
+ end
20
+
21
+ def widened_class(obj)
22
+ class_name = obj.class.to_s
23
+ return 'Boolean' if %w[TrueClass FalseClass].include? class_name
24
+ return 'Integer' if %w[Fixnum Bignum].include? class_name
25
+ class_name
26
+ end
27
+
28
+ def add_validation_error(path, msg)
29
+ logged_path = path.empty? ? '<root>' : path[1..-1]
30
+ @errors << "At #{logged_path}: #{msg}"
31
+ end
32
+
33
+ def get_full_schema(schema)
34
+ case schema
35
+ when Hash then schema
36
+ when Symbol then { type: 'Object', validate: schema }
37
+ when Array then
38
+ if schema.size == 1
39
+ { type: 'Array', all: schema[0] }
40
+ else
41
+ (raise "Invalid schema: #{schema.inspect}")
42
+ end
43
+ when String then
44
+ if schema.end_with?('?')
45
+ { type: schema[0..-2], optional: true }
46
+ else
47
+ { type: schema }
48
+ end
49
+ else raise "Invalid schema: #{schema.inspect}"
50
+ end
51
+ end
52
+
53
+ def validate_optional(full_schema, obj, path)
54
+ if obj.nil?
55
+ add_validation_error(path, 'missing required value') unless full_schema[:optional]
56
+ return false
57
+ end
58
+ true
59
+ end
60
+
61
+ def get_expected_types(full_schema)
62
+ type_key = full_schema.fetch(:type, 'Hash')
63
+ allowed_types = %w[Hash Array String Integer Float Object]
64
+ return [Object.const_get(type_key)] if allowed_types.include?(type_key)
65
+ return [TrueClass, FalseClass] if type_key == 'Boolean'
66
+ raise "Invalid expected type in schema: #{type_key}"
67
+ end
68
+
69
+ def validate_type(full_schema, obj, path)
70
+ expected_types = get_expected_types(full_schema)
71
+ unless expected_types.any? { |typ| obj.is_a?(typ) }
72
+ type_key = full_schema.fetch(:type, 'Hash')
73
+ add_validation_error(path, "expected #{type_key}, found #{widened_class(obj)}")
74
+ return false
75
+ end
76
+ true
77
+ end
78
+
79
+ def validate_custom(full_schema, obj, path)
80
+ if full_schema[:validate]
81
+ begin
82
+ @validators.method(full_schema[:validate]).call(@root, obj)
83
+ rescue StandardError => ex
84
+ add_validation_error(path, ex.message)
85
+ end
86
+ end
87
+ true
88
+ end
89
+
90
+ def validate_spec_keys(full_schema, obj, path, processed_keys)
91
+ full_schema.each do |spec_key, spec_value|
92
+ next if spec_key.is_a?(Symbol)
93
+ validate_object("#{path}.#{spec_key}", obj[spec_key], spec_value)
94
+ processed_keys << spec_key
95
+ end
96
+ true
97
+ end
98
+
99
+ def validate_remaining_keys(full_schema, obj, path, processed_keys)
100
+ expected_types = get_expected_types(full_schema)
101
+ if expected_types.size == 1 && expected_types[0].ancestors.member?(Enumerable)
102
+ hash = obj.is_a?(Hash) ? obj : Hash[obj.map.with_index { |x, i| [i, x] }]
103
+ hash.each do |key, value|
104
+ if full_schema[:all_keys] && !key.is_a?(Symbol)
105
+ validate_object(path, key, full_schema[:all_keys])
106
+ end
107
+
108
+ next if processed_keys.member? key
109
+ if full_schema[:all]
110
+ validate_object("#{path}.#{key}", value, full_schema[:all])
111
+ elsif !full_schema[:allow_unknown_keys]
112
+ add_validation_error(path, "unknown key: #{key}")
113
+ end
114
+ end
115
+ end
116
+ true
117
+ end
118
+
119
+ def validate_object(path, obj, schema)
120
+ full_schema = get_full_schema(schema)
121
+
122
+ return unless validate_optional(full_schema, obj, path)
123
+ return unless validate_type(full_schema, obj, path)
124
+ return unless validate_custom(full_schema, obj, path)
125
+
126
+ processed_keys = Set.new
127
+ return unless validate_spec_keys(full_schema, obj, path, processed_keys)
128
+ validate_remaining_keys(full_schema, obj, path, processed_keys)
129
+ end
130
+
131
+ def self.parse_symbols(obj)
132
+ case obj
133
+ when Array then obj.map { |e| parse_symbols(e) }
134
+ when Hash then Hash[obj.map { |k, v| [parse_symbols(k), parse_symbols(v)] }]
135
+ when String then obj.start_with?('$') ? obj[1..-1].to_sym : obj
136
+ else obj
137
+ end
138
+ end
139
+
140
+ def self.validate_obj(config, schema, options = {})
141
+ validator = Validator.new(config, options[:validators])
142
+ validator.validate_object('', config, schema)
143
+
144
+ if validator.errors.any?
145
+ if options[:print]
146
+ puts "#{validator.errors.length} config error(s) found:"
147
+ validator.errors.each do |error|
148
+ puts " - #{error}"
149
+ end
150
+ end
151
+
152
+ exit 1 if options[:fatal]
153
+ raise ValidationError.new(validator.errors), 'Invalid configuration' if options[:raise_error]
154
+ end
155
+ validator.errors
156
+ end
157
+
158
+ def self.validate(config, schema_file, options = {})
159
+ schema = parse_symbols(Parser.parse(schema_file))
160
+ validate_obj(config, schema, options)
161
+ end
162
+
163
+ def self.validate_at(config, at_path, schema_file, options = {})
164
+ schema = parse_symbols(Parser.parse(schema_file))
165
+ at_path.reverse.each { |key| schema = { key => schema, :allow_unknown_keys => true } }
166
+ validate_obj(config, schema, options)
167
+ end
168
+ end
169
+
170
+ # An error resulting of the validation of a config. The list of errors can be inspected using the
171
+ # errors method.
172
+ class ValidationError < StandardError
173
+ attr_reader :errors
174
+
175
+ def initialize(errors)
176
+ @errors = errors
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,3 @@
1
+ module Frise
2
+ VERSION = '0.1.0'.freeze
3
+ end
data/lib/frise.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'frise/defaults_loader'
2
+ require 'frise/loader'
3
+ require 'frise/parser'
4
+ require 'frise/validator'
5
+ require 'frise/version'
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: frise
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ShiftForward
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: liquid
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.14'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.14'
41
+ - !ruby/object:Gem::Dependency
42
+ name: coveralls
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.8.21
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.21
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.14.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.14.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.49.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.49.1
111
+ description:
112
+ email:
113
+ - info@shiftforward.eu
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".rubocop.yml"
121
+ - ".travis.yml"
122
+ - Gemfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - frise.gemspec
127
+ - lib/frise.rb
128
+ - lib/frise/defaults_loader.rb
129
+ - lib/frise/loader.rb
130
+ - lib/frise/parser.rb
131
+ - lib/frise/validator.rb
132
+ - lib/frise/version.rb
133
+ homepage: https://github.com/ShiftForward/frise
134
+ licenses:
135
+ - Apache-2.0
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 2.1.0
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 2.6.11
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: Ruby config library with schema validation, default values and templating
157
+ test_files: []