config_hound 1.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 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