config_hound 1.0.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: 889819e5d375efbd4ba6f012a02332fe2f79c0d1
4
+ data.tar.gz: f0b888501712c79f5e0f4f7e6d0db269ee7eda0a
5
+ SHA512:
6
+ metadata.gz: a53953b012bf1acb2eb819d2a32265edf410a2691cb933b29706a9c82964e189c17b16aa6a282c2c45afd9c3c99beab581c3c949465a697b5d03041d62f9d7ef
7
+ data.tar.gz: 674d5e2e1c9f45ca97f2a3a2a8777f14ef4e9454bd7dfa6f651db9afce1504b82f95c88dcb7de0a4cbd7b9ca650dccee40f2a30c383817530cb17b732014185a
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format doc
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in configuration_loader.gemspec
4
+ gemspec
5
+
6
+ gem "multi_json", :require => false
7
+ gem "rspec"
8
+ gem "toml", :require => false
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Mike Williams
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,48 @@
1
+ # ConfigHound
2
+
3
+ ConfigHound makes it easy to load configuration data that is
4
+ spread between multiple files.
5
+
6
+ ## Usage
7
+
8
+ # load YAML
9
+ config = ConfigHound.load("config.yml")
10
+
11
+ # load JSON
12
+ config = ConfigHound.load("config.json")
13
+
14
+ # load TOML
15
+ config = ConfigHound.load("config.toml")
16
+
17
+ ## Inclusion
18
+
19
+ ConfigHound let's you include defaults from other files.
20
+ Just list the file paths (or URLs) under the key "_include",
21
+
22
+ For example, in `config.yml`:
23
+
24
+ _include:
25
+ - defaults.yml
26
+ pool:
27
+ size: 10
28
+ log:
29
+ file: "app.log"
30
+
31
+ then in `defaults.yml`
32
+
33
+ log:
34
+ level: INFO
35
+ pool:
36
+ size: 1
37
+
38
+ Values in the original config file override those from included files.
39
+ Multiple levels of inclusion are possible.
40
+
41
+ If the placeholder "`_include`" doesn't suit, you can specify
42
+ another, e.g.
43
+
44
+ config = ConfigHound.load("config.yml", :include_key => "defaults")
45
+
46
+ ## Contributing
47
+
48
+ It's on GitHub; you know the drill.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.pattern = 'spec/**/*_spec.rb'
7
+ t.rspec_opts = ["--colour", "--format", "documentation"]
8
+ end
9
+
10
+ task "default" => "spec"
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'config_hound/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+
8
+ spec.name = "config_hound"
9
+ spec.version = ConfigHound::VERSION
10
+
11
+ spec.summary = %q{Sniffs out config, wherever it may be.}
12
+ spec.license = "MIT"
13
+
14
+ spec.authors = ["Mike Williams"]
15
+ spec.email = ["mdub@dogbiscuit.org"]
16
+ spec.homepage = "https://github.com/mdub/config_hound"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.test_files = spec.files.grep(%r{^spec/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+
25
+ end
@@ -0,0 +1,12 @@
1
+ require "config_hound/loader"
2
+ require "config_hound/version"
3
+
4
+ module ConfigHound
5
+
6
+ def self.load(*args)
7
+ Loader.load(*args)
8
+ end
9
+
10
+ end
11
+
12
+
@@ -0,0 +1,46 @@
1
+ require "config_hound/parser"
2
+ require "config_hound/resource"
3
+
4
+ module ConfigHound
5
+
6
+ class Loader
7
+
8
+ DEFAULT_INCLUDE_KEY = "_include"
9
+
10
+ def self.load(path, options = {})
11
+ new(path, options).load
12
+ end
13
+
14
+ def initialize(path, options)
15
+ @resource = Resource.new(path.to_s)
16
+ @include_key = options.fetch(:include_key, DEFAULT_INCLUDE_KEY)
17
+ end
18
+
19
+ def load
20
+ data = Parser.parse(resource.read, resource.format)
21
+ Array(data.delete(include_key)).each do |i|
22
+ defaults = Loader.load(resource.resolve(i))
23
+ include_into!(data, defaults)
24
+ end
25
+ data
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :resource
31
+ attr_reader :include_key
32
+
33
+ def include_into!(data, defaults)
34
+ return unless data.is_a?(Hash) && defaults.is_a?(Hash)
35
+ defaults.each do |key, value|
36
+ if data.has_key?(key)
37
+ include_into!(data[key], value)
38
+ else
39
+ data[key] = value
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,36 @@
1
+ module ConfigHound
2
+
3
+ class Parser
4
+
5
+ def self.parse(*args)
6
+ new.parse(*args)
7
+ end
8
+
9
+ def parse(raw, format)
10
+ parse_method = "parse_#{format}"
11
+ raise "unknown format: #{format}" unless respond_to?(parse_method, true)
12
+ send(parse_method, raw)
13
+ end
14
+
15
+ protected
16
+
17
+ def parse_yaml(raw)
18
+ require "yaml"
19
+ YAML.load(raw)
20
+ end
21
+
22
+ alias :parse_yml :parse_yaml
23
+
24
+ def parse_json(raw)
25
+ require "multi_json"
26
+ MultiJson.load(raw)
27
+ end
28
+
29
+ def parse_toml(raw)
30
+ require "toml"
31
+ TOML.load(raw)
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,55 @@
1
+ require "open-uri"
2
+ require "uri"
3
+
4
+ module ConfigHound
5
+
6
+ LoadError = Class.new(StandardError)
7
+
8
+ # Represents a source of configuration data.
9
+ #
10
+ class Resource
11
+
12
+ def initialize(path)
13
+ @uri = uri_for(path)
14
+ end
15
+
16
+ def to_s
17
+ uri.to_s
18
+ end
19
+
20
+ def resolve(path)
21
+ self.class.new(uri + path)
22
+ end
23
+
24
+ def read
25
+ open(uri.scheme == "file" ? uri.path : uri.to_s) do |io|
26
+ io.read
27
+ end
28
+ rescue Errno::ENOENT
29
+ raise LoadError, "can't load: #{uri}"
30
+ end
31
+
32
+ def format
33
+ File.extname(uri.to_s)[1..-1]
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :uri
39
+
40
+ def uri_for(path)
41
+ case path
42
+ when URI
43
+ path
44
+ when %r{^\w+:/}
45
+ URI(path)
46
+ when %r{^/}
47
+ URI("file:#{path}")
48
+ else
49
+ URI("file:#{File.expand_path(path)}")
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,3 @@
1
+ module ConfigHound
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,67 @@
1
+ require "spec_helper"
2
+
3
+ require "config_hound/resource"
4
+ require "uri"
5
+
6
+ describe ConfigHound::Resource do
7
+
8
+ let(:resource) { described_class.new(path) }
9
+
10
+ context "with a URI" do
11
+
12
+ let(:uri) { URI("http://example.com/some_uri") }
13
+ let(:path) { uri }
14
+
15
+ it "retains the URI" do
16
+ expect(resource.to_s).to eq(uri.to_s)
17
+ end
18
+
19
+ end
20
+
21
+ context "with a fully-qualified path" do
22
+
23
+ let(:path) { "/path/to/file" }
24
+
25
+ it "assumes it's a file" do
26
+ expect(resource.to_s).to eq("file:/path/to/file")
27
+ end
28
+
29
+ describe "#resolve" do
30
+
31
+ context "with a relative path" do
32
+ it "resolves relatively" do
33
+ other_resource = resource.resolve("other_file")
34
+ expect(other_resource).to be_a(ConfigHound::Resource)
35
+ expect(other_resource.to_s).to eq("file:/path/to/other_file")
36
+ end
37
+ end
38
+
39
+ context "with an absolute file path" do
40
+ it "resolves relatively" do
41
+ other_resource = resource.resolve("/different/path")
42
+ expect(other_resource.to_s).to eq("file:/different/path")
43
+ end
44
+ end
45
+
46
+ context "with a fully-qualified URI" do
47
+ it "uses the URI provided" do
48
+ other_resource = resource.resolve("http://foo/bar")
49
+ expect(other_resource.to_s).to eq("http://foo/bar")
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ context "with a relative path" do
58
+
59
+ let(:path) { "config.yml" }
60
+
61
+ it "assumes it's a file relative to $CWD" do
62
+ expect(resource.to_s).to eq("file:#{Dir.pwd}/config.yml")
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ require "config_hound"
4
+
5
+ describe ConfigHound do
6
+
7
+ def load(path)
8
+ ConfigHound.load(path)
9
+ end
10
+
11
+ given_resource "config.yml", %{
12
+ foo: 1
13
+ bar: 2
14
+ }
15
+
16
+ it "loads YAML" do
17
+ expect(load("config.yml")).to eq(
18
+ "foo" => 1,
19
+ "bar" => 2
20
+ )
21
+ end
22
+
23
+ given_resource "config.json", %{
24
+ {
25
+ "foo": 1,
26
+ "bar": 2
27
+ }
28
+ }
29
+
30
+ it "loads JSON" do
31
+ expect(load("config.json")).to eq(
32
+ "foo" => 1,
33
+ "bar" => 2
34
+ )
35
+ end
36
+
37
+ given_resource "config.toml", %{
38
+ foo = 1
39
+ bar = 2
40
+ }
41
+
42
+ it "loads TOML" do
43
+ expect(load("config.toml")).to eq(
44
+ "foo" => 1,
45
+ "bar" => 2
46
+ )
47
+ end
48
+
49
+ end
@@ -0,0 +1,169 @@
1
+
2
+ describe ConfigHound do
3
+
4
+ let(:options) { {} }
5
+
6
+ def load(path)
7
+ ConfigHound.load(path, options)
8
+ end
9
+
10
+ let(:config) { load("config.yml") }
11
+
12
+ context "when file includes another" do
13
+
14
+ given_resource "config.yml", %{
15
+ foo: 42
16
+ _include: included.yml
17
+ }
18
+
19
+ given_resource "included.yml", %{
20
+ foo: 1
21
+ bar: 2
22
+ }
23
+
24
+ it "merges in the included file" do
25
+ expect(config).to eq(
26
+ "foo" => 42,
27
+ "bar" => 2
28
+ )
29
+ end
30
+
31
+ end
32
+
33
+ context "when the included file has a different format" do
34
+
35
+ given_resource "config.yml", %{
36
+ foo: 42
37
+ _include: included.toml
38
+ }
39
+
40
+ given_resource "included.toml", %{
41
+ foo = 1
42
+ bar = 2
43
+ }
44
+
45
+ it "doesn't matter" do
46
+ expect(config).to eq(
47
+ "foo" => 42,
48
+ "bar" => 2
49
+ )
50
+ end
51
+
52
+ end
53
+
54
+ context "with multiple levels of inclusion" do
55
+
56
+ given_resource "config.yml", %{
57
+ from_config: C
58
+ _include: a.yml
59
+ }
60
+
61
+ given_resource "a.yml", %{
62
+ from_a: A
63
+ _include: b.yml
64
+ }
65
+
66
+ given_resource "b.yml", %{
67
+ from_b: B
68
+ }
69
+
70
+ it "merges in both files" do
71
+ expect(config).to eq(
72
+ "from_config" => "C",
73
+ "from_a" => "A",
74
+ "from_b" => "B"
75
+ )
76
+ end
77
+
78
+ end
79
+
80
+ context "with an alernate :include_key" do
81
+
82
+ before do
83
+ options[:include_key] = "slurp"
84
+ end
85
+
86
+ given_resource "config.yml", %{
87
+ _include: a.yml
88
+ slurp: b.yml
89
+ }
90
+
91
+ given_resource "a.yml", %{
92
+ from_a: A
93
+ }
94
+
95
+ given_resource "b.yml", %{
96
+ from_b: B
97
+ }
98
+
99
+ it "uses the specified include-key" do
100
+ expect(config).to eq(
101
+ "_include" => "a.yml",
102
+ "from_b" => "B"
103
+ )
104
+ end
105
+
106
+ end
107
+
108
+ context "with relative inclusion" do
109
+
110
+ given_resource "config.yml", %{
111
+ from_config: C
112
+ _include: subdir/a.yml
113
+ }
114
+
115
+ given_resource "subdir/a.yml", %{
116
+ from_a: A
117
+ _include: b.yml
118
+ }
119
+
120
+ given_resource "subdir/b.yml", %{
121
+ from_b: B
122
+ }
123
+
124
+ it "resolves the relative references" do
125
+ expect(config).to eq(
126
+ "from_config" => "C",
127
+ "from_a" => "A",
128
+ "from_b" => "B"
129
+ )
130
+ end
131
+
132
+ end
133
+
134
+ context "with deep structures" do
135
+
136
+ given_resource "config.yml", %{
137
+ _include: defaults.yml
138
+ nested:
139
+ stuff:
140
+ name: Stuff
141
+ strategy: none
142
+ }
143
+
144
+ given_resource "defaults.yml", %{
145
+ nested:
146
+ stuff:
147
+ size: large
148
+ strategy:
149
+ first: think
150
+ then: do
151
+ }
152
+
153
+ it "merges deeply" do
154
+ expect(config).to eq(
155
+ "nested" => {
156
+ "stuff" => {
157
+ "name" => "Stuff",
158
+ "size" => "large",
159
+ "strategy" => "none"
160
+ }
161
+ }
162
+ )
163
+ end
164
+
165
+ end
166
+
167
+ end
168
+
169
+
@@ -0,0 +1,43 @@
1
+ module GivenResource
2
+
3
+ def inputs
4
+ @inputs ||= {}
5
+ end
6
+
7
+ def given_resource(path, content)
8
+ unless path =~ %r(^\w+:/)
9
+ path = File.expand_path(path)
10
+ end
11
+ content = undent(content)
12
+ before do
13
+ allow_any_instance_of(ConfigHound::Resource).to receive(:open).with(path) do |_, _, &block|
14
+ block.call(StringIO.new(content.dup))
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def undent(raw)
22
+ # raw = raw.gsub(/^ *$/,'')
23
+ if raw =~ /\A( +)/
24
+ indent = $1
25
+ raw.gsub(/^#{indent}/, '').gsub(/ +$/, '')
26
+ else
27
+ raw
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ RSpec.configure do |config|
34
+
35
+ config.extend(GivenResource)
36
+
37
+ config.before do
38
+ allow_any_instance_of(ConfigHound::Resource).to receive(:open) do |_, path, &block|
39
+ raise Errno::ENOENT, "can't load: #{path}"
40
+ end
41
+ end
42
+
43
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: config_hound
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mike Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description:
42
+ email:
43
+ - mdub@dogbiscuit.org
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".rspec"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - config_hound.gemspec
55
+ - lib/config_hound.rb
56
+ - lib/config_hound/loader.rb
57
+ - lib/config_hound/parser.rb
58
+ - lib/config_hound/resource.rb
59
+ - lib/config_hound/version.rb
60
+ - spec/config_hound/resource_spec.rb
61
+ - spec/features/basics_spec.rb
62
+ - spec/features/include_spec.rb
63
+ - spec/spec_helper.rb
64
+ homepage: https://github.com/mdub/config_hound
65
+ licenses:
66
+ - MIT
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 2.2.2
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Sniffs out config, wherever it may be.
88
+ test_files:
89
+ - spec/config_hound/resource_spec.rb
90
+ - spec/features/basics_spec.rb
91
+ - spec/features/include_spec.rb
92
+ - spec/spec_helper.rb