frise 0.1.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 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: []