settable 1.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
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 = "1.1"
3
-
4
- module Rails
5
- DEFAULT_ENVIRONMENTS = [:development, :production, :test]
6
- CUSTOM_ENVIRONMENTS = []
7
-
8
- # allow us to add custom environment helpers
9
- def define_environments(*envs)
10
- envs.each do |env|
11
- CUSTOM_ENVIRONMENTS << env
12
- define_metaclass_method(:"in_#{env}"){ |&block| in_environment(env.to_sym, &block) }
13
- define_metaclass_method(:"in_#{env}?"){ in_environment?(env.to_sym) }
14
- end
15
- end
16
- alias_method :define_environment, :define_environments
17
-
18
- # create our default environments
19
- DEFAULT_ENVIRONMENTS.each do |env|
20
- define_method(:"in_#{env}"){ |&block| in_environment(env.to_sym, &block) }
21
- define_method(:"in_#{env}?"){ in_environment?(env.to_sym) }
22
- end
23
-
24
- # helper method that will call the block if the Rails.env matches the given environments
25
- def in_environments(*envs, &block)
26
- block.call if envs.include?(::Rails.env.to_sym)
27
- end
28
- alias_method :in_environment, :in_environments
29
-
30
- # tests if we're in the given environment(s)
31
- def in_environments?(*envs)
32
- envs.include?(::Rails.env.to_sym)
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 define_metaclass_method(method, &block)
38
- (class << self; self; end).send :define_method, method, &block
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
- # modified from sinatra
47
- def set(key, value=nil, &block)
48
- raise ArgumentError, "You must specify either a block or value" if block_given? && !value.nil?
49
- value = block if block_given?
50
- if value.is_a?(Proc)
51
- __settables__ << key
52
- define_metaclass_method key, &value
53
- define_metaclass_method(:"#{key}?"){ !!__send__(key) }
54
- else
55
- set key, Proc.new{value}
56
- end
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
- def namespace(name, &block)
60
- set name, Namespace.create(self, &block)
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
- def enable(*keys)
64
- keys.each{ |key| set key, true }
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
- def disable(*keys)
68
- keys.each{ |key| set key, false }
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
- def self.create(base, &block)
73
- include Settable
74
- klass = new
75
-
76
- # good lord this is hack. but we need to re-define the custom environments in our
77
- # namespaces
78
- if base.class.ancestors.include?(Settable::Rails)
79
- include Settable::Rails
80
- klass.instance_eval{ define_environments *CUSTOM_ENVIRONMENTS }
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.instance_eval(&block)
84
- klass
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
- $:.push File.expand_path("../lib", __FILE__)
3
- require "settable"
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 |s|
6
- s.name = "settable"
7
- s.version = Settable::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.authors = ["Rob Hurring"]
10
- s.email = ["robhurring@gmail.com"]
11
- s.homepage = ""
12
- s.summary = %q{Small include to make config files better}
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
- s.rubyforge_project = "settable"
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
- s.files = `git ls-files`.split("\n")
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