configurability 1.0.0 → 1.0.1
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.
- data/ChangeLog +40 -1
- data/README.md +126 -2
- data/Rakefile +7 -3
- data/lib/configurability/behavior.rb +26 -0
- data/lib/configurability/config.rb +561 -0
- data/lib/configurability.rb +5 -2
- data/rake/documentation.rb +25 -0
- data/rake/hg.rb +8 -1
- data/spec/configurability/config_spec.rb +311 -0
- metadata +13 -4
data/ChangeLog
CHANGED
@@ -1,4 +1,43 @@
|
|
1
|
-
|
1
|
+
17[tip] 4bff6d5f7b6e 2010-08-08 10:26 -0700 ged
|
2
|
+
Added tag 1.0.1 for changeset e3605ccbe057
|
3
|
+
|
4
|
+
16[1.0.1] e3605ccbe057 2010-08-08 10:26 -0700 ged
|
5
|
+
Added signature for changeset 41bc1de0bf36
|
6
|
+
|
7
|
+
15 41bc1de0bf36 2010-08-04 18:51 -0700 ged
|
8
|
+
More Configurability::Config work
|
9
|
+
|
10
|
+
14 44db952eb824 2010-08-04 18:23 -0700 ged
|
11
|
+
More work on Configurability::Config
|
12
|
+
|
13
|
+
13 2323bf260e7c 2010-08-04 16:28 -0700 ged
|
14
|
+
Finishing up Configurability::Config.
|
15
|
+
|
16
|
+
12 9e0bab83afd5 2010-08-04 10:18 -0700 ged
|
17
|
+
Build system updates.
|
18
|
+
|
19
|
+
11 cba63cef2f7f 2010-08-04 10:17 -0700 ged
|
20
|
+
Add an rspec shared behavior for testing classes with Configurability
|
21
|
+
|
22
|
+
10 4ffdde3f7a2f 2010-07-16 16:55 -0700 ged
|
23
|
+
Adding a Configurability::Config class for YAML config loading.
|
24
|
+
|
25
|
+
9 d225cef4269e 2010-07-16 16:54 -0700 ged
|
26
|
+
Updating README, project metadata, adding .irbrc
|
27
|
+
|
28
|
+
8 9da882b82786 2010-07-16 16:53 -0700 ged
|
29
|
+
Version bump; debugging log
|
30
|
+
|
31
|
+
7 ecf5d4565338 2010-07-12 15:52 -0700 ged
|
32
|
+
Added tag 1.0.0 for changeset 74b5dd9a89c9
|
33
|
+
|
34
|
+
6[1.0.0] 74b5dd9a89c9 2010-07-12 15:52 -0700 ged
|
35
|
+
Added signature for changeset e0fef8dabba4
|
36
|
+
|
37
|
+
5 e0fef8dabba4 2010-07-12 15:51 -0700 ged
|
38
|
+
README formatting, YARD tag fix.
|
39
|
+
|
40
|
+
4 4d7044641f87 2010-07-12 13:38 -0700 ged
|
2
41
|
Documentation, added a test for classname normalization.
|
3
42
|
|
4
43
|
3 0b395dbf657f 2010-07-12 08:13 -0700 ged
|
data/README.md
CHANGED
@@ -10,6 +10,7 @@ configuration is split up and sent to the objects that will use it.
|
|
10
10
|
To add configurability to a class, just require the library and extend
|
11
11
|
the class:
|
12
12
|
|
13
|
+
require 'configurability'
|
13
14
|
class User
|
14
15
|
extend Configurability
|
15
16
|
end
|
@@ -35,7 +36,12 @@ _section name_ as a `Symbol` or a `String`:
|
|
35
36
|
|
36
37
|
The section name is based on an object's _config key_, which is the name of
|
37
38
|
the object that is being extended with all non-word characters converted into
|
38
|
-
underscores (`_`) by default.
|
39
|
+
underscores (`_`) by default. It will also have any leading Ruby-style
|
40
|
+
namespaces stripped, e.g.,
|
41
|
+
|
42
|
+
MyClass -> :myclass
|
43
|
+
Acme::User -> :user
|
44
|
+
"J. Random Hacker" -> :j_random_hacker
|
39
45
|
|
40
46
|
If the object responds to the `#name` method, then the return value of that
|
41
47
|
method is used to derive the name. If it doesn't have a `#name` method, the
|
@@ -82,13 +88,131 @@ a `#configure` method that takes the config section as an argument:
|
|
82
88
|
class WebServer
|
83
89
|
extend Configurability
|
84
90
|
|
91
|
+
config_key :webserver
|
92
|
+
|
85
93
|
def self::configure( configsection )
|
86
94
|
@default_bind_addr = configsection[:host]
|
87
95
|
@default_port = configsection[:port]
|
88
96
|
end
|
89
97
|
end
|
90
98
|
|
91
|
-
If you still want the
|
99
|
+
If you still want the `@config` variable to be set, just `super` from your implementation; don't if you don't want it to be set.
|
100
|
+
|
101
|
+
|
102
|
+
## Configuration Objects
|
103
|
+
|
104
|
+
Configurability also includes `Configurability::Config`, a fairly simple
|
105
|
+
configuration object class that can be used to load a YAML configuration file,
|
106
|
+
and then present both a Hash-like and a Struct-like interface for reading
|
107
|
+
configuration sections and values; it's meant to be used in tandem with Configurability, but it's also useful on its own.
|
108
|
+
|
109
|
+
Here's a quick example to demonstrate some of its features. Suppose you have a
|
110
|
+
config file that looks like this:
|
111
|
+
|
112
|
+
---
|
113
|
+
database:
|
114
|
+
development:
|
115
|
+
adapter: sqlite3
|
116
|
+
database: db/dev.db
|
117
|
+
pool: 5
|
118
|
+
timeout: 5000
|
119
|
+
testing:
|
120
|
+
adapter: sqlite3
|
121
|
+
database: db/testing.db
|
122
|
+
pool: 2
|
123
|
+
timeout: 5000
|
124
|
+
production:
|
125
|
+
adapter: postgres
|
126
|
+
database: fixedassets
|
127
|
+
pool: 25
|
128
|
+
timeout: 50
|
129
|
+
ldap:
|
130
|
+
uri: ldap://ldap.acme.com/dc=acme,dc=com
|
131
|
+
bind_dn: cn=web,dc=acme,dc=com
|
132
|
+
bind_pass: Mut@ge.Mix@ge
|
133
|
+
branding:
|
134
|
+
header: "#333"
|
135
|
+
title: "#dedede"
|
136
|
+
anchor: "#9fc8d4"
|
137
|
+
|
138
|
+
You can load this config like so:
|
139
|
+
|
140
|
+
require 'configurability/config'
|
141
|
+
config = Configurability::Config.load( 'examples/config.yml' )
|
142
|
+
# => #<Configurability::Config:0x1018a7c7016 loaded from
|
143
|
+
examples/config.yml; 3 sections: database, ldap, branding>
|
144
|
+
|
145
|
+
And then access it using struct-like methods:
|
146
|
+
|
147
|
+
config.database
|
148
|
+
# => #<Configurability::Config::Struct:101806fb816
|
149
|
+
{:development=>{:adapter=>"sqlite3", :database=>"db/dev.db", :pool=>5,
|
150
|
+
:timeout=>5000}, :testing=>{:adapter=>"sqlite3",
|
151
|
+
:database=>"db/testing.db", :pool=>2, :timeout=>5000},
|
152
|
+
:production=>{:adapter=>"postgres", :database=>"fixedassets",
|
153
|
+
:pool=>25, :timeout=>50}}>
|
154
|
+
|
155
|
+
config.database.development.adapter
|
156
|
+
# => "sqlite3"
|
157
|
+
|
158
|
+
config.ldap.uri
|
159
|
+
# => "ldap://ldap.acme.com/dc=acme,dc=com"
|
160
|
+
|
161
|
+
config.branding.title
|
162
|
+
# => "#dedede"
|
163
|
+
|
164
|
+
or using a Hash-like interface using either `Symbol`s, `String`s, or a mix of
|
165
|
+
both:
|
166
|
+
|
167
|
+
config[:branding][:title]
|
168
|
+
# => "#dedede"
|
169
|
+
|
170
|
+
config['branding']['header']
|
171
|
+
# => "#333"
|
172
|
+
|
173
|
+
config['branding'][:anchor]
|
174
|
+
# => "#9fc8d4"
|
175
|
+
|
176
|
+
You can install it via the Configurability interface:
|
177
|
+
|
178
|
+
config.install
|
179
|
+
|
180
|
+
Check to see if the file it was loaded from has changed since you
|
181
|
+
loaded it:
|
182
|
+
|
183
|
+
config.changed?
|
184
|
+
# => false
|
185
|
+
|
186
|
+
# Simulate changing the file by manually changing its mtime
|
187
|
+
File.utime( Time.now, Time.now, config.path )
|
188
|
+
config.changed?
|
189
|
+
# => true
|
190
|
+
|
191
|
+
If it has changed (or even if it hasn't), you can reload it, which automatically re-installs it via the Configurability interface:
|
192
|
+
|
193
|
+
config.reload
|
194
|
+
|
195
|
+
You can make modifications via the same Struct- or Hash-like interfaces and write the modified config back out to the same file:
|
196
|
+
|
197
|
+
config.database.testing.adapter = 'mysql'
|
198
|
+
config[:database]['testing'].database = 't_fixedassets'
|
199
|
+
|
200
|
+
then dump it to a YAML string:
|
201
|
+
|
202
|
+
config.dump
|
203
|
+
# => "--- \ndatabase: \n development: \n adapter: sqlite3\n
|
204
|
+
database: db/dev.db\n pool: 5\n timeout: 5000\n testing: \n
|
205
|
+
adapter: mysql\n database: t_fixedassets\n pool: 2\n timeout:
|
206
|
+
5000\n production: \n adapter: postgres\n database:
|
207
|
+
fixedassets\n pool: 25\n timeout: 50\nldap: \n uri:
|
208
|
+
ldap://ldap.acme.com/dc=acme,dc=com\n bind_dn:
|
209
|
+
cn=web,dc=acme,dc=com\n bind_pass: Mut@ge.Mix@ge\nbranding: \n
|
210
|
+
header: \"#333\"\n title: \"#dedede\"\n anchor: \"#9fc8d4\"\n"
|
211
|
+
|
212
|
+
or write it back to the file it was loaded from:
|
213
|
+
|
214
|
+
config.write
|
215
|
+
|
92
216
|
|
93
217
|
|
94
218
|
## Development
|
data/Rakefile
CHANGED
@@ -76,7 +76,7 @@ elsif VERSION_FILE.exist?
|
|
76
76
|
PKG_VERSION = VERSION_FILE.read[ /VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, 1 ]
|
77
77
|
end
|
78
78
|
|
79
|
-
PKG_VERSION = '0.0.0' unless defined?( PKG_VERSION )
|
79
|
+
PKG_VERSION = '0.0.0' unless defined?( PKG_VERSION ) && !PKG_VERSION.nil?
|
80
80
|
|
81
81
|
PKG_FILE_NAME = "#{PKG_NAME.downcase}-#{PKG_VERSION}"
|
82
82
|
GEM_FILE_NAME = "#{PKG_FILE_NAME}.gem"
|
@@ -168,7 +168,7 @@ include RakefileHelpers
|
|
168
168
|
|
169
169
|
# Set the build ID if the mercurial executable is available
|
170
170
|
if hg = which( 'hg' )
|
171
|
-
id =
|
171
|
+
id = `#{hg} id -n`.chomp
|
172
172
|
PKG_BUILD = "pre%03d" % [(id.chomp[ /^[[:xdigit:]]+/ ] || '1')]
|
173
173
|
else
|
174
174
|
PKG_BUILD = 'pre000'
|
@@ -237,7 +237,11 @@ GEMSPEC = Gem::Specification.new do |gem|
|
|
237
237
|
|
238
238
|
gem.summary = PKG_SUMMARY
|
239
239
|
gem.description = [
|
240
|
-
"Configurability is mixin that allows you to add
|
240
|
+
"Configurability is a mixin that allows you to add ",
|
241
|
+
"configurability to one or more classes, assign them ",
|
242
|
+
"each a section of the configuration, and then when ",
|
243
|
+
"the configuration is loaded, the class is given the ",
|
244
|
+
"section it requested.",
|
241
245
|
].join( "\n" )
|
242
246
|
|
243
247
|
gem.authors = "Michael Granger"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'configurability'
|
4
|
+
require 'spec'
|
5
|
+
|
6
|
+
share_examples_for "an object with Configurability" do
|
7
|
+
|
8
|
+
before( :each ) do
|
9
|
+
fail "this behavior expects the object under test to be in the @object " +
|
10
|
+
"instance variable" unless defined?( @object )
|
11
|
+
end
|
12
|
+
|
13
|
+
it "is extended with Configurability" do
|
14
|
+
Configurability.configurable_objects.should include( @object )
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has a Symbol config key" do
|
18
|
+
@object.config_key.should be_a( Symbol )
|
19
|
+
end
|
20
|
+
|
21
|
+
it "has a config key that is a reasonable section name" do
|
22
|
+
@object.config_key.to_s.should =~ /^[a-z][a-z0-9]*$/i
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,561 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'pathname'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'yaml'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
require 'configurability'
|
10
|
+
|
11
|
+
# A configuration object class for systems with Configurability
|
12
|
+
#
|
13
|
+
# @author Michael Granger <ged@FaerieMUD.org>
|
14
|
+
# @author Mahlon E. Smith <mahlon@martini.nu>
|
15
|
+
#
|
16
|
+
# This class also delegates some of its methods to the underlying struct:
|
17
|
+
#
|
18
|
+
# @see Configurability::Config::Struct#to_hash
|
19
|
+
# #to_hash (delegated to its internal Struct)
|
20
|
+
# @see Configurability::Config::Struct#member?
|
21
|
+
# #member? (delegated to its internal Struct)
|
22
|
+
# @see Configurability::Config::Struct#members
|
23
|
+
# #members (delegated to its internal Struct)
|
24
|
+
# @see Configurability::Config::Struct#merge
|
25
|
+
# #merge (delegated to its internal Struct)
|
26
|
+
# @see Configurability::Config::Struct#merge!
|
27
|
+
# #merge! (delegated to its internal Struct)
|
28
|
+
# @see Configurability::Config::Struct#each
|
29
|
+
# #each (delegated to its internal Struct)
|
30
|
+
# @see Configurability::Config::Struct#[]
|
31
|
+
# #[] (delegated to its internal Struct)
|
32
|
+
# @see Configurability::Config::Struct#[]=
|
33
|
+
# #[]= (delegated to its internal Struct)
|
34
|
+
#
|
35
|
+
class Configurability::Config
|
36
|
+
extend Forwardable
|
37
|
+
|
38
|
+
|
39
|
+
#############################################################
|
40
|
+
### C L A S S M E T H O D S
|
41
|
+
#############################################################
|
42
|
+
|
43
|
+
### Read and return a Configurability::Config object from the file at the given +path+.
|
44
|
+
### @param [String] path the path to the config file
|
45
|
+
### @param [Hash] defaults a Hash of default config values which will be
|
46
|
+
### used if the config at +path+ doesn't override
|
47
|
+
### them.
|
48
|
+
### @param block passed through as the block argument to {#initialize}.
|
49
|
+
def self::load( path, defaults=nil, &block )
|
50
|
+
path = Pathname( path ).expand_path
|
51
|
+
source = path.read
|
52
|
+
Configurability.log.debug "Read %d bytes from %s" % [ source.length, path ]
|
53
|
+
return new( source, path, defaults, &block )
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
### Recursive hash-merge function. Used as the block argument to a Hash#merge.
|
58
|
+
### @param [Symbol] key the key that's in conflict
|
59
|
+
### @param [Object] oldval the value in the original Hash
|
60
|
+
### @param [Object] newval the value in the Hash being merged
|
61
|
+
def self::merge_complex_hashes( key, oldval, newval )
|
62
|
+
return oldval.merge( newval, &method(:merge_complex_hashes) ) if
|
63
|
+
oldval.is_a?( Hash ) && newval.is_a?( Hash )
|
64
|
+
return newval
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
#############################################################
|
70
|
+
### I N S T A N C E M E T H O D S
|
71
|
+
#############################################################
|
72
|
+
|
73
|
+
### Create a new Configurability::Config object. If the optional +source+ argument
|
74
|
+
### is specified, parse the config from it.
|
75
|
+
###
|
76
|
+
### @param [String] source the YAML source of the configuration
|
77
|
+
### @param [String, Pathname] path the path to the config file (if loaded from a file)
|
78
|
+
### @param [Hash] defaults a Hash containing default values which the loaded values
|
79
|
+
### will be merged into.
|
80
|
+
### @yield The block will be evaluated in the context of the config object after
|
81
|
+
### the config is loaded, unless it accepts an argument, in which case the config
|
82
|
+
### object is passed as the argument.
|
83
|
+
def initialize( source=nil, path=nil, defaults=nil, &block )
|
84
|
+
|
85
|
+
# Shift the hash parameter if it shows up as the path
|
86
|
+
if path.is_a?( Hash )
|
87
|
+
defaults = path
|
88
|
+
path = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# Make a deep copy of the defaults before loading so we don't modify
|
92
|
+
# the argument
|
93
|
+
@defaults = Marshal.load( Marshal.dump(defaults) ) if defaults
|
94
|
+
@time_created = Time.now
|
95
|
+
@path = path
|
96
|
+
|
97
|
+
if source
|
98
|
+
@struct = self.make_configstruct_from_source( source, @defaults )
|
99
|
+
else
|
100
|
+
@struct = Configurability::Config::Struct.new( @defaults )
|
101
|
+
end
|
102
|
+
|
103
|
+
if block
|
104
|
+
Configurability.log.debug "Block arity is: %p" % [ block.arity ]
|
105
|
+
|
106
|
+
# A block with an argument is called with the config as the argument
|
107
|
+
# instead of instance_evaled
|
108
|
+
case block.arity
|
109
|
+
when 0, -1 # 1.9 and 1.8, respectively
|
110
|
+
Configurability.log.debug "Instance evaling in the context of %p" % [ self ]
|
111
|
+
self.instance_eval( &block )
|
112
|
+
else
|
113
|
+
block.call( self )
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
######
|
120
|
+
public
|
121
|
+
######
|
122
|
+
|
123
|
+
# Define delegators to the inner data structure
|
124
|
+
def_delegators :@struct, :to_hash, :to_h, :member?, :members, :merge,
|
125
|
+
:merge!, :each, :[], :[]=
|
126
|
+
|
127
|
+
# @return [Configurability::Config::Struct] The underlying config data structure
|
128
|
+
attr_reader :struct
|
129
|
+
|
130
|
+
# @return [Time] The time the configuration was loaded
|
131
|
+
attr_accessor :time_created
|
132
|
+
|
133
|
+
# @return [Pathname] the path to the config file, if loaded from a file
|
134
|
+
attr_accessor :path
|
135
|
+
|
136
|
+
|
137
|
+
### Install this config object in any objects that have added
|
138
|
+
### Configurability.
|
139
|
+
def install
|
140
|
+
Configurability.configure_objects( self )
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
### Return the config object as a YAML hash
|
145
|
+
def dump
|
146
|
+
strhash = stringify_keys( self.to_h )
|
147
|
+
return YAML.dump( strhash )
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
### Write the configuration object using the specified name and any
|
152
|
+
### additional +args+.
|
153
|
+
def write( path=@path, *args )
|
154
|
+
raise ArgumentError,
|
155
|
+
"No name associated with this config." unless path
|
156
|
+
path.open( File::WRONLY|File::CREAT|File::TRUNC ) do |ofh|
|
157
|
+
ofh.print( self.dump )
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
### Returns +true+ for methods which can be autoloaded
|
163
|
+
def respond_to?( sym )
|
164
|
+
return true if @struct.member?( sym.to_s.sub(/(=|\?)$/, '').to_sym )
|
165
|
+
super
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
### Returns +true+ if the configuration has changed since it was last
|
170
|
+
### loaded, either by setting one of its members or changing the file
|
171
|
+
### from which it was loaded.
|
172
|
+
def changed?
|
173
|
+
return self.changed_reason ? true : false
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
### If the configuration has changed, return the reason. If it hasn't,
|
178
|
+
### returns nil.
|
179
|
+
def changed_reason
|
180
|
+
if @struct.dirty?
|
181
|
+
Configurability.log.debug "Changed_reason: struct was modified"
|
182
|
+
return "Struct was modified"
|
183
|
+
end
|
184
|
+
|
185
|
+
if self.path && self.is_older_than?( self.path )
|
186
|
+
Configurability.log.debug "Source file (%s) has changed." % [ self.path ]
|
187
|
+
return "Config source (%s) has been updated since %s" %
|
188
|
+
[ self.path, self.time_created ]
|
189
|
+
end
|
190
|
+
|
191
|
+
return nil
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
### Return +true+ if the specified +file+ is newer than the time the receiver
|
196
|
+
### was created.
|
197
|
+
def is_older_than?( path )
|
198
|
+
return false unless path.exist?
|
199
|
+
st = path.stat
|
200
|
+
Configurability.log.debug "File mtime is: %s, comparison time is: %s" %
|
201
|
+
[ st.mtime, @time_created ]
|
202
|
+
return st.mtime > @time_created
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
### Reload the configuration from the original source if it has
|
207
|
+
### changed. Returns +true+ if it was reloaded and +false+ otherwise.
|
208
|
+
###
|
209
|
+
def reload
|
210
|
+
raise "can't reload from an in-memory source" unless self.path
|
211
|
+
|
212
|
+
if self.changed?
|
213
|
+
self.time_created = Time.now
|
214
|
+
source = self.path.read
|
215
|
+
@struct = self.make_configstruct_from_source( source, @defaults )
|
216
|
+
|
217
|
+
self.install
|
218
|
+
return true
|
219
|
+
else
|
220
|
+
return false
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
### Return a human-readable, compact representation of the configuration
|
226
|
+
### suitable for debugging.
|
227
|
+
def inspect
|
228
|
+
return "#<%s:0x%0x16 loaded from %s; %d sections: %s>" % [
|
229
|
+
self.class.name,
|
230
|
+
self.object_id * 2,
|
231
|
+
self.path ? self.path : "memory",
|
232
|
+
self.struct.members.length,
|
233
|
+
self.struct.members.join( ', ' )
|
234
|
+
]
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
#########
|
239
|
+
protected
|
240
|
+
#########
|
241
|
+
|
242
|
+
|
243
|
+
### Read in the specified +filename+ and return a config struct.
|
244
|
+
### @param [String] source the YAML source to be converted
|
245
|
+
### @return [Configurability::Config::Struct] the converted config struct
|
246
|
+
def make_configstruct_from_source( source, defaults=nil )
|
247
|
+
defaults ||= {}
|
248
|
+
mergefunc = Configurability::Config.method( :merge_complex_hashes )
|
249
|
+
hash = YAML.load( source )
|
250
|
+
ihash = symbolify_keys( untaint_values(hash) )
|
251
|
+
mergedhash = defaults.merge( ihash, &mergefunc )
|
252
|
+
|
253
|
+
return Configurability::Config::Struct.new( mergedhash )
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
### Handle calls to struct-members
|
258
|
+
def method_missing( sym, *args )
|
259
|
+
key = sym.to_s.sub( /(=|\?)$/, '' ).to_sym
|
260
|
+
|
261
|
+
self.class.class_eval %{
|
262
|
+
def #{key}; @struct.#{key}; end
|
263
|
+
def #{key}=(arg); @struct.#{key} = arg; end
|
264
|
+
def #{key}?; @struct.#{key}?; end
|
265
|
+
}
|
266
|
+
|
267
|
+
return self.method( sym ).call( *args )
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
#######
|
272
|
+
private
|
273
|
+
#######
|
274
|
+
|
275
|
+
### Return a copy of the specified +hash+ with all of its values
|
276
|
+
### untainted.
|
277
|
+
def untaint_values( hash )
|
278
|
+
newhash = {}
|
279
|
+
hash.each do |key,val|
|
280
|
+
case val
|
281
|
+
when Hash
|
282
|
+
newhash[ key ] = untaint_values( hash[key] )
|
283
|
+
|
284
|
+
when Array
|
285
|
+
newval = val.collect {|v| v.dup.untaint}
|
286
|
+
newhash[ key ] = newval
|
287
|
+
|
288
|
+
when NilClass, TrueClass, FalseClass, Numeric, Symbol
|
289
|
+
newhash[ key ] = val
|
290
|
+
|
291
|
+
else
|
292
|
+
newval = val.dup
|
293
|
+
newval.untaint
|
294
|
+
newhash[ key ] = newval
|
295
|
+
end
|
296
|
+
end
|
297
|
+
return newhash
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
### Return a duplicate of the given +hash+ with its identifier-like keys
|
302
|
+
### transformed into symbols from whatever they were before.
|
303
|
+
def symbolify_keys( hash )
|
304
|
+
newhash = {}
|
305
|
+
hash.each do |key,val|
|
306
|
+
if val.is_a?( Hash )
|
307
|
+
newhash[ key.to_sym ] = symbolify_keys( val )
|
308
|
+
else
|
309
|
+
newhash[ key.to_sym ] = val
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
return newhash
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
### Return a version of the given +hash+ with its keys transformed
|
318
|
+
### into Strings from whatever they were before.
|
319
|
+
def stringify_keys( hash )
|
320
|
+
newhash = {}
|
321
|
+
hash.each do |key,val|
|
322
|
+
if val.is_a?( Hash )
|
323
|
+
newhash[ key.to_s ] = stringify_keys( val )
|
324
|
+
else
|
325
|
+
newhash[ key.to_s ] = val
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
return newhash
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
|
334
|
+
#############################################################
|
335
|
+
### I N T E R I O R C L A S S E S
|
336
|
+
#############################################################
|
337
|
+
|
338
|
+
### Hash-wrapper that allows struct-like accessor calls on nested
|
339
|
+
### hashes.
|
340
|
+
class Struct
|
341
|
+
extend Forwardable
|
342
|
+
include Enumerable
|
343
|
+
|
344
|
+
# Mask most of Kernel's methods away so they don't collide with
|
345
|
+
# config values.
|
346
|
+
Kernel.methods(false).each {|meth|
|
347
|
+
next unless method_defined?( meth )
|
348
|
+
next if /^(?:__|dup|object_id|inspect|class|raise|method_missing)/.match( meth )
|
349
|
+
undef_method( meth )
|
350
|
+
}
|
351
|
+
|
352
|
+
|
353
|
+
### Create a new ConfigStruct using the values from the given +hash+ if specified.
|
354
|
+
### @param [Hash] hash a hash of config values
|
355
|
+
def initialize( hash=nil )
|
356
|
+
hash ||= {}
|
357
|
+
@hash = hash.dup
|
358
|
+
@dirty = false
|
359
|
+
end
|
360
|
+
|
361
|
+
|
362
|
+
######
|
363
|
+
public
|
364
|
+
######
|
365
|
+
|
366
|
+
# Forward some methods to the internal hash
|
367
|
+
def_delegators :@hash, :keys, :key?, :values, :value?, :length,
|
368
|
+
:empty?, :clear, :each
|
369
|
+
|
370
|
+
# Let :each be called as :each_section, too
|
371
|
+
alias_method :each_section, :each
|
372
|
+
|
373
|
+
|
374
|
+
### Return the value associated with the specified +key+.
|
375
|
+
### @param [Symbol, String] key the key of the value to return
|
376
|
+
### @return [Object] the value associated with +key+, or another
|
377
|
+
### Configurability::Config::ConfigStruct if +key+
|
378
|
+
### is a section name.
|
379
|
+
def []( key )
|
380
|
+
key = key.untaint.to_sym
|
381
|
+
|
382
|
+
# Create the config struct on the fly for subsections
|
383
|
+
if !@hash.key?( key )
|
384
|
+
@hash[ key ] = self.class.new
|
385
|
+
elsif @hash[ key ].is_a?( Hash )
|
386
|
+
@hash[ key ] = self.class.new( @hash[key] )
|
387
|
+
end
|
388
|
+
|
389
|
+
return @hash[ key ]
|
390
|
+
end
|
391
|
+
|
392
|
+
|
393
|
+
### Set the value associated with the specified +key+ to +value+.
|
394
|
+
### @param [Symbol, String] key the key of the value to set
|
395
|
+
### @param [Object] value the value to set
|
396
|
+
def []=( key, value )
|
397
|
+
key = key.untaint.to_sym
|
398
|
+
self.mark_dirty if @hash[ key ] != value
|
399
|
+
@hash[ key ] = value
|
400
|
+
end
|
401
|
+
|
402
|
+
|
403
|
+
### Mark the struct has having been modified since its creation.
|
404
|
+
def mark_dirty
|
405
|
+
@dirty = true
|
406
|
+
end
|
407
|
+
|
408
|
+
|
409
|
+
### Returns +true+ if the ConfigStruct or any of its sub-structs
|
410
|
+
### have changed since it was created.
|
411
|
+
def dirty?
|
412
|
+
return true if @dirty
|
413
|
+
return true if @hash.values.find do |obj|
|
414
|
+
obj.respond_to?( :dirty? ) && obj.dirty?
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
|
419
|
+
### Return the receiver's values as a (possibly multi-dimensional)
|
420
|
+
### Hash with String keys.
|
421
|
+
def to_hash
|
422
|
+
rhash = {}
|
423
|
+
@hash.each {|k,v|
|
424
|
+
case v
|
425
|
+
when Configurability::Config::Struct
|
426
|
+
rhash[k] = v.to_h
|
427
|
+
when NilClass, FalseClass, TrueClass, Numeric
|
428
|
+
# No-op (can't dup)
|
429
|
+
rhash[k] = v
|
430
|
+
when Symbol
|
431
|
+
rhash[k] = v.to_s
|
432
|
+
else
|
433
|
+
rhash[k] = v.dup
|
434
|
+
end
|
435
|
+
}
|
436
|
+
return rhash
|
437
|
+
end
|
438
|
+
alias_method :to_h, :to_hash
|
439
|
+
|
440
|
+
|
441
|
+
### Return +true+ if the receiver responds to the given
|
442
|
+
### method. Overridden to grok autoloaded methods.
|
443
|
+
def respond_to?( sym, priv=false )
|
444
|
+
key = sym.to_s.sub( /(=|\?)$/, '' ).to_sym
|
445
|
+
return true if @hash.key?( key )
|
446
|
+
super
|
447
|
+
end
|
448
|
+
|
449
|
+
|
450
|
+
### Returns an Array of Symbols, one for each of the struct's members.
|
451
|
+
def members
|
452
|
+
return @hash.keys
|
453
|
+
end
|
454
|
+
|
455
|
+
|
456
|
+
### Returns +true+ if the given +name+ is the name of a member of
|
457
|
+
### the receiver.
|
458
|
+
def member?( name )
|
459
|
+
return @hash.key?( name.to_s.to_sym )
|
460
|
+
end
|
461
|
+
|
462
|
+
|
463
|
+
### Merge the specified +other+ object with this config struct. The
|
464
|
+
### +other+ object can be either a Hash, another Configurability::Config::Struct, or an
|
465
|
+
### Configurability::Config.
|
466
|
+
def merge!( other )
|
467
|
+
mergefunc = Configurability::Config.method( :merge_complex_hashes )
|
468
|
+
|
469
|
+
case other
|
470
|
+
when Hash
|
471
|
+
@hash = self.to_h.merge( other, &mergefunc )
|
472
|
+
|
473
|
+
when Configurability::Config::Struct
|
474
|
+
@hash = self.to_h.merge( other.to_h, &mergefunc )
|
475
|
+
|
476
|
+
when Configurability::Config
|
477
|
+
@hash = self.to_h.merge( other.struct.to_h, &mergefunc )
|
478
|
+
|
479
|
+
else
|
480
|
+
raise TypeError,
|
481
|
+
"Don't know how to merge with a %p" % other.class
|
482
|
+
end
|
483
|
+
|
484
|
+
# :TODO: Actually check to see if anything has changed?
|
485
|
+
@dirty = true
|
486
|
+
|
487
|
+
return self
|
488
|
+
end
|
489
|
+
|
490
|
+
|
491
|
+
### Return a new Configurability::Config::Struct which is the result of merging the
|
492
|
+
### receiver with the given +other+ object (a Hash or another
|
493
|
+
### Configurability::Config::Struct).
|
494
|
+
def merge( other )
|
495
|
+
self.dup.merge!( other )
|
496
|
+
end
|
497
|
+
|
498
|
+
|
499
|
+
### Return a human-readable representation of the Struct suitable for debugging.
|
500
|
+
def inspect
|
501
|
+
return "#<%s:%0x16 %p>" % [
|
502
|
+
self.class.name,
|
503
|
+
self.object_id * 2,
|
504
|
+
@hash,
|
505
|
+
]
|
506
|
+
end
|
507
|
+
|
508
|
+
|
509
|
+
#########
|
510
|
+
protected
|
511
|
+
#########
|
512
|
+
|
513
|
+
### Handle calls to key-methods
|
514
|
+
def method_missing( sym, *args )
|
515
|
+
key = sym.to_s.sub( /(=|\?)$/, '' ).to_sym
|
516
|
+
|
517
|
+
# Create new methods for this key
|
518
|
+
reader = self.create_member_reader( key )
|
519
|
+
writer = self.create_member_writer( key )
|
520
|
+
predicate = self.create_member_predicate( key )
|
521
|
+
|
522
|
+
# ...and install them
|
523
|
+
self.class.send( :define_method, key, &reader )
|
524
|
+
self.class.send( :define_method, "#{key}=", &writer )
|
525
|
+
self.class.send( :define_method, "#{key}?", &predicate )
|
526
|
+
|
527
|
+
# Now jump to the requested method in a way that won't come back through
|
528
|
+
# the proxy method if something didn't get defined
|
529
|
+
self.method( sym ).call( *args )
|
530
|
+
end
|
531
|
+
|
532
|
+
|
533
|
+
### Create a reader method for the specified +key+ and return it.
|
534
|
+
### @param [Symbol] key the config key to create the reader method body for
|
535
|
+
### @return [Proc] the body of the new method
|
536
|
+
def create_member_reader( key )
|
537
|
+
return lambda { self[key] }
|
538
|
+
end
|
539
|
+
|
540
|
+
|
541
|
+
### Create a predicate method for the specified +key+ and return it.
|
542
|
+
### @param [Symbol] key the config key to create the predicate method body for
|
543
|
+
### @return [Proc] the body of the new method
|
544
|
+
def create_member_predicate( key )
|
545
|
+
return lambda { self[key] ? true : false }
|
546
|
+
end
|
547
|
+
|
548
|
+
|
549
|
+
### Create a writer method for the specified +key+ and return it.
|
550
|
+
### @param [Symbol] key the config key to create the writer method body for
|
551
|
+
### @return [Proc] the body of the new method
|
552
|
+
def create_member_writer( key )
|
553
|
+
return lambda {|val| self[key] = val }
|
554
|
+
end
|
555
|
+
|
556
|
+
end # class Struct
|
557
|
+
|
558
|
+
end # class Configurability::Config
|
559
|
+
|
560
|
+
# vim: set nosta noet ts=4 sw=4:
|
561
|
+
|
data/lib/configurability.rb
CHANGED
@@ -9,10 +9,10 @@ require 'yaml'
|
|
9
9
|
module Configurability
|
10
10
|
|
11
11
|
# Library version constant
|
12
|
-
VERSION = '1.0.
|
12
|
+
VERSION = '1.0.1'
|
13
13
|
|
14
14
|
# Version-control revision constant
|
15
|
-
REVISION = %q$Revision:
|
15
|
+
REVISION = %q$Revision: 9da882b82786 $
|
16
16
|
|
17
17
|
require 'configurability/logformatter.rb'
|
18
18
|
|
@@ -84,6 +84,9 @@ module Configurability
|
|
84
84
|
### +config_key+, the object's #configure method is called with +nil+
|
85
85
|
### instead.
|
86
86
|
def self::configure_objects( config )
|
87
|
+
self.log.debug "Splitting up config %p between %d objects with configurability." %
|
88
|
+
[ config, self.configurable_objects.length ]
|
89
|
+
|
87
90
|
self.configurable_objects.each do |obj|
|
88
91
|
section = obj.config_key.to_sym
|
89
92
|
self.log.debug "Configuring %p with the %p section of the config." %
|
data/rake/documentation.rb
CHANGED
@@ -53,6 +53,31 @@ begin
|
|
53
53
|
class YARD::RegistryStore; include YardGlobals; end
|
54
54
|
class YARD::Docstring; include YardGlobals; end
|
55
55
|
module YARD::Templates::Helpers::ModuleHelper; include YardGlobals; end
|
56
|
+
|
57
|
+
if vvec(RUBY_VERSION) >= vvec("1.9.1")
|
58
|
+
# Monkeypatched to allow more than two '#' characters at the beginning
|
59
|
+
# of the comment line.
|
60
|
+
# Patched from yard-0.5.8
|
61
|
+
require 'yard/parser/ruby/ruby_parser'
|
62
|
+
class YARD::Parser::Ruby::RipperParser < Ripper
|
63
|
+
def on_comment(comment)
|
64
|
+
$stderr.puts "Adding comment: %p" % [ comment ]
|
65
|
+
visit_ns_token(:comment, comment)
|
66
|
+
|
67
|
+
comment = comment.gsub(/^\#+\s{0,1}/, '').chomp
|
68
|
+
append_comment = @comments[lineno - 1]
|
69
|
+
|
70
|
+
if append_comment && @comments_last_column == column
|
71
|
+
@comments.delete(lineno - 1)
|
72
|
+
comment = append_comment + "\n" + comment
|
73
|
+
end
|
74
|
+
|
75
|
+
@comments[lineno] = comment
|
76
|
+
@comments_last_column = column
|
77
|
+
end
|
78
|
+
end # class YARD::Parser::Ruby::RipperParser
|
79
|
+
end
|
80
|
+
|
56
81
|
# </metamonkeypatch>
|
57
82
|
|
58
83
|
YARD_OPTIONS = [] unless defined?( YARD_OPTIONS )
|
data/rake/hg.rb
CHANGED
@@ -215,13 +215,20 @@ unless defined?( HG_DOTDIR )
|
|
215
215
|
paths = get_repo_paths()
|
216
216
|
if origin_url = paths['default']
|
217
217
|
ask_for_confirmation( "Pull and update from '#{origin_url}'?", false ) do
|
218
|
-
|
218
|
+
Rake::Task['hg:pull_without_confirmation'].invoke
|
219
219
|
end
|
220
220
|
else
|
221
221
|
trace "Skipping pull: No 'default' path."
|
222
222
|
end
|
223
223
|
end
|
224
224
|
|
225
|
+
|
226
|
+
desc "Pull and update without confirmation"
|
227
|
+
task :pull_without_confirmation do
|
228
|
+
run 'hg', 'pull', '-u'
|
229
|
+
end
|
230
|
+
|
231
|
+
|
225
232
|
desc "Check the current code in if tests pass"
|
226
233
|
task :checkin => ['hg:pull', 'hg:newfiles', 'test', COMMIT_MSG_FILE] do
|
227
234
|
targets = get_target_args()
|
@@ -0,0 +1,311 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
BEGIN {
|
4
|
+
require 'pathname'
|
5
|
+
basedir = Pathname.new( __FILE__ ).dirname.parent.parent
|
6
|
+
|
7
|
+
libdir = basedir + "lib"
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
|
10
|
+
}
|
11
|
+
|
12
|
+
require 'tempfile'
|
13
|
+
require 'logger'
|
14
|
+
require 'fileutils'
|
15
|
+
|
16
|
+
require 'spec'
|
17
|
+
require 'spec/lib/helpers'
|
18
|
+
|
19
|
+
require 'configurability/config'
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
#####################################################################
|
24
|
+
### C O N T E X T S
|
25
|
+
#####################################################################
|
26
|
+
describe Configurability::Config do
|
27
|
+
include Configurability::SpecHelpers
|
28
|
+
|
29
|
+
TEST_CONFIG = %{
|
30
|
+
---
|
31
|
+
section:
|
32
|
+
subsection:
|
33
|
+
subsubsection: value
|
34
|
+
listsection:
|
35
|
+
- list
|
36
|
+
- values
|
37
|
+
- are
|
38
|
+
- neat
|
39
|
+
mergekey: Yep.
|
40
|
+
textsection: |-
|
41
|
+
With some text as the value
|
42
|
+
...and another line.
|
43
|
+
}.gsub(/^\t/, '')
|
44
|
+
|
45
|
+
|
46
|
+
before( :all ) do
|
47
|
+
setup_logging( :fatal )
|
48
|
+
end
|
49
|
+
|
50
|
+
after( :all ) do
|
51
|
+
reset_logging()
|
52
|
+
end
|
53
|
+
|
54
|
+
it "can dump itself as YAML" do
|
55
|
+
Configurability::Config.new.dump.should == "--- {}\n\n"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns nil as its change description" do
|
59
|
+
Configurability::Config.new.changed_reason.should be_nil()
|
60
|
+
end
|
61
|
+
|
62
|
+
it "autogenerates accessors for non-existant struct members" do
|
63
|
+
config = Configurability::Config.new
|
64
|
+
config.plugins.filestore.maxsize = 1024
|
65
|
+
config.plugins.filestore.maxsize.should == 1024
|
66
|
+
end
|
67
|
+
|
68
|
+
it "merges values loaded from the config with any defaults given" do
|
69
|
+
config = Configurability::Config.new( TEST_CONFIG, :defaultkey => "Oh yeah." )
|
70
|
+
config.defaultkey.should == "Oh yeah."
|
71
|
+
end
|
72
|
+
|
73
|
+
it "yields itself if a block is given at creation" do
|
74
|
+
yielded_self = nil
|
75
|
+
config = Configurability::Config.new { yielded_self = self }
|
76
|
+
yielded_self.should equal( config )
|
77
|
+
end
|
78
|
+
|
79
|
+
it "passes itself as the block argument if a block of arity 1 is given at creation" do
|
80
|
+
arg_self = nil
|
81
|
+
yielded_self = nil
|
82
|
+
config = Configurability::Config.new do |arg|
|
83
|
+
yielded_self = self
|
84
|
+
arg_self = arg
|
85
|
+
end
|
86
|
+
yielded_self.should_not equal( config )
|
87
|
+
arg_self.should equal( config )
|
88
|
+
end
|
89
|
+
|
90
|
+
it "supports both Symbols and Strings for Hash-like access" do
|
91
|
+
config = Configurability::Config.new( TEST_CONFIG )
|
92
|
+
config[:section]['subsection'][:subsubsection].should == 'value'
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
describe "created with in-memory YAML source" do
|
97
|
+
|
98
|
+
before(:each) do
|
99
|
+
@config = Configurability::Config.new( TEST_CONFIG )
|
100
|
+
end
|
101
|
+
|
102
|
+
it "responds to methods which are the same as struct members" do
|
103
|
+
@config.should respond_to( :section )
|
104
|
+
@config.section.should respond_to( :subsection )
|
105
|
+
@config.should_not respond_to( :pork_sausage )
|
106
|
+
end
|
107
|
+
|
108
|
+
it "contains values specified in the source" do
|
109
|
+
# section:
|
110
|
+
# subsection:
|
111
|
+
# subsubsection: value
|
112
|
+
@config.section.subsection.subsubsection.should == 'value'
|
113
|
+
|
114
|
+
# listsection:
|
115
|
+
# - list
|
116
|
+
# - values
|
117
|
+
# - are
|
118
|
+
# - neat
|
119
|
+
@config.listsection.should == %w[list values are neat]
|
120
|
+
|
121
|
+
# mergekey: Yep.
|
122
|
+
@config.mergekey.should == 'Yep.'
|
123
|
+
|
124
|
+
# textsection: |-
|
125
|
+
# With some text as the value
|
126
|
+
# ...and another line.
|
127
|
+
@config.textsection.should == "With some text as the value\n...and another line."
|
128
|
+
end
|
129
|
+
|
130
|
+
it "returns struct members as an Array of Symbols" do
|
131
|
+
@config.members.should be_an_instance_of( Array )
|
132
|
+
@config.members.should have_at_least( 4 ).things
|
133
|
+
@config.members.each do |member|
|
134
|
+
member.should be_an_instance_of( Symbol)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
it "is able to iterate over sections" do
|
139
|
+
@config.each do |key, struct|
|
140
|
+
key.should be_an_instance_of( Symbol)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it "dumps values specified in the source" do
|
145
|
+
@config.dump.should =~ /^section:/
|
146
|
+
@config.dump.should =~ /^\s+subsection:/
|
147
|
+
@config.dump.should =~ /^\s+subsubsection:/
|
148
|
+
@config.dump.should =~ /^- list/
|
149
|
+
end
|
150
|
+
|
151
|
+
it "provides a human-readable description of itself when inspected" do
|
152
|
+
@config.inspect.should =~ /4 sections/i
|
153
|
+
@config.inspect.should =~ /mergekey/
|
154
|
+
@config.inspect.should =~ /textsection/
|
155
|
+
@config.inspect.should =~ /from memory/i
|
156
|
+
end
|
157
|
+
|
158
|
+
it "raises an exception when reloaded" do
|
159
|
+
expect {
|
160
|
+
@config.reload
|
161
|
+
}.to raise_exception( RuntimeError, /can't reload from an in-memory source/i )
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
# saving if changed since loaded
|
168
|
+
describe " whose internal values have been changed since loaded" do
|
169
|
+
before(:each) do
|
170
|
+
@config = Configurability::Config.new( TEST_CONFIG )
|
171
|
+
@config.section.subsection.anothersection = 11451
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
### Specifications
|
176
|
+
it "should report that it is changed" do
|
177
|
+
@config.changed?.should == true
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should report that its internal struct was modified as the reason for the change" do
|
181
|
+
@config.changed_reason.should =~ /struct was modified/i
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# loading from a file
|
188
|
+
describe " loaded from a file" do
|
189
|
+
before(:all) do
|
190
|
+
@tmpfile = Tempfile.new( 'test.conf', '.' )
|
191
|
+
@tmpfile.print( TEST_CONFIG )
|
192
|
+
@tmpfile.close
|
193
|
+
end
|
194
|
+
|
195
|
+
after(:all) do
|
196
|
+
@tmpfile.delete
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
before(:each) do
|
201
|
+
@config = Configurability::Config.load( @tmpfile.path )
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
### Specifications
|
206
|
+
it "remembers which file it was loaded from" do
|
207
|
+
@config.path.should == Pathname( @tmpfile.path ).expand_path
|
208
|
+
end
|
209
|
+
|
210
|
+
it "writes itself back to the same file by default" do
|
211
|
+
@config.port = 114411
|
212
|
+
@config.write
|
213
|
+
otherconfig = Configurability::Config.load( @tmpfile.path )
|
214
|
+
|
215
|
+
otherconfig.port.should == 114411
|
216
|
+
end
|
217
|
+
|
218
|
+
it "includes the name of the file in its inspect output" do
|
219
|
+
@config.inspect.should include( File.basename(@tmpfile.path) )
|
220
|
+
end
|
221
|
+
|
222
|
+
it "yields itself if a block is given at load-time" do
|
223
|
+
yielded_self = nil
|
224
|
+
config = Configurability::Config.load( @tmpfile.path ) do
|
225
|
+
yielded_self = self
|
226
|
+
end
|
227
|
+
yielded_self.should equal( config )
|
228
|
+
end
|
229
|
+
|
230
|
+
it "passes itself as the block argument if a block of arity 1 is given at load-time" do
|
231
|
+
arg_self = nil
|
232
|
+
yielded_self = nil
|
233
|
+
config = Configurability::Config.load( @tmpfile.path ) do |arg|
|
234
|
+
yielded_self = self
|
235
|
+
arg_self = arg
|
236
|
+
end
|
237
|
+
yielded_self.should_not equal( config )
|
238
|
+
arg_self.should equal( config )
|
239
|
+
end
|
240
|
+
|
241
|
+
it "doesn't re-read its source file if it hasn't changed" do
|
242
|
+
@config.path.should_not_receive( :read )
|
243
|
+
Configurability.should_not_receive( :configure_objects )
|
244
|
+
@config.reload.should be_false()
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
# reload if file changes
|
250
|
+
describe " whose file changes after loading" do
|
251
|
+
before(:all) do
|
252
|
+
@tmpfile = Tempfile.new( 'test.conf', '.' )
|
253
|
+
@tmpfile.print( TEST_CONFIG )
|
254
|
+
@tmpfile.close
|
255
|
+
end
|
256
|
+
|
257
|
+
after(:all) do
|
258
|
+
@tmpfile.delete
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
before(:each) do
|
263
|
+
old_date = Time.now - 3600
|
264
|
+
File.utime( old_date, old_date, @tmpfile.path )
|
265
|
+
@config = Configurability::Config.load( @tmpfile.path )
|
266
|
+
now = Time.now + 10
|
267
|
+
File.utime( now, now, @tmpfile.path )
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
### Specifications
|
272
|
+
it "reports that it is changed" do
|
273
|
+
@config.should be_changed
|
274
|
+
end
|
275
|
+
|
276
|
+
it "reports that its source was updated as the reason for the change" do
|
277
|
+
@config.changed_reason.should =~ /source.*updated/i
|
278
|
+
end
|
279
|
+
|
280
|
+
it "re-reads its file when reloaded" do
|
281
|
+
@config.path.should_receive( :read ).and_return( TEST_CONFIG )
|
282
|
+
Configurability.should_receive( :configure_objects ).with( @config )
|
283
|
+
@config.reload.should be_true()
|
284
|
+
end
|
285
|
+
|
286
|
+
it "reapplies its defaults when reloading" do
|
287
|
+
config = Configurability::Config.load( @tmpfile.path, :defaultskey => 8 )
|
288
|
+
config.reload
|
289
|
+
config.defaultskey.should == 8
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
# merging
|
295
|
+
describe " created by merging two other configs" do
|
296
|
+
before(:each) do
|
297
|
+
@config1 = Configurability::Config.new
|
298
|
+
@config2 = Configurability::Config.new( TEST_CONFIG )
|
299
|
+
@merged = @config1.merge( @config2 )
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
### Specifications
|
304
|
+
it "should contain values from both" do
|
305
|
+
@merged.mergekey.should == @config2.mergekey
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
|
311
|
+
# vim: set nosta noet ts=4 sw=4:
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 1
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 1.0.
|
8
|
+
- 1
|
9
|
+
version: 1.0.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Michael Granger
|
@@ -14,11 +14,16 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-08-08 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
21
|
-
description:
|
21
|
+
description: |-
|
22
|
+
Configurability is a mixin that allows you to add
|
23
|
+
configurability to one or more classes, assign them
|
24
|
+
each a section of the configuration, and then when
|
25
|
+
the configuration is loaded, the class is given the
|
26
|
+
section it requested.
|
22
27
|
email:
|
23
28
|
- ged@FaerieMUD.org
|
24
29
|
executables: []
|
@@ -34,8 +39,11 @@ files:
|
|
34
39
|
- ChangeLog
|
35
40
|
- README.md
|
36
41
|
- LICENSE
|
42
|
+
- spec/configurability/config_spec.rb
|
37
43
|
- spec/configurability_spec.rb
|
38
44
|
- spec/lib/helpers.rb
|
45
|
+
- lib/configurability/behavior.rb
|
46
|
+
- lib/configurability/config.rb
|
39
47
|
- lib/configurability/logformatter.rb
|
40
48
|
- lib/configurability.rb
|
41
49
|
- rake/191_compat.rb
|
@@ -86,5 +94,6 @@ signing_key:
|
|
86
94
|
specification_version: 3
|
87
95
|
summary: A configurability mixin for Ruby
|
88
96
|
test_files:
|
97
|
+
- spec/configurability/config_spec.rb
|
89
98
|
- spec/configurability_spec.rb
|
90
99
|
- spec/lib/helpers.rb
|