blueprint_config 1.0.0
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/.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
|