anyway_config 2.0.0.pre2 → 2.0.0.rc1

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +350 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +444 -119
  5. data/lib/.rbnext/2.7/anyway/auto_cast.rb +33 -0
  6. data/lib/.rbnext/2.7/anyway/config.rb +404 -0
  7. data/lib/.rbnext/2.7/anyway/option_parser_builder.rb +31 -0
  8. data/lib/.rbnext/2.7/anyway/tracing.rb +187 -0
  9. data/lib/anyway/auto_cast.rb +33 -0
  10. data/lib/anyway/config.rb +235 -58
  11. data/lib/anyway/dynamic_config.rb +1 -1
  12. data/lib/anyway/env.rb +29 -19
  13. data/lib/anyway/ext/hash.rb +14 -6
  14. data/lib/anyway/loaders.rb +79 -0
  15. data/lib/anyway/loaders/base.rb +23 -0
  16. data/lib/anyway/loaders/env.rb +16 -0
  17. data/lib/anyway/loaders/yaml.rb +46 -0
  18. data/lib/anyway/option_parser_builder.rb +6 -3
  19. data/lib/anyway/optparse_config.rb +10 -6
  20. data/lib/anyway/rails.rb +16 -0
  21. data/lib/anyway/rails/config.rb +2 -69
  22. data/lib/anyway/rails/loaders.rb +5 -0
  23. data/lib/anyway/rails/loaders/credentials.rb +64 -0
  24. data/lib/anyway/rails/loaders/secrets.rb +39 -0
  25. data/lib/anyway/rails/loaders/yaml.rb +19 -0
  26. data/lib/anyway/rails/settings.rb +58 -0
  27. data/lib/anyway/railtie.rb +14 -2
  28. data/lib/anyway/settings.rb +29 -0
  29. data/lib/anyway/tracing.rb +187 -0
  30. data/lib/anyway/version.rb +1 -1
  31. data/lib/anyway_config.rb +27 -21
  32. data/lib/generators/anyway/app_config/USAGE +9 -0
  33. data/lib/generators/anyway/app_config/app_config_generator.rb +17 -0
  34. data/lib/generators/anyway/config/USAGE +13 -0
  35. data/lib/generators/anyway/config/config_generator.rb +46 -0
  36. data/lib/generators/anyway/config/templates/config.rb.tt +9 -0
  37. data/lib/generators/anyway/config/templates/config.yml.tt +13 -0
  38. data/lib/generators/anyway/install/USAGE +4 -0
  39. data/lib/generators/anyway/install/install_generator.rb +43 -0
  40. data/lib/generators/anyway/install/templates/application_config.rb.tt +17 -0
  41. metadata +75 -10
  42. data/lib/anyway/ext/string_serialize.rb +0 -38
  43. data/lib/anyway/loaders/env_loader.rb +0 -0
  44. data/lib/anyway/loaders/secrets_loader.rb +0 -0
  45. data/lib/anyway/loaders/yaml_loader.rb +0 -0
@@ -16,7 +16,7 @@ module Anyway
16
16
  config = allocate
17
17
  options[:env_prefix] ||= name.to_s.upcase
18
18
  options[:config_path] ||= config.resolve_config_path(name, options[:env_prefix])
19
- config.load_from_sources(name: name, **options)
19
+ config.load_from_sources(new_empty_config, name: name, **options)
20
20
  end
21
21
  end
22
22
 
data/lib/anyway/env.rb CHANGED
@@ -4,43 +4,53 @@ module Anyway
4
4
  # Parses environment variables and provides
5
5
  # method-like access
6
6
  class Env
7
+ using RubyNext
7
8
  using Anyway::Ext::DeepDup
8
- using Anyway::Ext::StringSerialize
9
+ using Anyway::Ext::Hash
9
10
 
10
- def initialize
11
+ include Tracing
12
+
13
+ attr_reader :data, :traces, :type_cast
14
+
15
+ def initialize(type_cast: AutoCast)
16
+ @type_cast = type_cast
11
17
  @data = {}
18
+ @traces = {}
12
19
  end
13
20
 
14
21
  def clear
15
- @data.clear
22
+ data.clear
23
+ traces.clear
16
24
  end
17
25
 
18
26
  def fetch(prefix)
19
- @data[prefix] ||= parse_env(prefix)
20
- @data[prefix].deep_dup
27
+ return data[prefix].deep_dup if data.key?(prefix)
28
+
29
+ Tracing.capture do
30
+ data[prefix] = parse_env(prefix)
31
+ end.then do |trace|
32
+ traces[prefix] = trace
33
+ end
34
+
35
+ data[prefix].deep_dup
36
+ end
37
+
38
+ def fetch_with_trace(prefix)
39
+ [fetch(prefix), traces[prefix]]
21
40
  end
22
41
 
23
42
  private
24
43
 
25
44
  def parse_env(prefix)
45
+ match_prefix = "#{prefix}_"
26
46
  ENV.each_pair.with_object({}) do |(key, val), data|
27
- next unless key.start_with?(prefix)
47
+ next unless key.start_with?(match_prefix)
28
48
 
29
49
  path = key.sub(/^#{prefix}_/, "").downcase
30
- set_by_path(data, path, val.serialize)
31
- end
32
- end
33
-
34
- def set_by_path(to, path, val)
35
- parts = path.split("__")
36
-
37
- to = get_hash(to, parts.shift) while parts.length > 1
38
50
 
39
- to[parts.first] = val
40
- end
41
-
42
- def get_hash(from, name)
43
- (from[name] ||= {})
51
+ paths = path.split("__")
52
+ trace!(:env, *paths, key: key) { data.bury(type_cast.call(val), *paths) }
53
+ end
44
54
  end
45
55
  end
46
56
  end
@@ -7,18 +7,14 @@ module Anyway
7
7
  refine ::Hash do
8
8
  # From ActiveSupport http://api.rubyonrails.org/classes/Hash.html#method-i-deep_merge
9
9
  def deep_merge!(other_hash)
10
- other_hash.each_pair do |current_key, other_value|
11
- this_value = self[current_key]
12
-
10
+ merge!(other_hash) do |key, this_value, other_value|
13
11
  if this_value.is_a?(::Hash) && other_value.is_a?(::Hash)
14
12
  this_value.deep_merge!(other_value)
15
13
  this_value
16
14
  else
17
- self[current_key] = other_value
15
+ other_value
18
16
  end
19
17
  end
20
-
21
- self
22
18
  end
23
19
 
24
20
  def stringify_keys!
@@ -30,6 +26,18 @@ module Anyway
30
26
 
31
27
  self
32
28
  end
29
+
30
+ def bury(val, *path)
31
+ raise ArgumentError, "No path specified" if path.empty?
32
+ raise ArgumentError, "Path cannot contain nil" if path.compact.size != path.size
33
+
34
+ last_key = path.pop
35
+ hash = path.reduce(self) do |hash, k|
36
+ hash[k] = {} unless hash.key?(k)
37
+ hash[k]
38
+ end
39
+ hash[last_key] = val
40
+ end
33
41
  end
34
42
 
35
43
  using self
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ using RubyNext
5
+
6
+ module Loaders
7
+ class Registry
8
+ attr_reader :registry
9
+
10
+ def initialize
11
+ @registry = []
12
+ end
13
+
14
+ def prepend(id, handler = nil, &block)
15
+ handler ||= block
16
+ insert_at(0, id, handler)
17
+ end
18
+
19
+ def append(id, handler = nil, &block)
20
+ handler ||= block
21
+ insert_at(registry.size, id, handler)
22
+ end
23
+
24
+ def insert_before(another_id, id, handler = nil, &block)
25
+ ind = registry.find_index { |(hid, _)| hid == another_id }
26
+ raise ArgumentError, "Loader with ID #{another_id} hasn't been registered" if ind.nil?
27
+
28
+ handler ||= block
29
+ insert_at(ind, id, handler)
30
+ end
31
+
32
+ def insert_after(another_id, id, handler = nil, &block)
33
+ ind = registry.find_index { |(hid, _)| hid == another_id }
34
+ raise ArgumentError, "Loader with ID #{another_id} hasn't been registered" if ind.nil?
35
+
36
+ handler ||= block
37
+ insert_at(ind + 1, id, handler)
38
+ end
39
+
40
+ def override(id, handler)
41
+ find(id).then do |id_to_handler|
42
+ raise ArgumentError, "Loader with ID #{id} hasn't been registered" if id_to_handler.nil?
43
+ id_to_handler[1] = handler
44
+ end
45
+ end
46
+
47
+ def delete(id)
48
+ find(id).then do |id_to_handler|
49
+ raise ArgumentError, "Loader with ID #{id} hasn't been registered" if id_to_handler.nil?
50
+ registry.delete id_to_handler
51
+ end
52
+ end
53
+
54
+ def each(&block)
55
+ registry.each(&block)
56
+ end
57
+
58
+ def freeze
59
+ registry.freeze
60
+ end
61
+
62
+ private
63
+
64
+ def insert_at(index, id, handler)
65
+ raise ArgumentError, "Loader with ID #{id} has been already registered" unless find(id).nil?
66
+
67
+ registry.insert(index, [id, handler])
68
+ end
69
+
70
+ def find(id)
71
+ registry.find { |(hid, _)| hid == id }
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ require "anyway/loaders/base"
78
+ require "anyway/loaders/yaml"
79
+ require "anyway/loaders/env"
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ module Loaders
5
+ class Base
6
+ include Tracing
7
+
8
+ class << self
9
+ def call(local: Anyway::Settings.use_local_files, **opts)
10
+ new(local: local).call(**opts)
11
+ end
12
+ end
13
+
14
+ def initialize(local:)
15
+ @local = local
16
+ end
17
+
18
+ def use_local?
19
+ @local == true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ using RubyNext
5
+
6
+ module Loaders
7
+ class Env < Base
8
+ def call(env_prefix:, **_options)
9
+ Anyway.env.fetch_with_trace(env_prefix).then do |(conf, trace)|
10
+ Tracing.current_trace&.merge!(trace)
11
+ conf
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "anyway/ext/hash"
5
+
6
+ using RubyNext
7
+ using Anyway::Ext::Hash
8
+
9
+ module Anyway
10
+ module Loaders
11
+ class YAML < Base
12
+ def call(config_path:, **_options)
13
+ load_yml(config_path).tap do |config|
14
+ config.deep_merge!(load_yml(local_config_path(config_path))) if use_local?
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def load_yml(path)
21
+ trace!(:yml, path: relative_config_path(path).to_s) { parse_yml(path) }
22
+ end
23
+
24
+ def parse_yml(path)
25
+ return {} unless File.file?(path)
26
+ require "yaml" unless defined?(::YAML)
27
+ if defined?(ERB)
28
+ ::YAML.safe_load(ERB.new(File.read(path)).result, [], [], true)
29
+ else
30
+ ::YAML.load_file(path)
31
+ end
32
+ end
33
+
34
+ def local_config_path(path)
35
+ path.sub(/\.yml/, ".local.yml")
36
+ end
37
+
38
+ def relative_config_path(path)
39
+ Pathname.new(path).then do |path|
40
+ return path if path.relative?
41
+ path.relative_path_from(Pathname.new(Dir.pwd))
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -8,9 +8,11 @@ module Anyway # :nodoc:
8
8
  class << self
9
9
  def call(options)
10
10
  OptionParser.new do |opts|
11
+ opts.accept(AutoCast) { AutoCast.call(_1) }
12
+
11
13
  options.each do |key, descriptor|
12
- opts.on(*option_parser_on_args(key, **descriptor)) do |arg|
13
- yield [key, arg]
14
+ opts.on(*option_parser_on_args(key, **descriptor)) do |val|
15
+ yield [key, val]
14
16
  end
15
17
  end
16
18
  end
@@ -18,8 +20,9 @@ module Anyway # :nodoc:
18
20
 
19
21
  private
20
22
 
21
- def option_parser_on_args(key, flag: false, desc: nil)
23
+ def option_parser_on_args(key, flag: false, desc: nil, type: AutoCast)
22
24
  on_args = ["--#{key.to_s.tr("_", "-")}#{flag ? "" : " VALUE"}"]
25
+ on_args << type unless flag
23
26
  on_args << desc unless desc.nil?
24
27
  on_args
25
28
  end
@@ -3,11 +3,9 @@
3
3
  require "anyway/option_parser_builder"
4
4
 
5
5
  require "anyway/ext/deep_dup"
6
- require "anyway/ext/string_serialize"
7
6
 
8
7
  module Anyway
9
8
  using Anyway::Ext::DeepDup
10
- using Anyway::Ext::StringSerialize
11
9
 
12
10
  # Adds ability to use script options as the source
13
11
  # of configuration (via optparse)
@@ -21,7 +19,11 @@ module Anyway
21
19
 
22
20
  def describe_options(**hargs)
23
21
  hargs.each do |name, desc|
24
- option_parser_descriptors[name.to_s][:desc] = desc
22
+ if String === desc
23
+ option_parser_descriptors[name.to_s][:desc] = desc
24
+ else
25
+ option_parser_descriptors[name.to_s].merge!(desc)
26
+ end
25
27
  end
26
28
  end
27
29
 
@@ -69,8 +71,8 @@ module Anyway
69
71
 
70
72
  def option_parser
71
73
  @option_parser ||= begin
72
- OptionParserBuilder.call(self.class.option_parser_options) do |key, arg|
73
- set_value(key, arg.is_a?(String) ? arg.serialize : arg)
74
+ OptionParserBuilder.call(self.class.option_parser_options) do |key, val|
75
+ write_config_attr(key, val)
74
76
  end.tap do |parser|
75
77
  self.class.option_parser_extensions.map do |extension|
76
78
  extension.call(parser, self)
@@ -80,7 +82,9 @@ module Anyway
80
82
  end
81
83
 
82
84
  def parse_options!(options)
83
- option_parser.parse!(options)
85
+ Tracing.with_trace_source(type: :options) do
86
+ option_parser.parse!(options)
87
+ end
84
88
  end
85
89
 
86
90
  def self.included(base)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ module Rails
5
+ end
6
+ end
7
+
8
+ require "anyway/rails/settings"
9
+ require "anyway/rails/config"
10
+ require "anyway/rails/loaders"
11
+ require "anyway/railtie"
12
+
13
+ # Configure Rails loaders
14
+ Anyway.loaders.override :yml, Anyway::Rails::Loaders::YAML
15
+ Anyway.loaders.insert_after :yml, :secrets, Anyway::Rails::Loaders::Secrets
16
+ Anyway.loaders.insert_after :secrets, :credentials, Anyway::Rails::Loaders::Credentials
@@ -11,76 +11,9 @@ module Anyway
11
11
  module Config
12
12
  module ClassMethods
13
13
  # Make defaults to be a Hash with indifferent access
14
- def defaults
15
- return @defaults if instance_variable_defined?(:@defaults)
16
-
17
- @defaults = super.with_indifferent_access
18
- end
19
- end
20
-
21
- def load_from_sources(**options)
22
- base_config = {}.with_indifferent_access
23
- each_source(options) do |config|
24
- base_config.deep_merge!(config) if config
25
- end
26
- base_config
27
- end
28
-
29
- def each_source(options)
30
- yield load_from_file(options)
31
- yield load_from_secrets(options)
32
- yield load_from_credentials(options)
33
- yield load_from_env(options)
34
- end
35
-
36
- def load_from_file(name:, config_path:, env_prefix:, **_options)
37
- file_config = load_from_yml(config_path)[::Rails.env] || {}
38
-
39
- if Anyway::Settings.use_local_files
40
- local_config_path = config_path.sub(/\.yml/, ".local.yml")
41
- file_config.deep_merge!(load_from_yml(local_config_path) || {})
14
+ def new_empty_config
15
+ {}.with_indifferent_access
42
16
  end
43
-
44
- file_config
45
- end
46
-
47
- def load_from_secrets(name:, **_options)
48
- return unless ::Rails.application.respond_to?(:secrets)
49
-
50
- ::Rails.application.secrets.public_send(name)
51
- end
52
-
53
- def load_from_credentials(name:, **_options)
54
- # do not load from credentials if we're in the context
55
- # of the `credentials:edit` command
56
- return if defined?(::Rails::Command::CredentialsCommand)
57
-
58
- return unless ::Rails.application.respond_to?(:credentials)
59
-
60
- # Create a new hash cause credentials are mutable!
61
- creds_config = {}
62
-
63
- creds_config.deep_merge!(::Rails.application.credentials.public_send(name) || {})
64
-
65
- creds_config.deep_merge!(load_from_local_credentials(name: name) || {}) if Anyway::Settings.use_local_files
66
- creds_config
67
- end
68
-
69
- def load_from_local_credentials(name:)
70
- local_creds_path = ::Rails.root.join("config/credentials/local.yml.enc").to_s
71
-
72
- return unless File.file?(local_creds_path)
73
-
74
- creds = ::Rails.application.encrypted(
75
- local_creds_path,
76
- key_path: ::Rails.root.join("config/credentials/local.key")
77
- )
78
-
79
- creds.public_send(name)
80
- end
81
-
82
- def default_config_path(name)
83
- ::Rails.root.join("config", "#{name}.yml")
84
17
  end
85
18
  end
86
19
  end