blueprint_config 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.idea/.gitignore +8 -0
- data/.idea/blue_config.iml +72 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +152 -0
- data/Rakefile +8 -0
- data/blueprint_config.gemspec +31 -0
- data/lib/blueprint_config/backend/active_record.rb +51 -0
- data/lib/blueprint_config/backend/base.rb +34 -0
- data/lib/blueprint_config/backend/credentials.rb +26 -0
- data/lib/blueprint_config/backend/env.rb +47 -0
- data/lib/blueprint_config/backend/yaml.rb +30 -0
- data/lib/blueprint_config/backend_collection.rb +67 -0
- data/lib/blueprint_config/configuration.rb +64 -0
- data/lib/blueprint_config/options_array.rb +81 -0
- data/lib/blueprint_config/options_hash.rb +112 -0
- data/lib/blueprint_config/setting.rb +28 -0
- data/lib/blueprint_config/version.rb +5 -0
- data/lib/blueprint_config/yaml.rb +46 -0
- data/lib/blueprint_config.rb +71 -0
- data/lib/generators/blueprint_config/install/USAGE +8 -0
- data/lib/generators/blueprint_config/install/install_generator.rb +23 -0
- data/lib/generators/blueprint_config/install/templates/migration.rb.erb +18 -0
- data/spec/backend_collection_spec.rb +103 -0
- data/spec/blueprint_config/backend/active_record_spec.rb +41 -0
- data/spec/blueprint_config/backend/env_spec.rb +53 -0
- data/spec/blueprint_config/backend/yaml_spec.rb +35 -0
- data/spec/blueprint_config/options_array_spec.rb +109 -0
- data/spec/blueprint_config/options_hash_spec.rb +211 -0
- data/spec/config/app.yml +24 -0
- data/spec/configuration_spec.rb +98 -0
- data/spec/spec_helper.rb +16 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: be61b7fe3cee8680edfeddde8bbd290aa4463fd68ea2b2582651f740afe4685b
|
4
|
+
data.tar.gz: 9e6f745b5acc18080066a2ffb482eee0d69f704010ef923b8cab0f960c10ba48
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e2849e6eb9237668d42fa9d7c10fe3075798e2cbc296a4a21d76429dcd921252d012a0c96672b69debc18f919d650dc8a2f1897f3401b664c6b6b688326c6719
|
7
|
+
data.tar.gz: c76bd8289d7075f53358f9d7035b9d8e3479c4dc081c40c2c826e1cb3f561803bbe2380f66b741b6dea13636b2337c7cef4587dd29d241cb59a04790f236c97e
|
data/.gitignore
ADDED
data/.idea/.gitignore
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<module type="RUBY_MODULE" version="4">
|
3
|
+
<component name="ModuleRunConfigurationManager">
|
4
|
+
<shared />
|
5
|
+
</component>
|
6
|
+
<component name="NewModuleRootManager">
|
7
|
+
<content url="file://$MODULE_DIR$">
|
8
|
+
<sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
|
9
|
+
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
10
|
+
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
11
|
+
</content>
|
12
|
+
<orderEntry type="inheritedJdk" />
|
13
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
14
|
+
<orderEntry type="library" scope="PROVIDED" name="activemodel (v7.1.2, rbenv: 3.2.2) [gem]" level="application" />
|
15
|
+
<orderEntry type="library" scope="PROVIDED" name="activerecord (v7.1.2, rbenv: 3.2.2) [gem]" level="application" />
|
16
|
+
<orderEntry type="library" scope="PROVIDED" name="activesupport (v7.1.2, rbenv: 3.2.2) [gem]" level="application" />
|
17
|
+
<orderEntry type="library" scope="PROVIDED" name="base64 (v0.2.0, rbenv: 3.2.2) [gem]" level="application" />
|
18
|
+
<orderEntry type="library" scope="PROVIDED" name="bigdecimal (v3.1.5, rbenv: 3.2.2) [gem]" level="application" />
|
19
|
+
<orderEntry type="library" scope="PROVIDED" name="bundler (v2.4.22, rbenv: 3.2.2) [gem]" level="application" />
|
20
|
+
<orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.2.2, rbenv: 3.2.2) [gem]" level="application" />
|
21
|
+
<orderEntry type="library" scope="PROVIDED" name="connection_pool (v2.4.1, rbenv: 3.2.2) [gem]" level="application" />
|
22
|
+
<orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.5.0, rbenv: 3.2.2) [gem]" level="application" />
|
23
|
+
<orderEntry type="library" scope="PROVIDED" name="drb (v2.2.0, rbenv: 3.2.2) [gem]" level="application" />
|
24
|
+
<orderEntry type="library" scope="PROVIDED" name="i18n (v1.14.1, rbenv: 3.2.2) [gem]" level="application" />
|
25
|
+
<orderEntry type="library" scope="PROVIDED" name="minitest (v5.20.0, rbenv: 3.2.2) [gem]" level="application" />
|
26
|
+
<orderEntry type="library" scope="PROVIDED" name="mutex_m (v0.2.0, rbenv: 3.2.2) [gem]" level="application" />
|
27
|
+
<orderEntry type="library" scope="PROVIDED" name="rake (v13.1.0, rbenv: 3.2.2) [gem]" level="application" />
|
28
|
+
<orderEntry type="library" scope="PROVIDED" name="redis (v5.0.8, rbenv: 3.2.2) [gem]" level="application" />
|
29
|
+
<orderEntry type="library" scope="PROVIDED" name="redis-client (v0.19.1, rbenv: 3.2.2) [gem]" level="application" />
|
30
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec (v3.12.0, rbenv: 3.2.2) [gem]" level="application" />
|
31
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.12.2, rbenv: 3.2.2) [gem]" level="application" />
|
32
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.12.3, rbenv: 3.2.2) [gem]" level="application" />
|
33
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.12.6, rbenv: 3.2.2) [gem]" level="application" />
|
34
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.12.1, rbenv: 3.2.2) [gem]" level="application" />
|
35
|
+
<orderEntry type="library" scope="PROVIDED" name="sqlite3 (v1.7.0, rbenv: 3.2.2) [gem]" level="application" />
|
36
|
+
<orderEntry type="library" scope="PROVIDED" name="timeout (v0.4.1, rbenv: 3.2.2) [gem]" level="application" />
|
37
|
+
<orderEntry type="library" scope="PROVIDED" name="tzinfo (v2.0.6, rbenv: 3.2.2) [gem]" level="application" />
|
38
|
+
</component>
|
39
|
+
<component name="RakeTasksCache">
|
40
|
+
<option name="myRootTask">
|
41
|
+
<RakeTaskImpl id="rake">
|
42
|
+
<subtasks>
|
43
|
+
<RakeTaskImpl description="Build econfig-0.1.1.gem into the pkg directory" fullCommand="build" id="build" />
|
44
|
+
<RakeTaskImpl id="build">
|
45
|
+
<subtasks>
|
46
|
+
<RakeTaskImpl description="Generate SHA512 checksum if econfig-0.1.1.gem into the checksums directory" fullCommand="build:checksum" id="checksum" />
|
47
|
+
</subtasks>
|
48
|
+
</RakeTaskImpl>
|
49
|
+
<RakeTaskImpl description="Remove any temporary products" fullCommand="clean" id="clean" />
|
50
|
+
<RakeTaskImpl description="Remove any generated files" fullCommand="clobber" id="clobber" />
|
51
|
+
<RakeTaskImpl description="Build and install econfig-0.1.1.gem into system gems" fullCommand="install" id="install" />
|
52
|
+
<RakeTaskImpl id="install">
|
53
|
+
<subtasks>
|
54
|
+
<RakeTaskImpl description="Build and install econfig-0.1.1.gem into system gems without network access" fullCommand="install:local" id="local" />
|
55
|
+
</subtasks>
|
56
|
+
</RakeTaskImpl>
|
57
|
+
<RakeTaskImpl description="Create tag v0.1.1 and build and push econfig-0.1.1.gem to rubygems.org" fullCommand="release[remote]" id="release[remote]" />
|
58
|
+
<RakeTaskImpl description="Run RSpec code examples" fullCommand="spec" id="spec" />
|
59
|
+
<RakeTaskImpl description="" fullCommand="default" id="default" />
|
60
|
+
<RakeTaskImpl description="" fullCommand="release" id="release" />
|
61
|
+
<RakeTaskImpl id="release">
|
62
|
+
<subtasks>
|
63
|
+
<RakeTaskImpl description="" fullCommand="release:guard_clean" id="guard_clean" />
|
64
|
+
<RakeTaskImpl description="" fullCommand="release:rubygem_push" id="rubygem_push" />
|
65
|
+
<RakeTaskImpl description="" fullCommand="release:source_control_push" id="source_control_push" />
|
66
|
+
</subtasks>
|
67
|
+
</RakeTaskImpl>
|
68
|
+
</subtasks>
|
69
|
+
</RakeTaskImpl>
|
70
|
+
</option>
|
71
|
+
</component>
|
72
|
+
</module>
|
data/.idea/misc.xml
ADDED
data/.idea/modules.xml
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<project version="4">
|
3
|
+
<component name="ProjectModuleManager">
|
4
|
+
<modules>
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/blue_config.iml" filepath="$PROJECT_DIR$/.idea/blue_config.iml" />
|
6
|
+
</modules>
|
7
|
+
</component>
|
8
|
+
</project>
|
data/.idea/vcs.xml
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-r spec_helper
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.2.2
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2024 Vladimir Elchinov, Rails Blueprint
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
# Blueprint Config
|
2
|
+
|
3
|
+
Blueprint Config is a gem which allows you to easily configure your Ruby applications
|
4
|
+
in a multitude of ways.
|
5
|
+
|
6
|
+
It was highly inspired by other solutions, Econfig in first place.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this to your Gemfile:
|
11
|
+
|
12
|
+
``` ruby
|
13
|
+
gem "blueprint_config"
|
14
|
+
```
|
15
|
+
|
16
|
+
## Using with Ruby on Rails app
|
17
|
+
|
18
|
+
In Rails, you'll want to add this in `config/application.rb`:
|
19
|
+
|
20
|
+
``` ruby
|
21
|
+
module MyApp
|
22
|
+
class Application < Rails::Application
|
23
|
+
BlueprintConfig.configure_rails(config)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
This will create module "AppConfig" for accessing configuration options,
|
29
|
+
and load configuration in following order:
|
30
|
+
- config/app.yml
|
31
|
+
- credentials
|
32
|
+
- ENV variables
|
33
|
+
- settings in database
|
34
|
+
- config/app.local.yml
|
35
|
+
|
36
|
+
Settings form database will be available only after rails app initialization.
|
37
|
+
Everything else can be used in initializers.
|
38
|
+
|
39
|
+
## Accessing configuration variables.
|
40
|
+
|
41
|
+
You can access configuration variables in any of followining ways:
|
42
|
+
|
43
|
+
### Member access syntax:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
irb(main):001> AppConfig.action_mailer.smtp_settings.address
|
47
|
+
=> "127.0.0.1"
|
48
|
+
```
|
49
|
+
|
50
|
+
If at some level variable is not defined and you try to access nested variable,
|
51
|
+
you'll gen an exception
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
irb(main):001> AppConfig.some.var
|
55
|
+
(irb):1:in `<main>': undefined method `var' for nil:NilClass (NoMethodError)
|
56
|
+
|
57
|
+
AppConfig.some.var
|
58
|
+
^^^^
|
59
|
+
```
|
60
|
+
|
61
|
+
If nil is suitable as a default value tyou can use safe navigation operator
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
irb(main):001> AppConfig.some&.var
|
65
|
+
=> nil
|
66
|
+
|
67
|
+
```
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
Optionaly you use bang methods to allways raise exception when variable is not defined
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
irb(main):001> AppConfig.some
|
75
|
+
=> nil
|
76
|
+
irb(main):002> AppConfig.some!
|
77
|
+
(irb):2:in `<main>': Configuration key 'some' is not set (KeyError)
|
78
|
+
irb(main):003> AppConfig.some!.var!
|
79
|
+
(irb):3:in `<main>': Configuration key 'some' is not set (KeyError)
|
80
|
+
```
|
81
|
+
|
82
|
+
Or use question mark to check if variable is defined
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
irb(main):001> AppConfig.some?
|
86
|
+
=> false
|
87
|
+
irb(main):002> AppConfig.host?
|
88
|
+
=> true
|
89
|
+
```
|
90
|
+
|
91
|
+
Note: Because question mark methods return Boolean you cannot chain em.
|
92
|
+
|
93
|
+
### Hash access syntax
|
94
|
+
You can use both symbols or strings as keys
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
irb(main):001> AppConfig[:action_mailer][:delivery_method]
|
98
|
+
=> :letter_opener
|
99
|
+
irb(main):002> AppConfig['action_mailer']['delivery_method']
|
100
|
+
=> :letter_opener
|
101
|
+
|
102
|
+
```
|
103
|
+
Again, if some level is missing you'll get an exception
|
104
|
+
```ruby
|
105
|
+
irb(main):001> AppConfig[:some][:var]
|
106
|
+
(irb):1:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)
|
107
|
+
|
108
|
+
AppConfig[:some][:var]
|
109
|
+
^^^^^^
|
110
|
+
```
|
111
|
+
|
112
|
+
### Fetch
|
113
|
+
|
114
|
+
You can use hash-style fetch method, but it works only for one level
|
115
|
+
(but you can chain it). Default values as second parameter or block are
|
116
|
+
supported. Without default value missing key will raise exception.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
irb(main):001> AppConfig.fetch(:host)
|
120
|
+
=> "localhost"
|
121
|
+
irb(main):002> AppConfig.fetch(:var)
|
122
|
+
(irb):2:in `<main>': Configuration key 'var' is not set (KeyError)
|
123
|
+
irb(main):003> AppConfig.fetch(:var, 123)
|
124
|
+
=> 123
|
125
|
+
irb(main):004> AppConfig.fetch(:var){123}
|
126
|
+
=> 123
|
127
|
+
```
|
128
|
+
|
129
|
+
### Dig
|
130
|
+
|
131
|
+
Dig permits to sefely get value in nested structures (including arrays)
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
irb(main):001> AppConfig.dig(:action_mailer, :smtp_settings, :address)
|
135
|
+
=> "127.0.0.1"
|
136
|
+
irb(main):002> AppConfig.dig(:action_mailer, :smtp_settings, :unknown)
|
137
|
+
=> nil
|
138
|
+
```
|
139
|
+
Bang version will raise exception when key at any level is missing
|
140
|
+
```ruby
|
141
|
+
irb(main):001> AppConfig.dig!(:action_mailer, :smtp_settings, :unknown)
|
142
|
+
(irb):1:in `<main>': Configuration key 'action_mailer.smtp_settings.unknown' is not set (KeyError)
|
143
|
+
irb(main):002> AppConfig.dig!(:action, :smtp_settings, :address)
|
144
|
+
(irb):2:in `<main>': Configuration key 'action' is not set (KeyError)
|
145
|
+
```
|
146
|
+
|
147
|
+
Whenever possible exception message specifies which key is missing.
|
148
|
+
|
149
|
+
|
150
|
+
## License
|
151
|
+
|
152
|
+
MIT, see separate LICENSE.txt file
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
|
5
|
+
lib = File.expand_path('lib', __dir__)
|
6
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
7
|
+
require 'blueprint_config/version'
|
8
|
+
|
9
|
+
Gem::Specification.new do |gem|
|
10
|
+
gem.name = 'blueprint_config'
|
11
|
+
gem.version = BlueprintConfig::VERSION
|
12
|
+
gem.authors = ['Vladimir Elchinov', 'Rails Blueprint']
|
13
|
+
gem.email = ['elik@elik.ru', 'info@railsblueprint.com']
|
14
|
+
gem.description = 'Flexible configuration for Ruby/Rails applications with a variety of backends'
|
15
|
+
gem.summary = 'Congifure Ruby apps'
|
16
|
+
gem.homepage = 'https://github.com/railsblueprint/blueprint_config'
|
17
|
+
gem.license = 'MIT'
|
18
|
+
|
19
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
20
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
21
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
22
|
+
gem.require_paths = ['lib']
|
23
|
+
|
24
|
+
gem.required_ruby_version = '~> 3.0'
|
25
|
+
|
26
|
+
gem.add_development_dependency 'activerecord'
|
27
|
+
gem.add_development_dependency 'rake'
|
28
|
+
gem.add_development_dependency 'redis'
|
29
|
+
gem.add_development_dependency 'rspec'
|
30
|
+
gem.add_development_dependency 'sqlite3'
|
31
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require 'blueprint_config/backend/base'
|
5
|
+
require 'blueprint_config/setting'
|
6
|
+
|
7
|
+
module BlueprintConfig
|
8
|
+
module Backend
|
9
|
+
class ActiveRecord < Base
|
10
|
+
MISSING_TABLE_WARNING = <<-WARNING.gsub(/^ */, '')
|
11
|
+
=======================================================================
|
12
|
+
Settings table not found. Please add the configuration table by running:
|
13
|
+
|
14
|
+
rails generate blue_config:migration
|
15
|
+
rake db:migrate
|
16
|
+
=======================================================================
|
17
|
+
WARNING
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
@options = options
|
21
|
+
@updated_at = nil
|
22
|
+
@last_checked_at = nil
|
23
|
+
@mutex = Mutex.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_keys
|
27
|
+
update_timestamp
|
28
|
+
|
29
|
+
data = Setting.all.map { |s| { s.key => s.parsed_value } }.reduce(:merge) || {}
|
30
|
+
return data.transform_keys(&:to_sym) unless @options[:nest]
|
31
|
+
|
32
|
+
nest_hash(data, @options[:nest_separator] || '.')
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_timestamp
|
36
|
+
@mutex.synchronize do
|
37
|
+
@updated_at = Setting.maximum(:updated_at)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def fresh?
|
42
|
+
return true if @last_checked_at.present? && @last_checked_at > 1.second.ago
|
43
|
+
|
44
|
+
@mutex.synchronize do
|
45
|
+
@last_checked_at = Time.now
|
46
|
+
end
|
47
|
+
@updated_at.present? && @updated_at >= Setting.maximum(:updated_at)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/hash/deep_merge'
|
4
|
+
|
5
|
+
module BlueprintConfig
|
6
|
+
module Backend
|
7
|
+
class Base
|
8
|
+
def nest_hash(hash, delimiter = '_')
|
9
|
+
hash.each_with_object({}) do |(key, value), results|
|
10
|
+
steps = key.split(delimiter).reverse
|
11
|
+
nested = steps.reduce(value) { |value, key| { key.to_sym => value } }
|
12
|
+
|
13
|
+
results.deep_merge!(nested) do |_key, a, b|
|
14
|
+
if a.is_a?(Hash) && b.is_a?(String)
|
15
|
+
a.deep_merge(nil => b)
|
16
|
+
elsif b.is_a?(Hash) && a.is_a?(String)
|
17
|
+
b.deep_merge(nil => a)
|
18
|
+
else
|
19
|
+
b
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def source
|
26
|
+
self.class.name
|
27
|
+
end
|
28
|
+
|
29
|
+
def fresh?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/encrypted_configuration'
|
4
|
+
|
5
|
+
module BlueprintConfig
|
6
|
+
module Backend
|
7
|
+
class Credentials < Base
|
8
|
+
def load_keys
|
9
|
+
credentials.to_h
|
10
|
+
end
|
11
|
+
|
12
|
+
def credentials
|
13
|
+
if defined?(Rails)
|
14
|
+
Rails.application.credentials
|
15
|
+
else
|
16
|
+
ActiveSupport::EncryptedConfiguration.new(
|
17
|
+
config_path: 'config/credentials.yml.enc',
|
18
|
+
key_path: 'config/master.key',
|
19
|
+
env_key: 'RAILS_MASTER_KEY',
|
20
|
+
raise_if_missing_key: false
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'blueprint_config/backend/base'
|
4
|
+
|
5
|
+
module BlueprintConfig
|
6
|
+
module Backend
|
7
|
+
class ENV < Base
|
8
|
+
attr_accessor :options
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_keys
|
15
|
+
{
|
16
|
+
# env: ::ENV.to_h.transform_keys(&:to_sym),
|
17
|
+
** transformed_env
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def env_downcased
|
22
|
+
@env_downcased ||= ::ENV.to_h.transform_keys(&:downcase)
|
23
|
+
end
|
24
|
+
|
25
|
+
def env_downcased_keys
|
26
|
+
@env_downcased_keys ||= env_downcased.keys
|
27
|
+
end
|
28
|
+
|
29
|
+
def filtered_env
|
30
|
+
return env_downcased if @options[:allow_all]
|
31
|
+
|
32
|
+
allowed_keys = @options[:whitelist_keys]&.map(&:to_s) || []
|
33
|
+
@options[:whitelist_prefixes]&.each do |prefix|
|
34
|
+
allowed_keys += env_downcased_keys.select { |key| key.to_s.start_with?(prefix.to_s) }
|
35
|
+
end
|
36
|
+
|
37
|
+
env_downcased.slice(* allowed_keys)
|
38
|
+
end
|
39
|
+
|
40
|
+
def transformed_env
|
41
|
+
return filtered_env.transform_keys(&:to_sym) unless @options[:nest]
|
42
|
+
|
43
|
+
nest_hash(filtered_env, @options[:nest_separator] || '_')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'yaml'
|
5
|
+
require 'active_support/hash_with_indifferent_access'
|
6
|
+
|
7
|
+
module BlueprintConfig
|
8
|
+
module Backend
|
9
|
+
class YAML < Base
|
10
|
+
def initialize(path = 'config/app.yml')
|
11
|
+
@path = path
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_keys
|
15
|
+
if File.exist?(path)
|
16
|
+
parsed = ::YAML.load(File.read(path), aliases: true).deep_symbolize_keys
|
17
|
+
parsed.fetch(:default, {}).deep_merge(parsed.fetch(BlueprintConfig.env&.to_sym, {}))
|
18
|
+
else
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def path
|
26
|
+
File.expand_path(@path, BlueprintConfig.root)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BlueprintConfig
|
4
|
+
class BackendCollection
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@backends = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](name)
|
12
|
+
pair = @backends.assoc(name)
|
13
|
+
pair&.last
|
14
|
+
end
|
15
|
+
|
16
|
+
def each
|
17
|
+
if block_given?
|
18
|
+
@backends.each { |(_name, backend)| yield backend }
|
19
|
+
else
|
20
|
+
enum_for { @backends.length }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def push(name, backend)
|
25
|
+
exists?(name)
|
26
|
+
@backends.push([name, backend])
|
27
|
+
end
|
28
|
+
alias use push
|
29
|
+
|
30
|
+
def unshift(name, backend)
|
31
|
+
exists?(name)
|
32
|
+
@backends.unshift([name, backend])
|
33
|
+
end
|
34
|
+
|
35
|
+
def insert_before(other, name, backend)
|
36
|
+
exists?(name)
|
37
|
+
@backends.insert(index_of!(other), [name, backend])
|
38
|
+
end
|
39
|
+
|
40
|
+
def insert_after(other, name, backend)
|
41
|
+
exists?(name)
|
42
|
+
@backends.insert(index_of!(other) + 1, [name, backend])
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete(name)
|
46
|
+
@backends.delete_at(index_of!(name))
|
47
|
+
end
|
48
|
+
|
49
|
+
def fresh?
|
50
|
+
@backends.all? { |(_name, backend)| backend.fresh? }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def exists?(name)
|
56
|
+
raise KeyError, "#{name} is already set" if index_of(name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def index_of!(name)
|
60
|
+
index_of(name) or raise KeyError, "#{name} is not set"
|
61
|
+
end
|
62
|
+
|
63
|
+
def index_of(name)
|
64
|
+
@backends.index(@backends.assoc(name))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|