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