flexconf 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format nested
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in flexconf.gemspec
4
+ gemspec
@@ -0,0 +1,116 @@
1
+ The Don't Be a Dick License
2
+ ===========================
3
+ _version 0.3_
4
+
5
+ **Project:** [FlexConf][3] (Ruby gem)
6
+ **Author:** Stephen Eley (<sfeley@gmail.com>)
7
+
8
+ This is a proposed draft of the **Don't Be a Dick** license for open source projects. The purpose of this license is to permit the broadest feasible scope for reuse and modification of creative work, restricted only by the requirement that one is not a dick about it.
9
+
10
+ > **NOTE:** Despite its original gender association, the word "Dick" is used herein in a _cultural_ context and not a _genital_ context. This License has chosen to adopt the term for its linguistic force and psychological impact, and sincerely regrets any inference of sexism. For purposes of the terms and conditions contained herein, it is understood that both men and women are equally capable of being Dicks.
11
+
12
+ > (But they shouldn't be.)
13
+
14
+
15
+ 1. Legal Parameters
16
+ -------------------
17
+ For legal purposes, the DBAD license is a strict superset of the [Apache License, Version 2.0][1] and incorporates all terms, conditions, privileges and limitations therein. The following text is a binding part of this license for this project:
18
+
19
+ > Copyright 2011 Stephen Eley
20
+
21
+ > Licensed under the Apache License, Version 2.0 (the "License");
22
+ you may not use this file except in compliance with the License.
23
+ You may obtain a copy of the License at
24
+
25
+ > <http://www.apache.org/licenses/LICENSE-2.0>
26
+
27
+ > Unless required by applicable law or agreed to in writing, software
28
+ distributed under the License is distributed on an "AS IS" BASIS,
29
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30
+ See the License for the specific language governing permissions and
31
+ limitations under the License.
32
+
33
+ Nothing in the following text should be construed to inhibit, contradict, or override any part of the Apache License, Version 2.0.
34
+
35
+ 2. Definitions
36
+ --------------
37
+ The following terms and definitions shall be in effect for the rest of this document.
38
+
39
+ > **NOTE:** Singular nouns and pronouns are used throughout this License for
40
+ grammatical convenience. However, any of the terms below _may_ refer to a
41
+ collective work or group of people. If this pegs your punctiliousness, you
42
+ are directed to mentally substitute "Person _or persons,_" "Dick _or
43
+ dicks,_" etc., throughout this license. Just don't harass us about it.
44
+
45
+ ### A. Project
46
+
47
+ A creative work of (software | writing | visual art | music) into which significant time and energy have been invested by people who _are not you,_ and which has been released into the world for the benefit of the general public (_including you._)
48
+
49
+ The **Project** may include, incorporate, derive from, or be inspired by other works. The Author, being a Reasonable Person, has made use of such source materials only as permitted by their own licenses or applicable law. The License you are reading applies only to those portions of the Project which are original and distinct to it. Source materials may be covered by their own licenses or conditions, and should not be assumed to have coverage under this License. (However, you are strongly encouraged not to be a dick about them either.)
50
+
51
+ ### B. Author
52
+
53
+ A person who has invested significant time and energy into the Project licensed herein. Given the Author's release of the Project to the world under a liberal license, the Author is declared a Reasonable Person (at least within this context) and inherits all attributes, freedoms, and responsibilities thereof. No other assumptions are made about the Author, nor should you make any.
54
+
55
+ ### C. Reasonable Person
56
+
57
+ A person who respects the time and energy that have been invested in the Project licensed herein, and acts accordingly. A Reasonable Person is broadly characterized as one who exercises his or her privilege to use, _not_ use, redistribute, modify, improve, worsen, review, report issues upon, participate in the community of, or ignore the work _without_ placing undue demands upon the Author. I.e., a Reasonable Person is a constituent of the majority of open source users and the population at large who are not Dicks.
58
+
59
+ ### D. Dick
60
+
61
+ A person who _does not_ respect the time and energy that have been invested in the Project, and acts to punish such effort by giving others associated with the Project -- including, but not limited to, the Author -- a hard time. A Dick is nearly always selfish, but not necessarily with deliberate intent; some Dicks are merely thoughtless. The distinguishing characteristic of a Dick is that he or she places burdens upon Reasonable People, reducing their motivation to engage in open source activities. This damping effect is a significant detriment to the Project, to open source in general, to the production of new intellectual value in the world -- and, ultimately, to the Dick himself or herself.
62
+
63
+ 3. Permissions
64
+ --------------
65
+
66
+ The following privileges are granted explicitly and with positive encouragement to Reasonable People. Although the Author acknowledges that Dicks cannot practically be barred from enjoying the same privileges, they are nevertheless asked to refrain from substantial public activity related to the Project _until_ they reconsider their behavior and stop being Dicks.
67
+
68
+ 1. You are permitted to use the Project or any component of the Project.
69
+
70
+ 2. You are permitted to redistribute the Project or any component of the Project, by itself or as part of a different work, provided that you give fair and reasonable credit back to the Author.
71
+
72
+ 3. You are permitted to change the Project for your own needs or anyone else's. You may keep your changes to yourself or submit them back to the Author or the community, as you see fit, provided you are not a Dick about it.
73
+
74
+ 4. You are permitted and encouraged to participate in any community related to the Project, or to create new communities.
75
+
76
+ 5. You are permitted to report issues or problems with the Project, and to request that they be addressed, so long as the request is made in a reasonable fashion. (This privilege does _not_ oblige the Author to respond.)
77
+
78
+ 6. You are permitted to make money from the Project. No recompense is owed to the Author unless by separate agreement, although sharing opportunities for mutual benefit is by no means discouraged.
79
+
80
+ 7. You are permitted to discuss the Project in any medium, in positive or negative spirit, so long as criticism is not libelous, excessively emotional, nor _ad hominem._ (I.e.: don't lie, don't be vicious, and keep it about the _work_ and not the _people._)
81
+
82
+ 8. You are permitted to ignore the Project completely and make no use of it whatsoever. This right is irrevocable and absolute, and extended to Dicks and Reasonable People alike.
83
+
84
+ 4. Limitations
85
+ --------------
86
+
87
+ The following restrictions are imposed upon all persons, but with active emphasis upon Dicks. These limitations are the inverse of the above privileges and are also tautological, as the prohibited actions are those _definitive_ of Dickhood.
88
+
89
+ 1. You may not impede Reasonable People from exercising their privilege to use, redistribute, change, participate in, profit from, discuss, or ignore the Project.
90
+
91
+ 2. You may not withhold credit from the Author for the Project, nor otherwise present the work of anyone else as your own.
92
+
93
+ 3. You may not hold the Author responsible for any use or abuse of the Project by you or anyone else.
94
+
95
+ 4. You may not troll, flame, nor dumb down any community associated with the Project.
96
+
97
+ 5. Barring separate agreements, you may not _demand_ any time or attention on the part of the Author nor anyone else in the community. You may not hold the Author personally accountable for any issues or damages you discover, nor otherwise spread your own problems to other people.
98
+
99
+ 6. You may not attempt to _compel_ time nor money from the Author nor any other community member by any means, including but not limited to legal action, intimidation, or annoyance.
100
+
101
+ 7. You may not engage in deceptive, excessive, nor _ad hominem_ (i.e. personal) attacks in the course of criticizing the Project.
102
+
103
+ 8. You may not impede the absolute and irrevocable privilege of the Author and other members of the community to ignore you.
104
+
105
+ 5. Use of This License
106
+ ----------------------
107
+ The Don't Be a Dick License is released under the terms of the Don't Be a Dick License. Projects released under this License must remain under the License, and only the Author may modify the terms of this License as it relates to this particular Project.
108
+
109
+ If you wish to make use of the License for your own open work, you may do so without asking permission, provided said work is truly your own, truly open, and you are truly not a Dick. You may modify the terms of the License to suit your own needs, but are encouraged to annotate said changes where they differ from the original. Modifications should not reduce the freedoms granted to Reasonable People as described in Section 3.
110
+
111
+ The 'official' Web site of the Don't Be a Dick License is at [dbad-license.org][2]. Reasonable People are _invited,_ but not compelled, to visit the site and browse or add to the repository of works released under the License.
112
+
113
+
114
+ [1]: http://apache.org/licenses/LICENSE-2.0
115
+ [2]: http://dbad-license.org
116
+ [3]: http://github.com/SFEley/flexconf
@@ -0,0 +1,203 @@
1
+ # FlexConf #
2
+
3
+ FlexConf is a simple configuration utility that does its job and gets out of
4
+ your way. It reads settings from a hash or YAML file (`config.yml` by default)
5
+ with optional overrides from a `*_local.yml` file and from environment
6
+ variables. Settings can be retrieved as indifferent hash values (config['foo']
7
+ or config[:foo]) or method calls (config.foo).
8
+
9
+ Simplicity and portability are major design goals. The code is a single
10
+ lightweight class (roughly 100 lines of code) with no external gem
11
+ dependencies. The spec suite passes in Ruby 1.8.7, 1.9.2, 1.9.3, and current
12
+ (October 2011) versions of JRuby and Rubunius.
13
+
14
+ ## Installation ##
15
+
16
+ Come on kids, you know this one:
17
+
18
+ gem install flexconf
19
+
20
+ Or do what the sophisticated urbanite does and use Bundler:
21
+
22
+ # In your Gemfile:
23
+ gem 'flexconf'
24
+
25
+ ## Loading From a Hash ##
26
+
27
+ This is the simplest approach for small use cases. Just create a Ruby file
28
+ and declare your values:
29
+
30
+ require 'flexconf'
31
+
32
+ CONFIG = FlexConf.new {
33
+ my_arbitrary_username: 'joeschmoe', # Ruby 1.9 syntax
34
+ my_arbitrary_password: 'blah1234',
35
+
36
+ :another_thing => 'here', # Classic 'hashrocket' syntax
37
+ 'some_number' => 24,
38
+
39
+ nested: {
40
+ 'mommy_bird' => 'Tweet!',
41
+ 'baby_bird' => 'Facebook.'
42
+ }
43
+ }
44
+
45
+ This usage mode has no options and no overrides. If you need to calculate
46
+ or merge anything, just do it before you create the FlexConf object.
47
+
48
+ All keys are converted to symbols on creation, regardless of their original
49
+ type. Yes, that means numbers and arrays and other crazy objects too. This is
50
+ for _configuration data;_ we're not trying to be a general-purpose hash.
51
+
52
+ Values are left alone, except that nested hashes are converted into nested
53
+ FlexConf objects.
54
+
55
+ ## Loading from YAML ##
56
+
57
+ This is the more flexible usage, with multiple options for scoping and
58
+ overrides:
59
+
60
+ require 'flexconf'
61
+
62
+ CONFIG = FlexConf.new '../mysettings.yml', # Core filename (include path if needed)
63
+ scope: :development, # Only load values from the 'development' key
64
+ local: 'mysettings_local.yml', # Merge in values from this other YAML file
65
+ environment: true, # Allow overrides from environment variables
66
+ override: { :cache => 'Dummy' } # Also override from the provided hash
67
+
68
+ Or, if it suits your needs, you could simply take the defaults:
69
+
70
+ require 'flexconf'
71
+
72
+ CONFIG = FlexConf.new
73
+
74
+ ...which is equivalent to:
75
+
76
+ CONFIG = FlexConf.new 'config.yml', local: 'config_local.yml', environment: true
77
+
78
+ As with hash loading, all keys are converted to symbols on creation and the
79
+ object is read-only after it's created. Options are described in detail after
80
+ the "Using It" section.
81
+
82
+ ## Using It ##
83
+
84
+ The FlexConf object supports both the 'indifferent hash' style and the 'method
85
+ call' style for accessing configuration values. Both are interchangeable and
86
+ recursive:
87
+
88
+ CONFIG[:some_number] #=> 24
89
+ CONFIG['some_number'] #=> 24
90
+ CONFIG.some_number #=> 24
91
+
92
+ CONFIG[:nested]['mommy_bird'] #=> 'Tweet!'
93
+ CONFIG['nested'][:mommy_bird] #=> 'Tweet!'
94
+ CONFIG.nested.mommy_bird #=> 'Tweet!'
95
+ CONFIG.nested[:baby_bird] #=> 'Facebook.'
96
+
97
+ If you've spent a little time working with [Chef](http://wiki.opscode.com),
98
+ this level of looseness will probably seem familiar. Their attribute access
99
+ patterns were a direct inspiration for FlexConf. (If you've spent a _lot_ of
100
+ time working with Chef, you're probably at the sanatorium and _nothing_ seems
101
+ familiar.)
102
+
103
+ FlexConf objects are _very loosely_ duck-typed to Hashes, but are not subclasses of Hash. They publicly support the following methods:
104
+
105
+ * `[]` (your friend the accessor)
106
+ * `has_key?`
107
+ * `each` (returns key and value, just like Hash)
108
+ * the Enumerable mixin
109
+
110
+ FlexConf objects are _read-only_ once they're initialized. You can't change
111
+ any values later. This is by deliberate design decision. (If you need to muck
112
+ with your application's configuration after it's up and running, it's not
113
+ 'configuration' any more, it's mutable state and you should be paying more
114
+ attention to it than this.)
115
+
116
+ ## Options ##
117
+
118
+ Options that can be passed at initialization are as follows (and only work in YAML mode):
119
+
120
+ ### :scope ###
121
+
122
+ Use this for Rails-style environments. The provided value must be a top-level
123
+ key in the main YAML file. The key/value pairs nested beneath it will become
124
+ top-level structures for the FlexConf configuration, and the rest of the file
125
+ will be ignored. Note that this happens _after_ the YAML library does its
126
+ processing, so if your file looks like:
127
+
128
+ defaults: &defaults
129
+ foo: 'bar'
130
+ yoo: 'yar'
131
+ something_else: 17
132
+
133
+ test:
134
+ << *defaults
135
+ foo: 'car'
136
+
137
+ ...and so forth, a `:scope => :test` option will still do the right thing.
138
+
139
+ Scope limiting applies _only_ to the main YAML file and occurs before any
140
+ other options are handled. Overrides from a 'local' YAML file, environment
141
+ variables, or a supplied hash are assumed already to be in the desired scope
142
+ and won't be transformed.
143
+
144
+ ### :local ###
145
+
146
+ This option addresses the common use case of supplying sensitive data
147
+ (passwords, etc.) or user-specific development machine settings in a secondary
148
+ YAML file that is _not_ checked into source control. The options from this
149
+ secondary file are merged into the main configuration. There are two ways to
150
+ use it:
151
+
152
+ * `:local => 'path/somefile.yml'` looks for the given file and loads it if it
153
+ exists.
154
+
155
+ * `:local => true` appends '_local' to the base filename of the main YAML
156
+ file, and loads it if it exists in the same directory. (E.g., if your
157
+ primary file was `/conf/amazon_settings.yml`, it would look for
158
+ `/conf/amazon_settings_local.yml`.)
159
+
160
+ In either case, no error will be raised if the file is not found.
161
+
162
+ ### :override ###
163
+
164
+ This option takes a hash value and merges it into the configuration _after_
165
+ the main YAML file and `:local` file are processed. It works just like the
166
+ "Loading From a Hash" section described above. 'Nuff said.
167
+
168
+ ### :environment ###
169
+
170
+ This option allows values to be passed in from the command line or from external processes (your Web server, et cetera) by means of environment variables. The environment variable name is lowercased and symbolized, and nested keys can be pointed to by separating them with a double underscore (`__`). The intended use case is something like:
171
+
172
+ $ AMAZON__ACCESS_ID=blahblah AMAZON__SECRET_KEY=yaddayadda rake deploy:thingy
173
+
174
+ If the Rake task is using a FlexConf anywhere with the `:environment` option set, then those two variables will automatically be merged into `CONFIG[:amazon][:access_id]` and `CONFIG[:amazon][:secret_key]`. As with the `:local` option, there are two ways to use it:
175
+
176
+ * `:environment => ['SOME_ENV_VAR', 'ANOTHER_ENV_VAR']` merges in _only_ the
177
+ environment variables specified in the given array. Keys that didn't
178
+ previously exist are created (including nested paths). The rest of the
179
+ environment is ignored, and no error is raised if the variables aren't
180
+ actually set. This is the most secure way to use it if you can plan ahead
181
+ for which configuration values may need changing at runtime.
182
+
183
+ * `:environment => true` scans the entire environment, but _only_ updates keys
184
+ that already exist (from the main YAML file or some other override). New
185
+ keys are not created, to prevent polluting your configuration with settings
186
+ like `[:home]` and `[:_]` and `[:grep_options]`. This is a useful shortcut
187
+ if you want your entire configuration to be alterable at runtime, but make
188
+ sure you don't have any top-level key names that may collide with common
189
+ Unix names.
190
+
191
+ ## Support and Such ##
192
+
193
+ Documentation can be found at the [usual RDoc.info](http://rdoc.info/github/SFEley/flexconf/master) spot.
194
+
195
+ If you have questions, problems, or suggestions please start by leaving an Issue here, but feel free to email me at <sfeley@gmail.com> if you don't get a timely response. I'm sporadic about staying on top of my Github stuff, but I'm trying to get better about it.
196
+
197
+ I encourage pull requests, forks, etc. There's probably more that could be done with this. (But not _much_ more, lest we lose the "simple" part.)
198
+
199
+ FlexConf is licensed under the [Don't Be a Dick](http://dbad-license.org) license, v0.3. The specific license for the project can be found [here](http://github.com/SFEley/flexconf/blob/master/LICENSE.md). (In brief: do anything you want with this, so long as you aren't a dick about it.)
200
+
201
+
202
+
203
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "flexconf"
6
+ s.version = "0.1.0"
7
+ s.authors = ["Stephen Eley"]
8
+ s.email = ["sfeley@gmail.com"]
9
+ s.homepage = ""
10
+ s.summary = %q{Simple, flexible YAML- or Ruby-based configuration management}
11
+ s.description = <<-END
12
+ FlexConf is a simple configuration utility that does its job and gets
13
+ out of your way. It reads settings from a hash or YAML file ('config.yml' by default)
14
+ but allows overrides from a '*_local.yml' file and from environment variables.
15
+ Settings can be read as indifferent hash values (config['foo'] or config[:foo])
16
+ or method calls (config.foo) with recursive nesting (config.foo.bar). The code
17
+ is lightweight and fast with no additional dependencies.
18
+ END
19
+
20
+ s.rubyforge_project = "flexconf"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+
27
+ # specify any dependencies here; for example:
28
+ s.add_development_dependency "rspec"
29
+ # s.add_runtime_dependency "rest-client"
30
+ end
@@ -0,0 +1,187 @@
1
+ require 'yaml'
2
+ require 'forwardable'
3
+
4
+ # A simple but flexible configuration class.
5
+ class FlexConf
6
+ extend Forwardable
7
+ include Enumerable
8
+
9
+ # Loads configuration from the supplied source(s).
10
+ # @overload initialize(hash)
11
+ # Reads the supplied hash as the entire configuration. No options are handled; you'll
12
+ # have to make sure the data you provide contains everything you intend it to.
13
+ # @param [Hash] source The configuration data you want to provide.
14
+ #
15
+ # @overload initialize(yaml_file, opts)
16
+ # Loads the given YAML file and optional overrides.
17
+ # @param [String, Hash] source A YAML filename. The file must be in the current
18
+ # directory or a complete path must be given.
19
+ # @param [optional, Hash] opts Specifies ways in which the supplied data can be
20
+ # overridden. Overrides are processed in the following order:
21
+ # 1. `:scope` (limit the source configuration namespace)
22
+ # 2. `:local` (secondary '*_local.yml' file)
23
+ # 3. `:override` (hash supplied in code)
24
+ # 4. `:environment` (environment variables)
25
+ # @option opts [Hash] :override A hash which will be merged over the source
26
+ # configuration.
27
+ # @option opts [String, Boolean] :local A second YAML file containing local data
28
+ # (presumably outside source control). No error is raised if this file does
29
+ # not exist. A value of 'true' appends `_local` to the source file's base name,
30
+ # e.g. `config_local.yml`.
31
+ # @option opts [Array, Boolean] :environment If given an array of environment
32
+ # variable names, FlexConf will lowercase the names and then inject or override
33
+ # them into the configuration. Nested values can be marked by a double underscore
34
+ # (i.e. 'FOO__BAR' would set a value at [:foo][:bar]). A value of 'true' scans
35
+ # all environment variables, but will _only_ override existing values (e.g. an
36
+ # existing config value at [:aws_access_key] could be overridden by the
37
+ # AWS_ACCESS_KEY environment variable.)
38
+ # (Use with caution! Casually setting ':environment => true' if you have
39
+ # configuration variables such as [:home] or [:path] could have undesired results.)
40
+ # @option opts [String, Symbol] :scope Limits the configuration to the values
41
+ # beneath the given top-level key in the YAML source file. Useful for
42
+ # Rails-style environment configurations ('development', 'production', etc.)
43
+ # Local, environment, and hash overrides are unaffected; they are assumed
44
+ # to be already within the intended scope.
45
+ #
46
+ # @overload initialize()
47
+ # With no parameters, takes a common default case and acts as if you had run
48
+ # `FlexConf.new('config.yml', :local => 'config_local.yml', :environment => true)`.
49
+ # Raises an exception if 'config.yml' does not exist.
50
+ def initialize(source=nil, options=nil)
51
+ @data = {}
52
+ case source
53
+ when /.*\.yml/
54
+ source_data = YAML.load_file(source)
55
+ if options && options[:scope]
56
+ flexify scoped(source_data, options[:scope])
57
+ else
58
+ flexify source_data
59
+ end
60
+ handle_overrides(source, options) if options
61
+ when Hash
62
+ flexify source
63
+ when nil
64
+ if File.exists?('config.yml')
65
+ initialize('config.yml', :local => 'config_local.yml', :environment => true)
66
+ else
67
+ raise ArgumentError, "FlexConf can't load: no configuration was given and there is no config.yml file to default to."
68
+ end
69
+ end
70
+ end
71
+
72
+ # Returns the value for the given key, which can be a string or a symbol. Named
73
+ # keys can also be accessed via method calls (i.e. `config.foo`). Nested
74
+ # configurations are returned as FlexConf objects.
75
+ def [](key)
76
+ @data[normalize_key(key)]
77
+ end
78
+
79
+ # Returns true if the given configuration value exists.
80
+ def has_key?(key)
81
+ @data.has_key? normalize_key(key)
82
+ end
83
+
84
+ # Calls Hash#each on the underlying data structure.
85
+ def_delegator(:@data, :each)
86
+
87
+ protected
88
+ def_delegator(:@data, :[]=) # Needed to merge override data
89
+
90
+ private
91
+ # We're deconstructionists. Everything is a symbol!
92
+ def normalize_key(key)
93
+ case key
94
+ when Symbol
95
+ key
96
+ when String
97
+ key.to_sym
98
+ else
99
+ key.to_s.to_sym
100
+ end
101
+ end
102
+
103
+ # Flatten out, turning keys to symbols and hash values to FlexConfs
104
+ def flexify(hash)
105
+ hash.each do |key, value|
106
+ key = normalize_key(key)
107
+ value = FlexConf.new(value) if value.kind_of?(Hash)
108
+ if @data[key].is_a?(FlexConf)
109
+ merge(@data[key], value)
110
+ else
111
+ @data[key] = value
112
+ end
113
+ end
114
+ end
115
+
116
+ # Deep merge into existing substructures
117
+ def merge(mine, theirs)
118
+ theirs.each do |key, value|
119
+ if mine.has_key?(key) and value.kind_of?(Hash)
120
+ merge(mine[key], value)
121
+ else
122
+ mine[key] = value
123
+ end
124
+ end
125
+ end
126
+
127
+ def handle_overrides(source_file, options={})
128
+ local_override(source_file, options[:local]) if options[:local]
129
+ hash_override(options[:override]) if options[:override]
130
+ if options[:environment].respond_to?(:each)
131
+ env_subset = ENV.select {|k,v| options[:environment].include? k}
132
+ environment_override(env_subset, true)
133
+ elsif options[:environment] == true
134
+ environment_override(ENV, false)
135
+ end
136
+ end
137
+
138
+ def local_override(source_file, local)
139
+ local = File.join(File.join(File.dirname(source_file), File.basename(source_file, '.yml') + '_local.yml')) if local == true
140
+ flexify YAML.load_file(local) if File.exists?(local)
141
+ end
142
+
143
+ def hash_override(hash)
144
+ flexify hash
145
+ end
146
+
147
+
148
+ # Turn 'THIS_LONG__ENVIRONMENT__PATH' to [:this_long, :environment, :path]
149
+ def normalize_envvar(name)
150
+ name.downcase.split('__').map {|e| e.empty? ? nil : e.to_sym}
151
+ end
152
+
153
+ def get_path(root, names, create_path=false)
154
+ this, remaining = names.first, names[1..-1]
155
+ if remaining.empty?
156
+ root
157
+ elsif root[this].kind_of?(FlexConf)
158
+ get_path(root[this], remaining, create_path)
159
+ elsif create_path and !root.has_key?(this)
160
+ root[this] = FlexConf.new({}) # Create an empty stub for the path
161
+ get_path(root[this], remaining, true)
162
+ else # The node exists and isn't a FlexConf, or create_path is false
163
+ false
164
+ end
165
+ end
166
+
167
+ # Override from a provided hash of environment variables. Create new paths
168
+ # or new values only if create=true.
169
+ def environment_override(env, create_key=false)
170
+ env.each do |key, value|
171
+ flexvar = normalize_envvar(key) # Get an array of symbols representing the path
172
+ if data_path = get_path(@data, flexvar, create_key)
173
+ data_key = flexvar[-1]
174
+ data_path[data_key] = value if data_path.has_key?(data_key) or create_key
175
+ end
176
+ end
177
+ end
178
+
179
+ def scoped(data, scope)
180
+ # This is before flattening, so we need to check both string and symbol forms
181
+ data[scope] || data[scope.to_s] || data[scope.to_sym]
182
+ end
183
+
184
+ def method_missing(name, *args, &block)
185
+ self[name] or super
186
+ end
187
+ end
@@ -0,0 +1,2 @@
1
+ this_file: 'The alternate in example/alternate.yml'
2
+ locally_grown: 'Yummy!'
@@ -0,0 +1 @@
1
+ locally_grown: 'Blech!'
@@ -0,0 +1,25 @@
1
+ # Overrides
2
+ this_file: 'example/config.yml'
3
+ local_file: 'none'
4
+
5
+ # Basic key access
6
+ foo: :bar # Ordinary string key
7
+ 7: 'seven' # Non-string key
8
+ :boo: 'far' # Symbol key
9
+
10
+ # Name collisions
11
+ 'zoo': 'ostrich'
12
+ :zoo: 'yak'
13
+
14
+ # Nesting
15
+ nest:
16
+ foo: 'barbar'
17
+ joo: 'lepp'
18
+ renest:
19
+ bar: 'foofoo'
20
+
21
+ # Scope
22
+ development:
23
+ foo: 'dev-fu'
24
+ boo: 'Chicken Boo'
25
+ yoo: 'yuh?'
@@ -0,0 +1,2 @@
1
+ local_file: 'example/config_local.yml'
2
+ yoo: 0.5
@@ -0,0 +1,250 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'flexconf'
4
+
5
+ describe FlexConf do
6
+ before(:each) do
7
+ Dir.chdir(File.join(File.dirname(__FILE__), 'example'))
8
+ @this = FlexConf.new
9
+ end
10
+
11
+ describe "access" do
12
+ it "can read keys as strings" do
13
+ @this['foo'].should == :bar
14
+ end
15
+
16
+ it "can read keys as symbols" do
17
+ @this[:foo].should == :bar
18
+ end
19
+
20
+ it "can read keys as method calls" do
21
+ @this.foo.should == :bar
22
+ end
23
+
24
+ it "can read non-string keys" do
25
+ @this[7].should == 'seven'
26
+ end
27
+
28
+ it "can read non-string keys as strings" do
29
+ @this['7'].should == 'seven'
30
+ end
31
+
32
+ it "can read non-string keys as symbols" do
33
+ @this[:'7'].should == 'seven'
34
+ end
35
+
36
+ it "can read symbol keys as strings" do
37
+ @this['boo'].should == 'far'
38
+ end
39
+
40
+ it "can read symbol keys as symbols" do
41
+ @this[:boo].should == 'far'
42
+ end
43
+
44
+ it "always keeps a consistent value in the event of name collisions" do
45
+ @this['zoo'].should == @this[:zoo]
46
+ end
47
+
48
+ it "knows when it has a key" do
49
+ @this.has_key?('zoo').should be_true
50
+ @this.has_key?(:zoo).should be_true
51
+ end
52
+
53
+ it "knows when it doesn't have a key" do
54
+ @this.has_key?(:fantastical).should be_false
55
+ end
56
+
57
+ it "can be iterated" do
58
+ @this.each do |key, value|
59
+ @this[key].should == value
60
+ end
61
+ end
62
+
63
+ it "is enumerable" do
64
+ @this.count.should > 0
65
+ end
66
+ end
67
+
68
+ describe "nesting" do
69
+ it "can return nested variables" do
70
+ @this[:nest]['foo'].should == 'barbar'
71
+ end
72
+
73
+ it "can return nested variables as method chains" do
74
+ @this.nest.foo.should == 'barbar'
75
+ end
76
+
77
+ it "can use arbitrary call styles" do
78
+ @this.nest[:renest].bar.should == 'foofoo'
79
+ end
80
+ end
81
+
82
+ describe "initialization" do
83
+ it "can be given a hash" do
84
+ c = FlexConf.new 'foo' => 17
85
+ c['foo'].should == 17
86
+ end
87
+
88
+ it "can be given a filename" do
89
+ c = FlexConf.new 'alternate.yml'
90
+ c.this_file.should == 'The alternate in example/alternate.yml'
91
+ end
92
+
93
+ it "defaults to config.yml" do
94
+ @this['this_file'].should == 'example/config.yml'
95
+ end
96
+
97
+ it "defaults to overriding from config_local.yml" do
98
+ @this.local_file.should == 'example/config_local.yml'
99
+ end
100
+
101
+ it "complains if given no parameters and there is no config.yml" do
102
+ Dir.chdir('..')
103
+ lambda {FlexConf.new}.should raise_error(ArgumentError, /config\.yml/)
104
+ end
105
+ end
106
+
107
+ describe "local overrides" do
108
+ before(:each) do
109
+ @this = FlexConf.new('config.yml', :local => 'alternate.yml')
110
+ end
111
+
112
+ it "replaces existing values with ones from the local file" do
113
+ @this.this_file.should == 'The alternate in example/alternate.yml'
114
+ end
115
+
116
+ it "adds new values from the local file" do
117
+ @this[:locally_grown].should == 'Yummy!'
118
+ end
119
+
120
+ it "still leaves values not referred to alone" do
121
+ @this[:development].foo.should == 'dev-fu'
122
+ end
123
+
124
+ it "loads the *_local.yml if :local is 'true'" do
125
+ c = FlexConf.new('alternate.yml', :local => true)
126
+ c[:locally_grown].should == 'Blech!'
127
+ end
128
+ end
129
+
130
+ describe "hash overrides" do
131
+ before(:each) do
132
+ @this = FlexConf.new 'config.yml', :override => {
133
+ 'foo' => 'fahrvergnugen',
134
+ :qoo => 'Qatar',
135
+ :nest => {
136
+ :foo => 'yep',
137
+ :renest => 5
138
+ }
139
+ }
140
+ end
141
+
142
+ it "overrides at the top level" do
143
+ @this.foo.should == 'fahrvergnugen'
144
+ end
145
+
146
+ it "adds new values" do
147
+ @this.qoo.should == 'Qatar'
148
+ end
149
+
150
+ it "overrides nested values" do
151
+ @this.nest.foo.should == 'yep'
152
+ end
153
+
154
+ it "leaves other values alone" do
155
+ @this.nest[:joo].should == 'lepp'
156
+ end
157
+
158
+ it "can take out full blocks" do
159
+ @this.nest.renest.should == 5
160
+ end
161
+ end
162
+
163
+ describe "environment variable overrides" do
164
+ before(:all) do
165
+ ENV['FOO'] = 'harrumph'
166
+ ENV['NEST__JOO'] = 'nipper'
167
+ ENV['HAPPY'] = 'go lucky'
168
+ ENV['ZOO'] = '97'
169
+ ENV['YOO'] = 'know who'
170
+ end
171
+
172
+ describe "stated as an array" do
173
+ before(:each) do
174
+ @this = FlexConf.new('config.yml', :environment => %w{ZOO HAPPY NEST__JOO FRACK})
175
+ end
176
+
177
+ it "overrides existing values" do
178
+ @this.zoo.should == '97'
179
+ end
180
+
181
+ it "creates new values" do
182
+ @this[:happy].should == 'go lucky'
183
+ end
184
+
185
+ it "overrides nested values" do
186
+ @this.nest.joo.should == 'nipper'
187
+ end
188
+
189
+ it "does nothing with stated variables that don't exist" do
190
+ @this.should_not have_key(:frack)
191
+ end
192
+ end
193
+
194
+ describe "automatically pulled with :environment => true" do
195
+ before(:each) do
196
+ @this = FlexConf.new('config.yml', :environment => true)
197
+ end
198
+
199
+ it "overrides existing values" do
200
+ @this.foo.should == 'harrumph'
201
+ end
202
+
203
+ it "does NOT create new values" do
204
+ @this.should_not have_key(:happy)
205
+ end
206
+
207
+ it "overrides nested values" do
208
+ @this[:nest][:joo].should == 'nipper'
209
+ end
210
+ end
211
+
212
+ describe "by default" do
213
+ it "overrides local overrides from the environment" do
214
+ @this.yoo.should == 'know who'
215
+ end
216
+ end
217
+
218
+ after(:all) do
219
+ ENV['FOO'] = nil
220
+ ENV['NEST__JOO'] = nil
221
+ ENV['HAPPY'] = nil
222
+ ENV['ZOO'] = nil
223
+ ENV['YOO'] = nil
224
+ end
225
+ end
226
+
227
+
228
+ describe "scope limitation" do
229
+ before(:each) do
230
+ @this = FlexConf.new('config.yml', :scope => :development)
231
+ end
232
+
233
+ it "returns the values in scope" do
234
+ @this.foo.should == 'dev-fu'
235
+ end
236
+
237
+ it "does not locally override unless explicitly told" do
238
+ @this.yoo.should == 'yuh?'
239
+ end
240
+
241
+ it "knows nothing about out-of-scope values" do
242
+ lambda {@this.zoo}.should raise_error(NoMethodError)
243
+ end
244
+
245
+ it "takes local overrides at the top level" do
246
+ c = FlexConf.new('config.yml', :scope => :development, :local => true)
247
+ c.yoo.should == 0.5
248
+ end
249
+ end
250
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flexconf
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stephen Eley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-28 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70311136259080 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70311136259080
25
+ description: ! "FlexConf is a simple configuration utility that does its job and gets\nout
26
+ of your way. It reads settings from a hash or YAML file ('config.yml' by default)\nbut
27
+ allows overrides from a '*_local.yml' file and from environment variables. \nSettings
28
+ can be read as indifferent hash values (config['foo'] or config[:foo]) \nor method
29
+ calls (config.foo) with recursive nesting (config.foo.bar). The code\nis lightweight
30
+ and fast with no additional dependencies.\n"
31
+ email:
32
+ - sfeley@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - .rspec
39
+ - Gemfile
40
+ - LICENSE.md
41
+ - README.md
42
+ - Rakefile
43
+ - flexconf.gemspec
44
+ - lib/flexconf.rb
45
+ - spec/example/alternate.yml
46
+ - spec/example/alternate_local.yml
47
+ - spec/example/config.yml
48
+ - spec/example/config_local.yml
49
+ - spec/flexconf_spec.rb
50
+ homepage: ''
51
+ licenses: []
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project: flexconf
70
+ rubygems_version: 1.8.10
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Simple, flexible YAML- or Ruby-based configuration management
74
+ test_files:
75
+ - spec/example/alternate.yml
76
+ - spec/example/alternate_local.yml
77
+ - spec/example/config.yml
78
+ - spec/example/config_local.yml
79
+ - spec/flexconf_spec.rb
80
+ has_rdoc: