anyway_config 2.0.0.pre2 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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