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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.idea/.gitignore +8 -0
  4. data/.idea/blue_config.iml +72 -0
  5. data/.idea/misc.xml +4 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/vcs.xml +6 -0
  8. data/.rspec +1 -0
  9. data/.ruby-version +1 -0
  10. data/Gemfile +6 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +152 -0
  13. data/Rakefile +8 -0
  14. data/blueprint_config.gemspec +31 -0
  15. data/lib/blueprint_config/backend/active_record.rb +51 -0
  16. data/lib/blueprint_config/backend/base.rb +34 -0
  17. data/lib/blueprint_config/backend/credentials.rb +26 -0
  18. data/lib/blueprint_config/backend/env.rb +47 -0
  19. data/lib/blueprint_config/backend/yaml.rb +30 -0
  20. data/lib/blueprint_config/backend_collection.rb +67 -0
  21. data/lib/blueprint_config/configuration.rb +64 -0
  22. data/lib/blueprint_config/options_array.rb +81 -0
  23. data/lib/blueprint_config/options_hash.rb +112 -0
  24. data/lib/blueprint_config/setting.rb +28 -0
  25. data/lib/blueprint_config/version.rb +5 -0
  26. data/lib/blueprint_config/yaml.rb +46 -0
  27. data/lib/blueprint_config.rb +71 -0
  28. data/lib/generators/blueprint_config/install/USAGE +8 -0
  29. data/lib/generators/blueprint_config/install/install_generator.rb +23 -0
  30. data/lib/generators/blueprint_config/install/templates/migration.rb.erb +18 -0
  31. data/spec/backend_collection_spec.rb +103 -0
  32. data/spec/blueprint_config/backend/active_record_spec.rb +41 -0
  33. data/spec/blueprint_config/backend/env_spec.rb +53 -0
  34. data/spec/blueprint_config/backend/yaml_spec.rb +35 -0
  35. data/spec/blueprint_config/options_array_spec.rb +109 -0
  36. data/spec/blueprint_config/options_hash_spec.rb +211 -0
  37. data/spec/config/app.yml +24 -0
  38. data/spec/configuration_spec.rb +98 -0
  39. data/spec/spec_helper.rb +16 -0
  40. 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
@@ -0,0 +1,19 @@
1
+ .idea
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ bundler_stubs
data/.idea/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
@@ -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
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectRootManager" version="2" project-jdk-name="rbenv: 3.2.2" project-jdk-type="RUBY_SDK" />
4
+ </project>
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
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -r spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in econfig.gemspec
6
+ gemspec
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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new
7
+
8
+ task default: :spec
@@ -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