sourced_config 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 67bcae6215d95ddc63e2dd5522805c69b73c4357756160d765a9f5581404152d
4
+ data.tar.gz: e6d714e45c36234238ff22c7c1390273de3df7b4c9d6ea1f52ee9829946f8ff4
5
+ SHA512:
6
+ metadata.gz: 8d786b59013736cceb1435f5e88351cff813658f88ba88a0a1a927f32c2ad5baf5668b7ed7565179b880f21de5c206cb8b89454798c6545dbc52f8c7578d74ba
7
+ data.tar.gz: 977b760623e3e29f545de365193b83c2f1555274ed53c68dcb6941b13d56beec8b3b7c976703998a57a8ad04ad1c58c198b6eab451336a37748a0804e408f92d
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/testdouble/standard
3
+ ruby_version: 2.7
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-11-18
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in sourced_config.gemspec
6
+ gemspec
7
+
8
+ gem "sqlite3"
9
+
10
+ gem "rake", "~> 13.0"
11
+
12
+ gem "minitest", "~> 5.0"
13
+
14
+ gem "standard", "~> 1.3"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Stephen Ierodiaconou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # SourcedConfig
2
+
3
+ App configuration & Locales for Rails apps where the config is loaded from a remote or local non-repo source.
4
+
5
+ Useful in apps that are 'white-labeled' and can have different configurations for different deployments.
6
+
7
+ Config can be loaded from:
8
+ - S3
9
+ - Local files
10
+
11
+ Locale data can be loaded from:
12
+ - S3
13
+ - Local files
14
+ - Google Spreadsheets
15
+
16
+ ## Installation
17
+
18
+ Install the gem and add to the application's Gemfile by executing:
19
+
20
+ $ bundle add sourced_config
21
+
22
+ If bundler is not being used to manage dependencies, install the gem by executing:
23
+
24
+ $ gem install sourced_config
25
+
26
+ ## Usage
27
+
28
+ TODO: Write usage instructions here
29
+
30
+ - Add an initializer to your app to configure the gem
31
+ - Add a base config file to your app
32
+
33
+ Access config with `SourcedConfig[key]`
34
+
35
+ ## Development
36
+
37
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
38
+
39
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
40
+
41
+ ## Contributing
42
+
43
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sourced_config.
44
+
45
+ ## License
46
+
47
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "standard/rake"
13
+
14
+ task default: %i[test standard]
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-monads"
4
+ require "dry-validation"
5
+
6
+ module SourcedConfig
7
+ class ConfigContract < ::Dry::Validation::Contract
8
+ ::Dry::Validation.load_extensions(:monads)
9
+
10
+ class LocalesConfig < ConfigContract
11
+ params do
12
+ required(:supported).filled(array[:string])
13
+ optional(:load_from_type).filled(:string)
14
+ optional(:load_from_source).maybe(:string)
15
+ end
16
+ end
17
+
18
+ params do
19
+ required(:locales).hash(LocalesConfig.schema)
20
+ end
21
+
22
+ class << self
23
+ def key_names
24
+ schema.key_map.map(&:name)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SourcedConfig
4
+ class ConfigManager
5
+ SOURCE_TYPE_LOCAL_FILE = "local_file"
6
+ SOURCE_TYPE_S3_CONFIG_BUCKET = "s3_config_bucket"
7
+
8
+ def root(key, raises: false)
9
+ raise ConfigurationNotLoadedError, "You cannot access config nodes until you have loaded configuration!" unless loaded?
10
+ raise ConfigurationRootKeyNotFoundError, "Config key #{key} not found" if raises && !configuration.key?(key)
11
+ configuration[key]
12
+ end
13
+
14
+ def load!(external_type, external_source_path)
15
+ Rails.logger.info "Load configuration data from #{external_type} - #{external_source_path}" if external_type
16
+ if loaded?
17
+ Rails.logger.warn "An attempt to load configuration happened when it is already loaded"
18
+ return false
19
+ end
20
+ primary_config = load_yaml_and_parse_erb(SourcedConfig.configuration.base_configuration_file_path).deep_symbolize_keys
21
+ external = external_type.present? && load_external_config(external_type, external_source_path).deep_symbolize_keys
22
+
23
+ schema = SourcedConfig.configuration.config_schema_klass.new
24
+ config_contract = schema.call(external ? primary_config.deep_merge(external) : primary_config)
25
+ if config_contract.failure?
26
+ messages = config_contract.errors(full: true).to_h
27
+ Rails.logger.error "Error in configuration file! #{messages}"
28
+ raise InvalidConfigurationError, "Failed to load configuration data! #{messages}"
29
+ end
30
+ @configuration = config_contract
31
+
32
+ loaded?
33
+ end
34
+
35
+ # Reload configuration files, only in development, this is hooked up to a file watcher on the config file
36
+ # and external directory if specified.
37
+ def reload!(external_type, external_source_path, force: false)
38
+ return false if Rails.env.production? && !force
39
+ Rails.logger.warn "Something changed: Reloading application configuration!"
40
+ @configuration = nil
41
+ load!(external_type, external_source_path)
42
+ end
43
+
44
+ def loaded?
45
+ configuration.present?
46
+ end
47
+
48
+ private
49
+
50
+ attr_reader :configuration
51
+
52
+ def load_external_config(external_type, external_source_path)
53
+ Rails.logger.info "Load external configuration data #{external_type}: #{external_source_path}"
54
+ case external_type
55
+ when SOURCE_TYPE_LOCAL_FILE
56
+ load_yaml(external_source_path)
57
+ when SOURCE_TYPE_S3_CONFIG_BUCKET
58
+ load_s3_config_bucket_file(external_source_path)
59
+ else
60
+ Rails.logger.error "Cannot load external configuration data for unknown #{external_type}"
61
+ raise ArgumentError, "Invalid external file type"
62
+ end
63
+ end
64
+
65
+ def load_yaml_and_parse_erb(file)
66
+ parsed = ERB.new(File.read(file)).result(binding)
67
+ parse_yaml(parsed)
68
+ end
69
+
70
+ def parse_yaml(str)
71
+ YAML.safe_load(str, permitted_classes: [Symbol])
72
+ end
73
+
74
+ def load_s3_config_bucket_file(path)
75
+ file = path || s3_file_path
76
+ Rails.logger.info "Loading from S3 #{s3_bucket} - #{s3_region} - #{file}"
77
+ s = SourcedConfig::S3File.read(s3_bucket, file, s3_region)
78
+ parse_yaml(s)
79
+ end
80
+
81
+ def s3_file_path
82
+ SourcedConfig.configuration.configuration_file_path
83
+ end
84
+
85
+ def s3_bucket
86
+ SourcedConfig.configuration.configuration_bucket
87
+ end
88
+
89
+ def s3_region
90
+ SourcedConfig.configuration.configuration_bucket_region
91
+ end
92
+
93
+ def load_yaml(file)
94
+ YAML.safe_load_file(file, permitted_classes: [Symbol])
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module SourcedConfig
6
+ module Locale
7
+ class GSheetsClient
8
+ def initialize(url)
9
+ @url = url
10
+ end
11
+
12
+ attr_reader :url
13
+
14
+ def load(locale)
15
+ Rails.logger.debug { "[locale - #{locale}] Strings read (as UTF-8) from remote URL #{url}" }
16
+ content = get_content
17
+ parse_body(content.body.force_encoding("UTF-8")).reduce({}) do |memo, row|
18
+ parse_item(row, memo)
19
+ end
20
+ rescue => e
21
+ Rails.logger.error "[locale - #{locale}] Could not fetch TSV doc directly from Google Spreadsheets: #{e.message}"
22
+ {}
23
+ end
24
+
25
+ private
26
+
27
+ def parse_body(text)
28
+ CSV.parse(text, col_sep: "\t", quote_char: "^", skip_lines: /^\s*#.*$/, skip_blanks: true, headers: true)
29
+ end
30
+
31
+ def parse_item(item, locale_hash)
32
+ *keys, string = item.to_a.map { |x| x[1] }
33
+ keys.compact!
34
+ leaf_item = keys[..-2].reduce(locale_hash) do |h, k|
35
+ next h unless k.present?
36
+ h[k] ||= {}
37
+ end
38
+ leaf_item[keys.last] = string
39
+ locale_hash
40
+ end
41
+
42
+ def get_content
43
+ uri = URI.parse(url)
44
+ http = Net::HTTP.new(uri.host, uri.port)
45
+ http.read_timeout = 5
46
+ http.open_timeout = 5
47
+ http.use_ssl = true
48
+ http.start do |http|
49
+ http.get(uri.request_uri)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "loader"
4
+ require_relative "null_client"
5
+
6
+ module SourcedConfig
7
+ module Locale
8
+ class I18nBackend < ::I18n::Backend::Simple
9
+ def load_translations
10
+ return unless ::SourcedConfig.loaded?
11
+ type = ::SourcedConfig[:locales][:load_from_type]
12
+ source = ::SourcedConfig[:locales][:load_from_source]
13
+ null_client = Rails.env.test? ? NullClient.new : nil
14
+ loader = Loader.new(type, source, client: null_client)
15
+ ::SourcedConfig[:locales][:supported].each do |locale|
16
+ Rails.logger.info "Load I18n: for supported locale #{locale}"
17
+ store_translations(locale, loader.load(locale.to_sym)[locale])
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./g_sheets_client"
4
+ require_relative "./s3_config_client"
5
+
6
+ module SourcedConfig
7
+ module Locale
8
+ class Loader
9
+ COPY_SOURCE_TYPE_LOCAL_DIRECTORY = "local-directory"
10
+ COPY_SOURCE_TYPE_S3_CONFIG = "s3-config"
11
+ COPY_SOURCE_TYPE_GOOGLE_SHEETS = "google-sheets"
12
+ COPY_SOURCE_TYPE_DEFAULT = "default"
13
+
14
+ def initialize(type, source, client: nil)
15
+ @type = type
16
+ @source = source
17
+ @client = client
18
+ end
19
+
20
+ attr_reader :locale, :type, :source
21
+
22
+ def load(locale)
23
+ Rails.logger.debug { "[locale - #{locale}] Load copy from #{type} #{source}" }
24
+ case type
25
+ when COPY_SOURCE_TYPE_LOCAL_DIRECTORY
26
+ HashWithIndifferentAccess.new load_from_local_dir(locale)
27
+ when COPY_SOURCE_TYPE_S3_CONFIG, COPY_SOURCE_TYPE_GOOGLE_SHEETS
28
+ HashWithIndifferentAccess.new(locale => HashWithIndifferentAccess.new(client.load(locale)))
29
+ when COPY_SOURCE_TYPE_DEFAULT
30
+ Rails.logger.warn "[locale - #{locale}] Using only default app copy"
31
+ HashWithIndifferentAccess.new(locale => {})
32
+ else
33
+ raise StandardError, "When loading the Copy and Content the type was unrecognised! #{type}"
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def client
40
+ return @client if @client
41
+ @client = case type
42
+ when COPY_SOURCE_TYPE_S3_CONFIG
43
+ S3ConfigClient.new
44
+ when COPY_SOURCE_TYPE_GOOGLE_SHEETS
45
+ GSheetsClient.new(source)
46
+ end
47
+ end
48
+
49
+ def load_from_local_dir(locale)
50
+ YAML.safe_load_file(Rails.root.join(source, "#{locale}.yml")) || {}
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SourcedConfig
4
+ module Locale
5
+ class NullClient
6
+ def load(_locale)
7
+ {}
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../s3_file"
4
+
5
+ module SourcedConfig
6
+ module Locale
7
+ class S3ConfigClient
8
+ def initialize(
9
+ bucket: ::SourcedConfig.configuration.configuration_bucket,
10
+ region: ::SourcedConfig.configuration.configuration_bucket_region
11
+ )
12
+ @bucket = bucket
13
+ @region = region
14
+ end
15
+
16
+ attr_reader :bucket, :region
17
+
18
+ def load(locale)
19
+ Rails.logger.debug "Locale read from S3 locale file"
20
+ file = "locales/#{locale}.yml" # TODO: make this configurable
21
+ str = ::SourcedConfig::S3File.read(bucket, file, region)
22
+ yaml = YAML.safe_load(str)
23
+ root_node = yaml[locale.to_s]
24
+ return {} if root_node.blank?
25
+ root_node
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ module SourcedConfig
2
+ class Railtie < ::Rails::Railtie
3
+ # rake_tasks do
4
+ # load "tasks/sourced_config_tasks.rake"
5
+ # end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SourcedConfig
4
+ class S3File
5
+ def self.read(bucket, path, region, retry_limit: 10)
6
+ s3 = Aws::S3::Client.new(retry_limit: retry_limit, region: region)
7
+ resp = s3.get_object(bucket: bucket, key: path)
8
+ resp.body
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SourcedConfig
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "sourced_config/version"
4
+ require_relative "sourced_config/railtie"
5
+ require_relative "sourced_config/config_manager"
6
+ require_relative "sourced_config/config_contract"
7
+ require_relative "sourced_config/locale/i18n_backend"
8
+
9
+ module SourcedConfig
10
+ class ConfigurationNotLoadedError < StandardError; end
11
+
12
+ class InvalidConfigurationError < StandardError; end
13
+
14
+ class ConfigurationRootKeyNotFoundError < StandardError; end
15
+
16
+ class << self
17
+ def configuration
18
+ @configuration ||= Configuration.new
19
+ end
20
+
21
+ def configure
22
+ yield(configuration) if block_given?
23
+ configuration
24
+ end
25
+ end
26
+
27
+ class Configuration
28
+ attr_accessor :config_schema_klass,
29
+ :config_type,
30
+ :base_configuration_file_path, # Can also be ERB
31
+ :configuration_bucket,
32
+ :configuration_bucket_region,
33
+ :configuration_file_path # Can not be ERB
34
+
35
+ def initialize
36
+ @config_schema_klass = ConfigContract
37
+ @base_configuration_file_path = Rails.root.join("config/config.yml.erb")
38
+ end
39
+ end
40
+
41
+ # The configuration class API
42
+ # Uses the ConfigManager to actually hold and prepare configuration data
43
+ class << self
44
+ # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/i18n_railtie.rb
45
+ @inited = false
46
+
47
+ def config_type_file?
48
+ configuration.config_type == ConfigManager::SOURCE_TYPE_LOCAL_FILE
49
+ end
50
+
51
+ def setup(app)
52
+ return if @inited
53
+
54
+ I18n.backend = I18n::Backend::Chain.new(Locale::I18nBackend.new, I18n.backend)
55
+
56
+ files = [configuration.base_configuration_file_path]
57
+
58
+ files << Rails.root.join(configuration.configuration_file_path) if config_type_file?
59
+
60
+ reloader = app.config.file_watcher.new(files) do
61
+ Rails.logger.warn "*** Application configuration changed in #{Rails.env} " \
62
+ "(zeitwerk: #{Rails.autoloaders.zeitwerk_enabled?})"
63
+ SourcedConfig.load!(configuration.config_type, configuration.configuration_file_path)
64
+ end
65
+
66
+ app.reloaders << reloader
67
+ app.reloader.to_run do
68
+ # We are *not* using execute_if_updated cause if anything causes Classes to be reloaded it will kill the config
69
+ # persisted in the class variable in the Config singleton. So we want to always execute a reload (eg when
70
+ # I18n content changes)
71
+ reloader.execute { require_unload_lock! }
72
+ end
73
+
74
+ # Load now
75
+ reloader.execute
76
+
77
+ @started_up ||= Time.zone.now
78
+ @inited = true
79
+ end
80
+
81
+ def load!(type = SourcedConfig.configuration.config_type, source_path = SourcedConfig.configuration.configuration_file_path, force: false)
82
+ loaded? ? manager.reload!(type, source_path, force: force) : manager.load!(type, source_path)
83
+ end
84
+
85
+ def startup_time
86
+ @started_up
87
+ end
88
+
89
+ # Get a key from the configuration
90
+ def [](key)
91
+ manager.root(key)
92
+ end
93
+
94
+ # Dig lets you reach in and extract deeply nested keys from an array of keys. Note will NOT raise if the key
95
+ # doesnt exist, like Hash#dig etc
96
+ def dig(root, *keys)
97
+ manager.root(root).dig(*keys)
98
+ end
99
+
100
+ def loaded?
101
+ manager&.loaded?
102
+ end
103
+
104
+ private
105
+
106
+ def manager
107
+ @manager ||= ConfigManager.new
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :sourced_config do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,4 @@
1
+ module SourcedConfig
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sourced_config
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Ierodiaconou
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-11-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-s3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-monads
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dry-validation
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Load configuration & locales for Rails apps from a remote or local non-repo
70
+ source.
71
+ email:
72
+ - stevegeek@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".standard.yml"
78
+ - CHANGELOG.md
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - lib/sourced_config.rb
84
+ - lib/sourced_config/config_contract.rb
85
+ - lib/sourced_config/config_manager.rb
86
+ - lib/sourced_config/locale/g_sheets_client.rb
87
+ - lib/sourced_config/locale/i18n_backend.rb
88
+ - lib/sourced_config/locale/loader.rb
89
+ - lib/sourced_config/locale/null_client.rb
90
+ - lib/sourced_config/locale/s3_config_client.rb
91
+ - lib/sourced_config/railtie.rb
92
+ - lib/sourced_config/s3_file.rb
93
+ - lib/sourced_config/version.rb
94
+ - lib/tasks/sourced_config_tasks.rake
95
+ - sig/sourced_config.rbs
96
+ homepage: https://github.com/stevegeek/sourced_config
97
+ licenses:
98
+ - MIT
99
+ metadata:
100
+ homepage_uri: https://github.com/stevegeek/sourced_config
101
+ source_code_uri: https://github.com/stevegeek/sourced_config
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 2.7.0
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubygems_version: 3.3.7
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Load configuration & locales for Rails apps from a remote or local non-repo
121
+ source.
122
+ test_files: []