settable 1.1 → 3.0.2
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 +7 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Readme.md +127 -64
- data/lib/settable/environment/env.rb +9 -0
- data/lib/settable/environment/rails.rb +10 -0
- data/lib/settable.rb +411 -74
- data/settable.gemspec +16 -16
- data/spec/settable_spec.rb +261 -0
- data/spec/spec_helper.rb +8 -0
- metadata +32 -16
- data/examples/example.rb +0 -43
- data/examples/rails.rb +0 -65
- data/test/settable_test.rb +0 -252
data/lib/settable.rb
CHANGED
@@ -1,87 +1,424 @@
|
|
1
|
+
# Settings module that can be easily included into classes that store your
|
2
|
+
# applications configuration.
|
3
|
+
#
|
4
|
+
# Examples
|
5
|
+
#
|
6
|
+
# $config = Settable.configure do
|
7
|
+
# # basic set, similar to capistrano and sinatra
|
8
|
+
# set :username, 'user'
|
9
|
+
# set :password, 's3kr1t'
|
10
|
+
#
|
11
|
+
# # namespace support to keep config clean
|
12
|
+
# namespace :tracking do
|
13
|
+
# set :enabled, true
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# set :block do
|
17
|
+
# 'blocks are allowed too!'
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# $config.username
|
22
|
+
# # => 'user'
|
23
|
+
#
|
24
|
+
# # Using presence methods, detect if theres a 'truthy' value
|
25
|
+
# $config.username?
|
26
|
+
# # => true
|
27
|
+
#
|
28
|
+
# $config.tracking.enabled?
|
29
|
+
# # => true
|
30
|
+
#
|
1
31
|
module Settable
|
2
|
-
VERSION =
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
|
32
|
-
|
32
|
+
VERSION = '3.0.2'
|
33
|
+
|
34
|
+
# name of the top-level namespace when none is provided
|
35
|
+
ROOT_NAMESPACE = :__settable__
|
36
|
+
|
37
|
+
# Mixin to make a settable DSL.
|
38
|
+
#
|
39
|
+
# Examples
|
40
|
+
#
|
41
|
+
# class MyApp
|
42
|
+
# include Settable
|
43
|
+
#
|
44
|
+
# settable :config do
|
45
|
+
# set :hello, 'world'
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# # can be used as a class method or an instance method
|
50
|
+
# MyApp.config == MyApp.new.config
|
51
|
+
# # => true
|
52
|
+
#
|
53
|
+
# MyApp.config.hello
|
54
|
+
# # => 'world'
|
55
|
+
#
|
56
|
+
module DSL
|
57
|
+
# Public: Mixin method to add the +settable+ class method.
|
58
|
+
#
|
59
|
+
# name - The method name to store your settings under.
|
60
|
+
# block - Your settings block.
|
61
|
+
#
|
62
|
+
# Examples
|
63
|
+
#
|
64
|
+
# class MyApp
|
65
|
+
# include Settable
|
66
|
+
#
|
67
|
+
# settable :settings do
|
68
|
+
# set :hello, 'world'
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# MyApp.settings.hello
|
73
|
+
# # => 'world'
|
74
|
+
#
|
75
|
+
# Creates a class and instance method with the name +name+ that contains
|
76
|
+
# your settings.
|
77
|
+
def settable(name, &block)
|
78
|
+
metaclass = (class << self; self; end)
|
79
|
+
metaclass.__send__(:define_method, name){ Namespace.new(name, &block) }
|
80
|
+
self.__send__(:define_method, name){ self.class.__send__(name) }
|
33
81
|
end
|
34
|
-
alias_method :in_environment?, :in_environments?
|
35
82
|
end
|
36
|
-
|
37
|
-
def
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
# list
|
42
|
-
def __settables__
|
43
|
-
@__settables__ ||= []
|
83
|
+
|
84
|
+
def self.included(base)
|
85
|
+
base.extend DSL
|
44
86
|
end
|
45
|
-
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
87
|
+
|
88
|
+
# Public: Standalone configuration helper.
|
89
|
+
#
|
90
|
+
# block - The settings block.
|
91
|
+
#
|
92
|
+
# Examples
|
93
|
+
#
|
94
|
+
# $config = Settable.configure do
|
95
|
+
# set :hello, 'world'
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# $config.hello
|
99
|
+
# # => 'world'
|
100
|
+
#
|
101
|
+
# Returns the settable object.
|
102
|
+
def self.configure(&block)
|
103
|
+
Namespace.new(ROOT_NAMESPACE, &block)
|
57
104
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
105
|
+
|
106
|
+
# Private: Environment testers for using the +environment+ helper methods within
|
107
|
+
# the settings.
|
108
|
+
module Environment
|
109
|
+
autoload :Rails, 'settable/environment/rails'
|
110
|
+
autoload :Env, 'settable/environment/env'
|
61
111
|
end
|
62
112
|
|
63
|
-
|
64
|
-
|
113
|
+
# Private: Wrapper around the setting's value.
|
114
|
+
# Returns the wrapped value.
|
115
|
+
class SettingBlock
|
116
|
+
# Public: Create a setting block in the given namespace.
|
117
|
+
#
|
118
|
+
# namespace - The namespace to operate in.
|
119
|
+
# block - The block to call when retrieving the setting.
|
120
|
+
#
|
121
|
+
# Returns the setting value block.
|
122
|
+
def initialize(namespace, &block)
|
123
|
+
@block = lambda &block
|
124
|
+
@namespace = namespace
|
125
|
+
@environment = namespace.environment
|
126
|
+
end
|
127
|
+
|
128
|
+
# Public: Retrieve the value from this block, if we are using environment
|
129
|
+
# checkers then we will check the environment and override any
|
130
|
+
# default values
|
131
|
+
#
|
132
|
+
# Returns the value for the setting.
|
133
|
+
def call
|
134
|
+
@__env_return_value__ = nil # avoid using throw/catch
|
135
|
+
default_return_value = instance_eval &@block
|
136
|
+
@__env_return_value__ || default_return_value
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# Private: The root namespace, useful for calling back into other settings
|
142
|
+
# and retrieving their values.
|
143
|
+
#
|
144
|
+
# Returns the topmost namespace.
|
145
|
+
def root
|
146
|
+
@namespace.root
|
147
|
+
end
|
148
|
+
|
149
|
+
# Private: Override the default value if our environment matches the
|
150
|
+
# passed in arguments.
|
151
|
+
#
|
152
|
+
# name_or_names - The values to match against our environment checker.
|
153
|
+
# value - A single value to return if the environment is matched.
|
154
|
+
# block - A block to run if the environment is matched.
|
155
|
+
#
|
156
|
+
# Examples
|
157
|
+
#
|
158
|
+
# $config = Settable.configure do
|
159
|
+
# use_environment do |value|
|
160
|
+
# # value is given to us by the +environment+ helper below
|
161
|
+
# value.to_s == 'production'
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# set :hello do
|
165
|
+
# environment :production, 'production'
|
166
|
+
# 'world'
|
167
|
+
# end
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# # returns production since our +use_environment's value+ will
|
171
|
+
# # come in as :production, and a match is made
|
172
|
+
# $config.hello
|
173
|
+
# # => 'production'
|
174
|
+
#
|
175
|
+
# Returns the value for the given environment, or nil if no environment
|
176
|
+
# checker is being used or there isn't a match.
|
177
|
+
def environment(name_or_names, value = nil, &block)
|
178
|
+
return @__env_return_value__ if @__env_return_value__
|
179
|
+
return unless @environment
|
180
|
+
|
181
|
+
if Array(name_or_names).any?{ |n| @environment.call(n) }
|
182
|
+
# store this and cache it so we can return it
|
183
|
+
@__env_return_value__ = block_given? ? block.call : value
|
184
|
+
end
|
185
|
+
end
|
65
186
|
end
|
66
|
-
|
67
|
-
|
68
|
-
|
187
|
+
|
188
|
+
# Private: Wrapper around our settings.
|
189
|
+
class Setting
|
190
|
+
# Public: Create a new setting.
|
191
|
+
#
|
192
|
+
# namespace - The namespace to create this setting in.
|
193
|
+
# key - The setting name.
|
194
|
+
# value - The value to return for this setting (if not block given).
|
195
|
+
# block - The block to run when this setting is retrieved.
|
196
|
+
#
|
197
|
+
# Examples
|
198
|
+
#
|
199
|
+
# setting = Setting.new(namespace, :hello, 'world')
|
200
|
+
# setting.value
|
201
|
+
# # => 'world'
|
202
|
+
#
|
203
|
+
# setting = Setting.new(namespace, :hello){ 'ohai' }
|
204
|
+
# setting.value
|
205
|
+
# # => 'ohai'
|
206
|
+
#
|
207
|
+
# Returns the setting object.
|
208
|
+
def initialize(namespace, key, value, &block)
|
209
|
+
@key = key
|
210
|
+
value = SettingBlock.new(namespace, &block) if block_given?
|
211
|
+
@value = value
|
212
|
+
end
|
213
|
+
|
214
|
+
# Public: Retrieve the settings value. If a block was given in the
|
215
|
+
# constructor, it runs the block - otherwise it will return
|
216
|
+
# the value given in the constructor.
|
217
|
+
#
|
218
|
+
# Returns the value.
|
219
|
+
def value
|
220
|
+
if @value.respond_to?(:call)
|
221
|
+
@value.call
|
222
|
+
else
|
223
|
+
@value
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Public: Presence method for the setting.
|
228
|
+
# Returns the truthiness of the value.
|
229
|
+
def present?
|
230
|
+
!!value
|
231
|
+
end
|
69
232
|
end
|
70
|
-
|
233
|
+
|
234
|
+
# Private: A container for multiple settings. Every setting belongs to a
|
235
|
+
# namespace group.
|
71
236
|
class Namespace
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
237
|
+
attr_reader :name, :parent, :environment
|
238
|
+
|
239
|
+
# Public: Create a new namespace to store settings in. Namespaces inherit
|
240
|
+
# their parent's environment checker by default, but that may be
|
241
|
+
# overridden using +use_environment+
|
242
|
+
#
|
243
|
+
# name - The namespace name.
|
244
|
+
# parent - The parent namespace when nesting namespaces.
|
245
|
+
# block - A block containing all your +set+ calls
|
246
|
+
#
|
247
|
+
# Examples
|
248
|
+
#
|
249
|
+
# namespace = Namespace.new(:global, nil) do
|
250
|
+
# set :hello, 'world'
|
251
|
+
# end
|
252
|
+
#
|
253
|
+
# namespace.hello
|
254
|
+
# # => 'world'
|
255
|
+
#
|
256
|
+
# Returns the namespace to attach settings to.
|
257
|
+
def initialize(name, parent = nil, &block)
|
258
|
+
@name = name
|
259
|
+
@environment = parent ? parent.environment : nil
|
260
|
+
@parent = parent
|
261
|
+
instance_eval &block
|
262
|
+
end
|
263
|
+
|
264
|
+
# Public: A custom checker when using the Setting's +environment+ helper.
|
265
|
+
# You can pass a block in which will receive the +environment+ value
|
266
|
+
# and should return a true/false to indicate a match. All nested
|
267
|
+
# namespaces will inherit their parent environment by default.
|
268
|
+
#
|
269
|
+
# klass - A Class which responds to a #call(value) method, or a symbol of
|
270
|
+
# either :rails, or :env for builtin testers
|
271
|
+
# block - A block that will be called with the environment to see if it is
|
272
|
+
# a match.
|
273
|
+
#
|
274
|
+
# Examples
|
275
|
+
#
|
276
|
+
# # check if the given value is in our ENV
|
277
|
+
# namespace.use_environment{ |value| ENV.has_key?(value) }
|
278
|
+
#
|
279
|
+
# # use the built-in tester that looks at Rails.env
|
280
|
+
# namespace.use_environment(:rails)
|
281
|
+
#
|
282
|
+
# # use a custom class that responds to #call(value)
|
283
|
+
# namespace.use_environment(MyCustomChecker.new)
|
284
|
+
#
|
285
|
+
# Returns the duplicated String.
|
286
|
+
def use_environment(klass = nil, &block)
|
287
|
+
if klass.nil?
|
288
|
+
if block_given?
|
289
|
+
klass = block
|
290
|
+
else
|
291
|
+
return
|
292
|
+
end
|
293
|
+
elsif klass.is_a?(Symbol)
|
294
|
+
klass = Object.module_eval("Settable::Environment::#{klass.to_s.capitalize}", __FILE__, __LINE__)
|
295
|
+
else
|
296
|
+
unless klass.is_a?(Object) && klass.respond_to?(:call)
|
297
|
+
raise "#{klass} must respond to #call(value) to be a valid environment!"
|
298
|
+
end
|
81
299
|
end
|
82
|
-
|
83
|
-
klass
|
84
|
-
|
300
|
+
|
301
|
+
@environment = klass
|
302
|
+
end
|
303
|
+
|
304
|
+
# Public: Create a setting method and its 'presence' method.
|
305
|
+
#
|
306
|
+
# name - The setting name.
|
307
|
+
# value - The settings value (unless a block is given).
|
308
|
+
# block - The block to run for this setting's value
|
309
|
+
#
|
310
|
+
# Examples
|
311
|
+
#
|
312
|
+
# $config = Settable.configure do
|
313
|
+
# set :hello, 'world'
|
314
|
+
# end
|
315
|
+
# $config.hello
|
316
|
+
# # => 'world'
|
317
|
+
#
|
318
|
+
# $config.hello?
|
319
|
+
# # => true
|
320
|
+
#
|
321
|
+
# $config = Settable.configure do
|
322
|
+
# set(:hello){ 'world' }
|
323
|
+
# end
|
324
|
+
# $config.hello
|
325
|
+
# # => 'world'
|
326
|
+
#
|
327
|
+
# Returns nothing.
|
328
|
+
def set(name, value = nil, &block)
|
329
|
+
setting = Setting.new(self, name, value, &block)
|
330
|
+
define_metaclass_method(name.to_sym){ setting.value }
|
331
|
+
define_metaclass_method(:"#{name}?"){ setting.present? }
|
332
|
+
end
|
333
|
+
|
334
|
+
# Public: Create a nested namespace.
|
335
|
+
#
|
336
|
+
# name - The name for the namespace.
|
337
|
+
# block - A block to run in this namespace's context.
|
338
|
+
#
|
339
|
+
# Examples
|
340
|
+
#
|
341
|
+
# $config = Settable.configure do
|
342
|
+
# set :username, 'user'
|
343
|
+
#
|
344
|
+
# namespace :api do
|
345
|
+
# set :username, 'api-user'
|
346
|
+
# end
|
347
|
+
# end
|
348
|
+
#
|
349
|
+
# $config.username
|
350
|
+
# # => 'user'
|
351
|
+
#
|
352
|
+
# $config.api.username
|
353
|
+
# # => 'api-user'
|
354
|
+
#
|
355
|
+
# Returns nothing.
|
356
|
+
def namespace(name, &block)
|
357
|
+
define_metaclass_method(name.to_sym) do
|
358
|
+
namespace = Namespace.new(name, self, &block)
|
359
|
+
namespace
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Public: Helper method for setting values based off our +environment+.
|
364
|
+
#
|
365
|
+
# values - An array of values to check against our +environment+
|
366
|
+
#
|
367
|
+
# Examples
|
368
|
+
#
|
369
|
+
# $config = Settable.configure do
|
370
|
+
# use_environment :rails
|
371
|
+
# set :google_analytics, environment_matches?(:production, :staging)
|
372
|
+
# end
|
373
|
+
#
|
374
|
+
# Rails.env = "production" # (or Rails.env = "staging")
|
375
|
+
# $config.google_analytics?
|
376
|
+
# # => true
|
377
|
+
#
|
378
|
+
# Rails.env = "development"
|
379
|
+
# $config.google_analytics?
|
380
|
+
# # => false
|
381
|
+
#
|
382
|
+
# Returns true if our environment matches any of our +values+.
|
383
|
+
def environment_matches?(*values)
|
384
|
+
Array(values).any?{ |v| @environment.call(v) }
|
385
|
+
end
|
386
|
+
|
387
|
+
# Public: Find the top-most namespace and return it.
|
388
|
+
# Examples
|
389
|
+
#
|
390
|
+
# $config = Settable.configure do
|
391
|
+
# set :hello, 'world'
|
392
|
+
#
|
393
|
+
# namespace :a do
|
394
|
+
# namespace :b do
|
395
|
+
# set(:test){ root.hello }
|
396
|
+
# end
|
397
|
+
# end
|
398
|
+
# end
|
399
|
+
#
|
400
|
+
# $config.a.b.test
|
401
|
+
# # => 'world'
|
402
|
+
#
|
403
|
+
# Returns the topmost namespace.
|
404
|
+
def root
|
405
|
+
@root ||= begin
|
406
|
+
root = self
|
407
|
+
root = root.parent until root.parent.nil?
|
408
|
+
root
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
private
|
413
|
+
|
414
|
+
# Private: Create a method on the metaclass.
|
415
|
+
#
|
416
|
+
# method - Method name.
|
417
|
+
# block - Method body.
|
418
|
+
#
|
419
|
+
# Returns nothing.
|
420
|
+
def define_metaclass_method(method, &block)
|
421
|
+
(class << self; self; end).__send__ :define_method, method, &block
|
85
422
|
end
|
86
423
|
end
|
87
|
-
end
|
424
|
+
end
|
data/settable.gemspec
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'settable'
|
4
5
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
s.description = %q{Small include to make config files better}
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'settable'
|
8
|
+
gem.version = Settable::VERSION
|
9
|
+
gem.authors = ['Rob Hurring']
|
10
|
+
gem.email = ['robhurring@gmail.com']
|
11
|
+
gem.summary = %q{Small include to make config files better}
|
12
|
+
gem.description = %q{Small include to make config files better}
|
13
|
+
gem.homepage = 'https://github.com/robhurring/settable'
|
14
14
|
|
15
|
-
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
16
19
|
|
17
|
-
|
18
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
-
s.require_paths = ["lib"]
|
20
|
+
gem.add_development_dependency 'rspec'
|
21
21
|
end
|