configatronn 4.5.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +27 -0
  3. data/.gitignore +22 -0
  4. data/Gemfile +6 -0
  5. data/History.txt +82 -0
  6. data/LICENSE.txt +23 -0
  7. data/README.md +312 -0
  8. data/Rakefile +15 -0
  9. data/configatronn.gemspec +28 -0
  10. data/lib/configatron/core.rb +6 -0
  11. data/lib/configatron/delayed.rb +2 -0
  12. data/lib/configatron/dynamic.rb +7 -0
  13. data/lib/configatron/errors.rb +9 -0
  14. data/lib/configatron/ext/kernel.rb +5 -0
  15. data/lib/configatron/integrations/minitest.rb +29 -0
  16. data/lib/configatron/integrations/rails.rb +53 -0
  17. data/lib/configatron/integrations.rb +4 -0
  18. data/lib/configatron/proc.rb +34 -0
  19. data/lib/configatron/root_store.rb +126 -0
  20. data/lib/configatron/store.rb +182 -0
  21. data/lib/configatron/version.rb +3 -0
  22. data/lib/configatron.rb +23 -0
  23. data/lib/generators/configatron/install/install_generator.rb +24 -0
  24. data/lib/generators/configatron/install/templates/configatron/defaults.rb +7 -0
  25. data/lib/generators/configatron/install/templates/configatron/development.rb +4 -0
  26. data/lib/generators/configatron/install/templates/configatron/production.rb +4 -0
  27. data/lib/generators/configatron/install/templates/configatron/test.rb +4 -0
  28. data/lib/generators/configatron/install/templates/initializers/configatron.rb +2 -0
  29. data/test/_lib.rb +17 -0
  30. data/test/functional/_lib/scripts/core.rb +11 -0
  31. data/test/functional/_lib.rb +10 -0
  32. data/test/functional/configatron.rb +194 -0
  33. data/test/functional/delayed.rb +18 -0
  34. data/test/functional/loading.rb +10 -0
  35. data/test/functional/minitest.rb +18 -0
  36. data/test/functional/rails.rb +94 -0
  37. data/test/unit/_lib.rb +10 -0
  38. data/test/unit/configatron/proc.rb +24 -0
  39. data/test/unit/configatron/root_store.rb +37 -0
  40. data/test/unit/configatron/store.rb +149 -0
  41. metadata +203 -0
@@ -0,0 +1,126 @@
1
+ require 'singleton'
2
+ require 'forwardable'
3
+
4
+ # This is the root configatron object, and contains methods which
5
+ # operate on the entire configatron hierarchy.
6
+ class Configatron::RootStore < BasicObject
7
+ include ::Singleton
8
+ extend ::Forwardable
9
+
10
+ attr_reader :store
11
+
12
+ # Have one global RootStore instance, but allow people to create
13
+ # their own parallel ones if they desire.
14
+ class << self
15
+ public :new
16
+ end
17
+
18
+ @@cow = 0
19
+
20
+ def initialize
21
+ @locked = false
22
+ @cow = nil
23
+
24
+ @temporary_locks = []
25
+ @temporary_states = []
26
+
27
+ reset!
28
+ end
29
+
30
+ def __cow
31
+ @cow
32
+ end
33
+
34
+ def __cow_path(path)
35
+ start = @store.__cow_clone
36
+
37
+ node = start
38
+ branch = path.map do |key|
39
+ node = node[key]
40
+ node.__cow_clone
41
+ end
42
+ nodes = [start] + branch
43
+
44
+ # [node1, node2, node3] with
45
+ # [node2, node3, node4] and
46
+ # ['key1', 'key2, 'key3']
47
+ nodes[0...-1].zip(nodes[1..-1], path) do |parent, child, key|
48
+ # These are all cow_clones, so won't trigger a further cow
49
+ # modification.
50
+ parent[key] = child
51
+ end
52
+
53
+ @store = nodes.first
54
+ nodes.last
55
+ end
56
+
57
+ def method_missing(name, *args, &block)
58
+ store.__send__(name, *args, &block)
59
+ end
60
+
61
+ def reset!
62
+ @store = ::Configatron::Store.new(self)
63
+ end
64
+
65
+ def temp(&block)
66
+ temp_start
67
+
68
+ begin
69
+ yield
70
+ ensure
71
+ temp_end
72
+ end
73
+ end
74
+
75
+ def temp_start
76
+ @temp_cow = @cow
77
+
78
+ # Just need to have a unique Copy-on-Write generation ID
79
+ @cow = @@cow += 1
80
+
81
+ @temporary_locks.push(@locked)
82
+ @temporary_states.push(@store)
83
+ end
84
+
85
+ def temp_end
86
+ @cow = @temp_cow
87
+
88
+ @locked = @temporary_locks.pop
89
+ @store = @temporary_states.pop
90
+ end
91
+
92
+ def locked?
93
+ @locked
94
+ end
95
+
96
+ def lock!(&blk)
97
+ if blk
98
+ orig = @locked
99
+ begin
100
+ @locked = true
101
+ blk.call
102
+ ensure
103
+ @locked = orig
104
+ end
105
+ else
106
+ @locked = true
107
+ end
108
+ end
109
+
110
+ def unlock!(&blk)
111
+ if blk
112
+ orig = @locked
113
+ begin
114
+ @locked = false
115
+ blk.call
116
+ ensure
117
+ @locked = orig
118
+ end
119
+ else
120
+ @locked = false
121
+ end
122
+ end
123
+
124
+ def_delegator :@store, :to_s
125
+ def_delegator :@store, :inspect
126
+ end
@@ -0,0 +1,182 @@
1
+ require 'forwardable'
2
+
3
+ class Configatron
4
+ class Store < BasicObject
5
+ extend ::Forwardable
6
+
7
+ def initialize(root_store, name='configatron', attributes={}, path=[])
8
+ @root_store = root_store
9
+ @name = name
10
+ @attributes = attributes
11
+
12
+ # We could derive @name from @path, though this would break
13
+ # backwards-compatibility.
14
+ @path = path
15
+
16
+ @cow = root_store.__cow
17
+ end
18
+
19
+ def clone
20
+ Store.new(
21
+ @root_store,
22
+ @name,
23
+ @attributes.clone,
24
+ @path
25
+ )
26
+ end
27
+
28
+ def __cow
29
+ @cow
30
+ end
31
+
32
+ def __cow_clone
33
+ # A temp has started since the last time this was written
34
+ if @root_store.__cow != @cow
35
+ self.clone
36
+ else
37
+ self
38
+ end
39
+ end
40
+
41
+ def [](key)
42
+ val = fetch(key.to_sym) do
43
+ if @root_store.locked?
44
+ ::Kernel.raise ::Configatron::UndefinedKeyError.new("Key not found: #{key} (for locked #{self})")
45
+ end
46
+ ::Configatron::Store.new(@root_store, "#{@name}.#{key}", {}, @path + [key])
47
+ end
48
+ return val
49
+ end
50
+
51
+ def store(key, value)
52
+ if @root_store.locked?
53
+ ::Kernel.raise ::Configatron::LockedError.new("Cannot set key #{key} for locked #{self}")
54
+ end
55
+
56
+ key = key.to_sym
57
+ if @root_store.__cow != @cow
58
+ copy = @root_store.__cow_path(@path)
59
+ # Cow should now match, so this won't recurse. (Note this is
60
+ # not particularly thread-safe.)
61
+ copy.store(key, value)
62
+ else
63
+ @attributes[key] = value
64
+ end
65
+ end
66
+
67
+ def fetch(key, default_value = nil, &block)
68
+ key = key.to_sym
69
+ if key?(key)
70
+ val = @attributes[key]
71
+ else
72
+ if block
73
+ val = block.call
74
+ elsif default_value
75
+ val = default_value
76
+ end
77
+ store(key, val)
78
+ end
79
+ if ::Configatron::Proc === val
80
+ val = val.call
81
+ end
82
+ return val
83
+ end
84
+
85
+ def key?(key)
86
+ @attributes.key?(key.to_sym)
87
+ end
88
+
89
+ def configure_from_hash(hash)
90
+ hash.each do |key, value|
91
+ if ::Hash === value
92
+ self[key].configure_from_hash(value)
93
+ else
94
+ store(key, value)
95
+ end
96
+ end
97
+ end
98
+
99
+ def to_s
100
+ @name
101
+ end
102
+
103
+ def inspect
104
+ f_out = []
105
+ @attributes.each do |k, v|
106
+ if ::Configatron::Store === v
107
+ v.inspect.each_line do |line|
108
+ if line.match(/\n/)
109
+ line.each_line do |l|
110
+ l.strip!
111
+ f_out << l
112
+ end
113
+ else
114
+ line.strip!
115
+ f_out << line
116
+ end
117
+ end
118
+ else
119
+ f_out << "#{@name}.#{k} = #{v.inspect}"
120
+ end
121
+ end
122
+ f_out.compact.sort.join("\n")
123
+ end
124
+
125
+ def method_missing(name, *args, &block)
126
+ do_lookup(name, *args, &block)
127
+ end
128
+
129
+ def to_h
130
+ @attributes.each_with_object({}) do |(k, v), h|
131
+ v = v.call if ::Configatron::Proc === v
132
+ h[k] = Store === v ? v.to_h : v
133
+ end
134
+ end
135
+
136
+ # So that puts works (it expects the object to respond to to_ary)
137
+ def to_ary
138
+ nil
139
+ end
140
+
141
+ # So that we keep backward-compatibility in case people are using nil? to check
142
+ # configatron settings:
143
+ def nil?
144
+ false
145
+ end
146
+
147
+ private
148
+
149
+ def do_lookup(name, *args, &block)
150
+ if block
151
+ yield self[name]
152
+ else
153
+ name = name.to_s
154
+ if name.end_with?('=')
155
+ return store(name[0..-2], args[0])
156
+ elsif name.end_with?('!')
157
+ key = name[0..-2]
158
+ if self.has_key?(key)
159
+ return self[key]
160
+ else
161
+ ::Kernel.raise ::Configatron::UndefinedKeyError.new($1)
162
+ end
163
+ else
164
+ return self[name]
165
+ end
166
+ end
167
+ end
168
+
169
+ alias :[]= :store
170
+ alias :has_key? :key?
171
+ alias :to_hash :to_h
172
+
173
+ def_delegator :@attributes, :values
174
+ def_delegator :@attributes, :keys
175
+ def_delegator :@attributes, :each
176
+ def_delegator :@attributes, :delete
177
+ # def_delegator :@attributes, :fetch
178
+ # def_delegator :@attributes, :has_key?
179
+ # def_delegator :$stdout, :puts
180
+
181
+ end
182
+ end
@@ -0,0 +1,3 @@
1
+ class Configatron
2
+ VERSION = "4.5.2"
3
+ end
@@ -0,0 +1,23 @@
1
+ require 'configatron/version'
2
+
3
+ require 'configatron/errors'
4
+ require 'configatron/integrations'
5
+ require 'configatron/root_store'
6
+ require 'configatron/store'
7
+
8
+ # Proc *must* load before dynamic/delayed, or else Configatron::Proc
9
+ # will refer to the global ::Proc
10
+ require 'configatron/proc'
11
+ require 'configatron/delayed'
12
+ require 'configatron/dynamic'
13
+
14
+ class Configatron
15
+ end
16
+
17
+ # NO_EXT gets defined when you require "configatron/core", which
18
+ # signals that you don't want any extensions. It'd be nice to have a
19
+ # better internal signaling mechanism (could use environment
20
+ # variables, but then they become part of the public interface).
21
+ unless defined?(Configatron::NO_EXT)
22
+ require 'configatron/ext/kernel'
23
+ end
@@ -0,0 +1,24 @@
1
+ require 'rails/generators/base'
2
+
3
+ class Configatron
4
+ class InstallGenerator < ::Rails::Generators::Base #:nodoc:
5
+
6
+ desc 'Generates configatron files for the default Rails environments.'
7
+
8
+ def self.source_root
9
+ @_configatron_source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
10
+ end
11
+
12
+ def self.banner
13
+ "rails generate configatron:install"
14
+ end
15
+
16
+ def copy_files
17
+ template 'initializers/configatron.rb', 'config/initializers/configatron.rb'
18
+ %w{defaults development production test}.each do |env|
19
+ template "configatron/#{env}.rb", "config/configatron/#{env}.rb"
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ # Put all your default configatron settings here.
2
+
3
+ # Example:
4
+ # configatron.emails.welcome.subject = 'Welcome!'
5
+ # configatron.emails.sales_reciept.subject = 'Thanks for your order'
6
+ #
7
+ # configatron.file.storage = :s3
@@ -0,0 +1,4 @@
1
+ # Override your default settings for the Development environment here.
2
+ #
3
+ # Example:
4
+ # configatron.file.storage = :local
@@ -0,0 +1,4 @@
1
+ # Override your default settings for the Production environment here.
2
+ #
3
+ # Example:
4
+ # configatron.file.storage = :s3
@@ -0,0 +1,4 @@
1
+ # Override your default settings for the Test environment here.
2
+ #
3
+ # Example:
4
+ # configatron.file.storage = :local
@@ -0,0 +1,2 @@
1
+ require 'configatron'
2
+ Configatron::Integrations::Rails.init
data/test/_lib.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ ENV['MT_NO_EXPECTATIONS'] = 'true'
5
+ require 'minitest/autorun'
6
+ require 'minitest/spec'
7
+ require 'mocha/minitest'
8
+
9
+ require 'configatron'
10
+
11
+ module Critic
12
+ class Test < ::Minitest::Spec
13
+ def setup
14
+ # Put any stubs here that you want to apply globally
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if defined?(configatron)
4
+ raise "`configatron` method was defined at load-time!"
5
+ end
6
+
7
+ require 'configatron/core'
8
+
9
+ if defined?(configatron)
10
+ raise "Loaded configatron/core but `configatron` was defined!"
11
+ end
@@ -0,0 +1,10 @@
1
+ require File.expand_path('../../_lib', __FILE__)
2
+
3
+ module Critic::Functional
4
+ module Stubs
5
+ end
6
+
7
+ class Test < Critic::Test
8
+ include Stubs
9
+ end
10
+ end
@@ -0,0 +1,194 @@
1
+ require_relative '_lib'
2
+
3
+ class Critic::Functional::ConfigatronTest < Critic::Functional::Test
4
+ before do
5
+ @kernel = Configatron::RootStore.new
6
+ end
7
+
8
+ describe 'temp' do
9
+ before do
10
+ @kernel.a = 'A'
11
+ @kernel.b = 'B'
12
+ end
13
+
14
+ it 'allows for temporary setting of values' do
15
+ assert_equal('A', @kernel.a)
16
+ assert_equal('B', @kernel.b)
17
+ @kernel.temp do
18
+ @kernel.a = 'AA'
19
+ @kernel.c = 'C'
20
+ assert_equal('AA', @kernel.a)
21
+ assert_equal('B', @kernel.b)
22
+ assert_equal('C', @kernel.c)
23
+ end
24
+ assert_equal('A', @kernel.a)
25
+ assert_equal('B', @kernel.b)
26
+ assert_equal(false, @kernel.key?(:c))
27
+ end
28
+
29
+ it 'nested' do
30
+ @kernel.foo.bar = 'original'
31
+ @kernel.temp do
32
+ @kernel.foo.bar = 'temp'
33
+ assert_equal('temp', @kernel.foo.bar)
34
+ end
35
+ assert_equal('original', @kernel.foo.bar)
36
+ end
37
+
38
+ it 'handles nested temps' do
39
+ @kernel.temp do
40
+ @kernel.a = 'Z'
41
+ @kernel.temp do
42
+ @kernel.a = 'Y'
43
+ assert_equal('Y', @kernel.a)
44
+ end
45
+ assert_equal('Z', @kernel.a)
46
+ end
47
+ assert_equal('A', @kernel.a)
48
+ end
49
+
50
+ it 'cleans up after an exception' do
51
+ @kernel.foo.bar = 'original'
52
+
53
+ assert_raises(RuntimeError) do
54
+ @kernel.temp do
55
+ @kernel.foo.bar = 'temp'
56
+ raise RuntimeError.new('error')
57
+ end
58
+ end
59
+
60
+ assert_equal('original', @kernel.foo.bar)
61
+ end
62
+
63
+ it 'restores locking state' do
64
+ @kernel.lock!
65
+ @kernel.temp do
66
+ @kernel.unlock!
67
+ end
68
+ assert(@kernel.locked?)
69
+ end
70
+
71
+ it 'restores locking state in nested temps' do
72
+ @kernel.lock!
73
+ @kernel.temp do
74
+ @kernel.unlock!
75
+ @kernel.temp do
76
+ @kernel.lock!
77
+ end
78
+ end
79
+ assert(@kernel.locked?)
80
+ end
81
+
82
+ describe 'start/end' do
83
+ it 'allows for temporary setting of values' do
84
+ assert_equal('A', @kernel.a)
85
+ assert_equal('B', @kernel.b)
86
+ @kernel.temp_start
87
+ @kernel.a = 'AA'
88
+ @kernel.c = 'C'
89
+ assert_equal('AA', @kernel.a)
90
+ assert_equal('B', @kernel.b)
91
+ assert_equal('C', @kernel.c)
92
+ @kernel.temp_end
93
+ assert_equal('A', @kernel.a)
94
+ assert_equal('B', @kernel.b)
95
+ assert_equal(false, @kernel.key?(:c))
96
+ end
97
+ end
98
+ end
99
+
100
+ describe 'lock!' do
101
+ before do
102
+ @kernel.a.b.c.d = 'DD'
103
+ @kernel.lock!
104
+ end
105
+
106
+ it 'raises an error when accessing non-existing values' do
107
+ assert @kernel.a != nil
108
+ assert @kernel.a.b != nil
109
+ assert @kernel.a.b.c != nil
110
+ assert_equal('DD', @kernel.a.b.c.d)
111
+ assert_raises(Configatron::UndefinedKeyError) do
112
+ @kernel.unknown
113
+ end
114
+ end
115
+
116
+ it 'responds to nil? for backward compatibility' do
117
+ refute_nil @kernel.a
118
+ end
119
+
120
+ it 'raises an error when trying to set a non-existing key' do
121
+ assert_raises(Configatron::LockedError) do
122
+ @kernel.unknown = 'known'
123
+ end
124
+ end
125
+
126
+ it 'locks during the block argument' do
127
+ @kernel.unlock!
128
+
129
+ @kernel.lock! do
130
+ assert(@kernel.locked?)
131
+ end
132
+
133
+ assert(!@kernel.locked?)
134
+ end
135
+
136
+ it 'executes a block argument' do
137
+ a = 1
138
+ @kernel.lock! do
139
+ a = 2
140
+ end
141
+ assert_equal(2, a)
142
+ end
143
+ end
144
+
145
+ describe 'name' do
146
+ it 'assigns an appropriate nested name' do
147
+ name = @kernel.foo.bar.baz.to_s
148
+ assert_equal(name, 'configatron.foo.bar.baz')
149
+ end
150
+ end
151
+
152
+ describe 'puts' do
153
+ it 'does not cause an exception' do
154
+ original_stdout = $stdout
155
+ $stdout = File.open(File::NULL, "w")
156
+ begin
157
+ puts @kernel
158
+ puts @kernel.hi
159
+ ensure
160
+ $stdout = original_stdout
161
+ end
162
+ end
163
+ end
164
+
165
+ describe 'private methods on kernel' do
166
+ it 'can be accessed through method accessors' do
167
+ @kernel.catch = 'hi'
168
+ @kernel.foo.catch = 'hi'
169
+
170
+ assert_equal('hi', @kernel.catch)
171
+ assert_equal('hi', @kernel.foo.catch)
172
+ end
173
+ end
174
+
175
+ describe 'to_h and to_hash' do
176
+ before do
177
+ @kernel.a = 1
178
+ @kernel.b.c = Configatron::Delayed.new{ @kernel.a + 3 }
179
+ end
180
+
181
+ it 'returns a hash representation' do
182
+ expected_hash = { a: 1, b: { c: 4 } }
183
+ assert_equal(expected_hash, @kernel.to_h)
184
+ assert_equal(expected_hash, @kernel.to_hash)
185
+ end
186
+ end
187
+
188
+ describe 'nil value' do
189
+ it 'remembers a nil value' do
190
+ @kernel.a = nil
191
+ assert_nil(@kernel.a)
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,18 @@
1
+ require_relative '_lib'
2
+
3
+ class Critic::Functional::DelayedTest < Critic::Functional::Test
4
+ before do
5
+ @kernel = Configatron::RootStore.new
6
+ end
7
+
8
+ describe 'delayed' do
9
+ before do
10
+ @kernel.a = Configatron::Delayed.new { @kernel.b }
11
+ @kernel.b = "expected"
12
+ end
13
+
14
+ it 'works independent of the order' do
15
+ assert_equal('expected', @kernel.a)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ require_relative '_lib'
2
+ require 'subprocess'
3
+
4
+ class Critic::Functional::ConfigatronTest < Critic::Functional::Test
5
+ describe 'loading' do
6
+ it 'does not define top-level configatron method if loading configatron/core' do
7
+ Subprocess.check_call([File.expand_path('../_lib/scripts/core.rb', __FILE__)])
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ require_relative '_lib'
2
+ require 'subprocess'
3
+
4
+ class Critic::Functional::MinitestTest < Critic::Functional::Test
5
+ include Configatron::Integrations::Minitest
6
+
7
+ describe 'multiple runs' do
8
+ it 'settings get reset: 1' do
9
+ assert(!configatron.key?(:my_crazy_setting))
10
+ configatron.my_crazy_setting = true
11
+ end
12
+
13
+ it 'settings get reset: 2' do
14
+ assert(!configatron.key?(:my_crazy_setting))
15
+ configatron.my_crazy_setting = true
16
+ end
17
+ end
18
+ end