app_config_for 0.0.3 → 0.0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fffc63810437848a24a9e6f1d8cf02081060a0fe2d348bd8e7bb14c5917ca2ec
4
- data.tar.gz: 76c6787ebe99d673214fdd9e9bba7f4e0ff003c18308214444bdd48e38736023
3
+ metadata.gz: 12be8423d59d7901c58655092ddd1a35408b853e4d025943a38c0d5f59c33048
4
+ data.tar.gz: f6a1e5432c5aaf95e50881049a1040ca4311429ec73cc322635a28c255a6f703
5
5
  SHA512:
6
- metadata.gz: 142a9d57a16dcb0951ae39da4103138dd9b8d6b9d53b52bf4776d4ae4701290f0d00b6b36e773ac1af95d69bc150dade305bf43353a49a1072e715253c6fd034
7
- data.tar.gz: '0499b786cade8df74b4adc315f231fcbb3ef1c7f0b91cd74bdb430d8d779451edd4dfba7fd509cbe2e8af2daec00f6ad7ad1b1a78b287211016eda55bfb623d3'
6
+ metadata.gz: 7fa3fa1e7bc2becdded7a85c0d64809fb4c19a8684c035418e745b6bccb0a25344d62960f9babf4ad4c54a581245d60d3748210f2bc3911f73951a645102f103
7
+ data.tar.gz: bc6fff79b9a1dfe781f81481fc84fb2ef41f1738dd80ea7e049b08050e2a832dfcb80b57f3004d094cafac531732d3c94dfcc5c5932d0cd2b571d988bf551350
data/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  ## [Alpha Release]
2
2
 
3
- ## [0.0.1] - 2022-02-18
3
+ ## [0.0.1] - 2022-04-07
4
4
 
5
5
  - Initial release
6
+
7
+ ## [0.0.2] - 2022-04-12
8
+
9
+ - Added prefix inheritance.
10
+ - Initial inclusion vs extension support.
11
+ - Refactored some internal behaviors.
12
+
13
+ ## [0.0.3] - 2022-04-12
14
+
15
+ - More internal refactoring and miscellaneous bug fixes.
16
+
17
+ ## [0.0.4] - 2022-04-14
18
+
19
+ - Reduced minimal ActiveSupport version requirements to 5.0.
20
+ - Added a legacy support shim for older versions of ActiveSupport.
21
+ - Multiple configuration directory support.
22
+ - Single configuration name override support.
23
+ - Automatically prep gems consumers with the ability to ship with a default configuration.
24
+
25
+ ## [0.0.4.1] - 2022-04-15
26
+
27
+ - Reduced minimal Ruby version requirements to 2.3.6
28
+ - Bug fix: active_support/configuration_file was still being requested on older ActiveSupport installations.
29
+
30
+ ## [0.0.5] - 2022-04-25
31
+
32
+ - Full Yard documentation.
33
+ - Multiple config directories for file searching can be added at one time.
34
+ - Small updates to initializations.
35
+ - Started specs.
36
+ - Fixed bug in progenitor_prefixes_of that prevented rails and rack prefixes from being used.
37
+ - additional_config_directories duped by default to prevent accidental changes.
38
+ - add_config_directory now converts its argument to a string via #to_s.
39
+
40
+ ## [0.0.6] - 2022-04-25
41
+
42
+ - Reading and setting configuration values can now be done directly on the extending class/module.
43
+ - Documentation updates.
44
+ - Fallback configuration support.
45
+ - Requesting configuration for another object that can supply it's own config_files will use those files instead of locally determining them.
46
+
47
+ ## [0.0.6.1] - 2022-04-25
48
+ - Fixed issue when initializing env_prefixes in a namespaced module/class.
49
+
50
+ ## [0.0.6.2] - 2022-04-26
51
+ - When namespace_of, prefix_from, and yml_name_from encounter an anonymous class/module, the class hierarchy will be traversed until a name is found.
52
+
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # AppConfigFor
1
+ # AppConfigFor [![Gem Version](https://badge.fury.io/rb/app_config_for.svg)](https://badge.fury.io/rb/app_config_for)
2
2
 
3
3
  Ruby gem providing Rails::Application#config_for style capabilities for non-rails applications, gems, and rails engines.
4
4
 
@@ -20,54 +20,52 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
  Presume a typical rails database config at ./config/database.yml
23
- One environment variable ('MY_APP_ENV', 'RAILS_ENV', or 'RACK_ENV') is set to 'development'
23
+ One environment variable ('MY_APP_ENV', 'RAILS_ENV', or 'RACK_ENV') is set to 'development' or all are non existent.
24
24
 
25
- #### ./config/my_app.yml
25
+ #### ./config/sample_app.yml
26
26
  ```yml
27
27
  default: &default
28
- site: <%= ENV.fetch("MY_APP_SITE", 'www.slackware.com') %>
29
- password: Slackware#1!
28
+ site: <%= ENV.fetch("MY_APP_SITE", 'www.slackware.com') %>
29
+ password: Slackware#1!
30
30
 
31
31
  development:
32
- <<: *default
33
- username: Linux
32
+ <<: *default
33
+ username: Linux
34
34
 
35
35
  test:
36
- <<: *default
37
- username: TestingWith
36
+ <<: *default
37
+ username: TestingWith
38
38
 
39
39
  production:
40
- <<: *default
41
- username: DefinitelyUsing
40
+ <<: *default
41
+ username: DefinitelyUsing
42
42
 
43
43
  shared:
44
- color: 'Blue'
44
+ color: 'Blue'
45
45
  ```
46
46
 
47
- #### ./my_class.rb
47
+ #### sample_application.rb
48
48
  ```ruby
49
49
  require 'app_config_for'
50
50
 
51
- module MyApp
52
-
51
+ module Sample
52
+ class App
53
53
  extend AppConfigFor
54
-
55
- class MyClass
56
- def info
57
- puts "Curent environment is #{MyApp.env}"
58
-
59
- puts "Remote Host: #{MyApp.configured.site}"
60
-
61
- # Can access same configuration in other ways
62
- puts "Username: MyApp.config_for(:my_app)[:username]"
63
- puts "Password: MyApp.config_for(MyApp).username"
64
-
65
- # Access a different config
66
- if MyApp.config_file?(:database)
67
- puts "Rails database config: MyApp.config_for(:database)"
68
- end
69
- end
54
+ def info
55
+ puts "Current environment is #{App.env}"
56
+
57
+ puts "Remote Host: #{App.configured.site}"
58
+
59
+ # Can access same configuration in other ways
60
+ puts "Username: self.class.config_for(:app)[:username]"
61
+ puts "Password: App.config_for(App).username"
62
+
63
+ # Access a different config
64
+ if App.config_file?(:database)
65
+ puts "Rails database config: App.config_for(:database)"
66
+ end
70
67
  end
68
+ end
71
69
  end
72
70
  ```
73
71
 
@@ -1,9 +1,15 @@
1
1
  module AppConfigFor
2
+ # Base class for all errors generated by AppConfigFor
2
3
  class Error < StandardError; end
3
4
 
5
+ # Raised when the configuration could not be found.
4
6
  class ConfigNotFound < Error
5
7
 
6
- attr_reader :locations_searched, :original_exception
8
+ # The full path of every place the configuration file was looked for.
9
+ attr_reader :locations_searched
10
+ # The underlying SystemCallError that signaled the missing file.
11
+ # Most commonly this will be +Errno::ENOENT+.
12
+ attr_reader :original_exception
7
13
 
8
14
  def initialize(locations, original_exception)
9
15
  @locations_searched = Array(locations).map { |x| Pathname(x).expand_path }
@@ -12,9 +18,14 @@ module AppConfigFor
12
18
  end
13
19
  end
14
20
 
21
+ # Raised when there was an issue parsing the configuration file.
15
22
  class LoadError < Error
16
23
 
17
- attr_reader :file, :original_exception
24
+ # The file that was being parsed.
25
+ attr_reader :file
26
+ # The original exception that signaled the parsing problme.
27
+ # Most commonly this will be a +Psych::SyntaxError+
28
+ attr_reader :original_exception
18
29
 
19
30
  def initialize(file, original_exception)
20
31
  @file = Pathname(file).expand_path
@@ -23,13 +34,17 @@ module AppConfigFor
23
34
  end
24
35
  end
25
36
 
37
+ # Raised when an attempting to utilize an unrecognized inheritance style.
26
38
  class InvalidEnvInheritanceStyle < Error
27
39
 
28
- attr_reader :attempted, :valid
40
+ # The inheritance style that was attempted to be used.
41
+ attr_reader :attempted
42
+ # List of valid styles at the time of the attempt.
43
+ attr_reader :valid
29
44
 
30
45
  def initialize(attempted)
31
46
  @attempted = attempted
32
- @valid = EnvPrefixInheritanceStyles.dup
47
+ @valid = EnvInheritanceStyles.dup
33
48
  super "Invalid inheritance style #{@attempted.inspect}. Please use one of the following: #{@valid.map(&:inspect).join(', ')}"
34
49
  end
35
50
 
@@ -0,0 +1,20 @@
1
+ module AppConfigFor
2
+
3
+ # Current version of this gem with comparable values.
4
+ # @return [Gem::Version]
5
+ def self.gem_version
6
+ Gem::Version.new(VERSION::STRING)
7
+ end
8
+
9
+ # The rendition
10
+ module VERSION
11
+ MAJOR = 0 # A field-grade officer
12
+ MINOR = 0 # When the semitones show up as intervals between the 2nd and 3rd degrees
13
+ TINY = 6 # The number of people who use antidisestablishmentarianism in everyday conversation
14
+ PRE = 2 # Ante not auntie
15
+
16
+ # String form of the version (duh). Are you seriously reading this? I guess it is slightly more interesting that Moby-Dick.
17
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
18
+ end
19
+
20
+ end
@@ -0,0 +1,61 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+ name = File.basename(__dir__).classify
3
+ STDERR.puts "#{name}: Warning! Outdated version of ActiveSupport active! To avoid security issues, please upgrade your version of ActiveSupport to at least 6.1.4."
4
+ if ActiveSupport.gem_version < Gem::Version.new('6.1.0')
5
+ puts "#{name}: Loading legacy support for ActiveSupport version #{ActiveSupport.gem_version}."
6
+
7
+ # Quick and dirty backport. This won't be here long. Just enough to support AppConfigFor during some legacy upgrades.
8
+ require "active_support/string_inquirer"
9
+ require "erb"
10
+ require "yaml"
11
+
12
+ # @note This legacy support is only included if the existing ActiveSupport version is below 6.1.0.
13
+ # If your application is using any version of ActiveSupport below 6.1.4 it is *strongly* suggested you upgrade your application due to security and bug fixes.
14
+ # This backwards compatability is only enough to support AppConfigFor and is not to be considered a full backport of existing features.
15
+ # This support will be removed in the future.
16
+ module ActiveSupport
17
+
18
+ # @note This legacy support is only included if the existing ActiveSupport version is below 6.1.0.
19
+ # If your application is using any version of ActiveSupport below 6.1.4 it is *strongly* suggested you upgrade your application due to security and bug fixes.
20
+ # This backwards compatability is only enough to support AppConfigFor and is not to be considered a full backport of existing features.
21
+ # This support will be removed in the future.
22
+ class EnvironmentInquirer < StringInquirer
23
+
24
+ # Default environments
25
+ Environments = %w(development test production)
26
+
27
+ def initialize(env)
28
+ super(env)
29
+ Environments.each { |e| instance_variable_set(:"@#{e}", env == e) }
30
+ end
31
+
32
+ Environments.each { |e| define_method("#{e}?") { instance_variable_get("@#{e}") }}
33
+ end
34
+
35
+ # @note This legacy support is only included if the existing ActiveSupport version is below 6.1.0.
36
+ # If your application is using any version of ActiveSupport below 6.1.4 it is *strongly* suggested you upgrade your application due to security and bug fixes.
37
+ # This backwards compatability is only enough to support AppConfigFor and is not to be considered a full backport of existing features.
38
+ # This support will be removed in the future.
39
+ class ConfigurationFile
40
+ def initialize(file_name)
41
+ @file_name = file_name
42
+ @config = File.read(file_name)
43
+ warn(file_name + ' contains invisible non-breaking spaces.') if @config.match?("\u00A0")
44
+ end
45
+
46
+ # Quick and dirty parse
47
+ def self.parse(file_name)
48
+ new(file_name).parse
49
+ end
50
+
51
+ # Quick and dirty parse
52
+ def parse
53
+ YAML.load(ERB.new(@config).result) || {}
54
+ rescue Psych::SyntaxError => e
55
+ raise "YAML syntax error occurred while parsing #{@file_name}. Error: #{e.message}"
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -1,5 +1,13 @@
1
1
  # frozen_string_literal: true
2
+ require_relative "gem_version"
2
3
 
3
4
  module AppConfigFor
4
- VERSION = "0.0.3"
5
+
6
+ # Current version of this gem.
7
+ # @return [Gem::Version]
8
+ # @see gem_version
9
+ def self.version
10
+ gem_version
11
+ end
12
+
5
13
  end
@@ -2,53 +2,265 @@
2
2
 
3
3
  require_relative "app_config_for/version"
4
4
  require_relative "app_config_for/errors"
5
- require 'active_support/environment_inquirer'
6
- require 'active_support/configuration_file'
5
+
6
+ require 'active_support/gem_version'
7
+ if ActiveSupport.gem_version >= Gem::Version.new('6.1.4')
8
+ require 'active_support/environment_inquirer'
9
+ require 'active_support/configuration_file'
10
+ else
11
+ require_relative 'app_config_for/legacy_support'
12
+ end
13
+
7
14
  require 'active_support/core_ext/object/blank'
8
15
  require 'active_support/core_ext/string/inflections'
9
- require "active_support/core_ext/hash/indifferent_access"
16
+ require 'active_support/core_ext/hash/indifferent_access'
10
17
  require 'active_support/ordered_options'
11
18
  require 'active_support/core_ext/object/try'
12
-
19
+ require 'active_support/backtrace_cleaner'
20
+
21
+ # {<img src="https://badge.fury.io/rb/app_config_for.svg" alt="Gem Version" />}[https://badge.fury.io/rb/app_config_for]
22
+ #
23
+ # Ruby gem providing Rails::Application#config_for style capabilities for non-rails applications, gems, and rails engines.
24
+ # It respects RAILS_ENV and RACK_ENV while providing additional capabilities beyond Rails::Application#config_for.
25
+ #
26
+ # = Usage
27
+ # Typical usage will be done by extension but inclusion is also supported.
28
+ #
29
+ # Presume a typical rails database config at ./config/database.yml
30
+ #
31
+ # One environment variable ('MY_APP_ENV', 'RAILS_ENV', or 'RACK_ENV') is set to 'development' or all are non existent.
32
+ #
33
+ # ==== ./config/sample_app.yml
34
+ # default: &default
35
+ # site: <%= ENV.fetch("MY_APP_SITE", 'www.slackware.com') %>
36
+ # password: Slackware#1!
37
+ #
38
+ # development:
39
+ # <<: *default
40
+ # username: Linux
41
+ #
42
+ # test:
43
+ # <<: *default
44
+ # username: TestingWith
45
+ #
46
+ # production:
47
+ # <<: *default
48
+ # username: DefinitelyUsing
49
+ #
50
+ # shared:
51
+ # color: 'Blue'
52
+ #
53
+ # === sample_application.rb
54
+ # require 'app_config_for'
55
+ #
56
+ # module Sample
57
+ # class App
58
+ # extend AppConfigFor
59
+ # def info
60
+ # puts "Current environment is #{App.env}"
61
+ #
62
+ # # Access the configuration in various ways depending on need/preference.
63
+ # puts "Remote Host: #{App.site}"
64
+ # puts "Username: #{App.configured.username}"
65
+ # puts "Password: #{App.config_for(App).password}"
66
+ # puts "Domain: #{self.class.config_for(:app)[:domain]}"
67
+ #
68
+ # # Access a different config
69
+ # if App.config_file?(:database)
70
+ # adapter_name = App.config_for(:database).adapter
71
+ # puts "Rails is using the #{adapter_name} adapter."
72
+ # end
73
+ # end
74
+ # end
75
+ # end
76
+ #
13
77
  module AppConfigFor
14
-
15
- EnvPrefixInheritanceStyles = %i(none namespace class namespace_class class_namespace)
16
-
78
+ # Types of hierarchical traversal used to determine the runtime environment.
79
+ # * +:none+ - No inheritance active
80
+ # * +:namespace+ - Inheritance by lexical namespace
81
+ # * +:class+ - Inheritance by class hierarchy
82
+ # * +:namespace_class+ - Namespace inheritance combined with class inheritance
83
+ # * +:class_namespace+ - Class inheritance combined with namespace inheritance
84
+ EnvInheritanceStyles = [:none, :namespace, :class, :namespace_class, :class_namespace]
85
+
86
+ # AppConfigFor can be included instead of extended. If this occurs, instances of the class will have their
87
+ # own list of prefixes. The default class prefix will be automatically added to the list.
17
88
  def initialize(*args)
18
89
  add_env_prefix
19
90
  super
20
91
  end
21
92
 
93
+ # Add multiple additional directories to be used when searching for the config file.
94
+ # Any duplicates will be ignored.
95
+ # @param additional_directories [Array<#to_s>] additional directories to add
96
+ # @param at_beginning [Boolean] where to insert the new directories with respect to existing prefixes
97
+ # * +true+ - Add to the beginning of the list.
98
+ # * +false+ - Add to the end of the list.
99
+ # @return [Array<Pathname>] updated array of additional config directories
100
+ # @see #additional_config_directories Additional config directories
101
+ # @see #config_directories All config directories
102
+ def add_config_directories(*additional_directories, at_beginning: true)
103
+ additional_directories = additional_directories.flatten.map { |d| Pathname.new(d.to_s) }
104
+ directories = additional_config_directories(false).send(at_beginning ? :unshift : :push, additional_directories)
105
+ directories.flatten!
106
+ directories.uniq!
107
+ directories.dup
108
+ end
109
+
110
+ # Add an single additional directory to be used when searching for the config file.
111
+ # Any duplicates will be ignored.
112
+ # @param additional_directory [#to_s] additional directory to add
113
+ # @param at_beginning [Boolean] where to insert the new directory with respect to existing prefixes
114
+ # * +true+ - Add to the beginning of the list.
115
+ # * +false+ - Add to the end of the list.
116
+ # @return [Array<Pathname>] updated array of additional config directories
117
+ # @see #additional_config_directories Additional config directories
118
+ # @see #config_directories All config directories
119
+ def add_config_directory(additional_directory, at_beginning = true)
120
+ add_config_directories additional_directory, at_beginning: at_beginning
121
+ end
122
+
123
+ # Add an additional base name to be used when locating the config file.
124
+ # @param config_name [Object] Object to extract a config name from.
125
+ # @param at_beginning [Boolean] where to insert the new config name with respect to existing names.
126
+ # * +true+ - Add to the beginning of the list.
127
+ # * +false+ - Add to the end of the list.
128
+ # @return [Array<Object>] current config names
129
+ # @see yml_name_from How the name of the yml file is determined
130
+ def add_config_name(config_name, at_beginning = true)
131
+ add_config_names config_name, at_beginning: at_beginning
132
+ end
133
+
134
+ # Add multiple additional base names to be used when locating the config file.
135
+ # @param config_names [Array<Object>] Array of objects to extract a config name from.
136
+ # @param at_beginning [Boolean] where to insert the new config names with respect to existing names.
137
+ # * +true+ - Add to the beginning of the list.
138
+ # * +false+ - Add to the end of the list.
139
+ # @return [Array<Object>] current config names
140
+ # @see yml_name_from How the name of the yml file is determined
141
+ def add_config_names(*config_names, at_beginning: true)
142
+ names = config_names(false).send(at_beginning ? :unshift : :push, config_names)
143
+ names.flatten!
144
+ names.uniq!
145
+ names.dup
146
+ end
147
+
148
+ # Add an additional environmental prefix to be used when determining current environment.
149
+ # @param prefix [Symbol, Object] Prefix to add.
150
+ # +nil+ is treated as +self+
151
+ # Non symbols are converted via {.prefix_from AppConfigFor.prefix_from}.
152
+ # @param at_beginning [Boolean] where to insert the new prefix with respect to existing prefixes
153
+ # * +true+ - Add to the beginning of the list.
154
+ # * +false+ - Add to the end of the list.
155
+ # @return [Array<Symbol>] Current prefixes (without inheritance)
156
+ # @see #env_prefixes Current prefixes
22
157
  def add_env_prefix(prefix = nil, at_beginning = true)
23
158
  env_prefixes(false, false).send(at_beginning ? :unshift : :push, AppConfigFor.prefix_from(prefix || self)).uniq!
159
+ env_prefixes(false)
24
160
  end
25
161
 
162
+ # Directories to be checked in addition to the defaults when searching for the config file.
163
+ # @param dup [Boolean] Return a duplicated array to prevent accidental side effects
164
+ # @return [Array<Pathname>]
165
+ # @see #add_config_directory Adding config directories
166
+ # @see #config_directories All config directories
167
+ def additional_config_directories(dup = true)
168
+ @additional_config_directories ||= []
169
+ dup ? @additional_config_directories.dup : @additional_config_directories
170
+ end
171
+
172
+ # Clear all additional config directories and set to the directory given.
173
+ # @param directory [#to_s] additional directory to use
174
+ # @return [Array<Pathname>] updated array of additional config directories
175
+ # @see #additional_config_directories Additional config directories
176
+ # @see #config_directories All config directories
177
+ def config_directory=(directory)
178
+ additional_config_directories(false).clear
179
+ add_config_directory(directory)
180
+ end
181
+ alias_method :config_directories=, :config_directory=
182
+
183
+ # All directories that will be used when searching for the config file. Search order is as follows:
184
+ # 1. Rails configuration directories if Rails is present.
185
+ # 2. Engine configuration directories if extended by an engine.
186
+ # 3. Additional configuration directories.
187
+ # 4. ./config within the current working directory.
188
+ # All paths are expanded at time of call.
189
+ # @return [Array<Pathname>] directories in the order they will be searched.
190
+ # @see #add_config_directory Adding config directories
26
191
  def config_directories
27
192
  directories = ['Rails'.safe_constantize&.application&.paths, try(:paths)].compact.map { |root| root["config"].existent.first }.compact
28
- directories.map! { |directory| Pathname.new(directory) }
193
+ directories.concat additional_config_directories
29
194
  directories.push(Pathname.getwd + 'config')
30
- directories.uniq
195
+ directories.map { |directory| Pathname.new(directory).expand_path }.uniq
31
196
  end
32
197
 
33
- def config_file(name = nil)
198
+ # Configuration file that will be used.
199
+ # This is the first file from {#config_files} that exists or +nil+ if none exists.
200
+ # @param name [Symbol, Object] Name of the config to load.
201
+ # Conversion to a file name will occur using {.yml_name_from AppConfigFor.yml_name_from}.
202
+ # If name is +nil+ {#config_names} will be used.
203
+ # @param fallback [Symbol, Object] If not +nil+, attempt to load a fallback configuration if the requested one cannot be found.
204
+ # @return [Pathname, nil]
205
+ def config_file(name = nil, fallback = nil)
34
206
  unless name.is_a?(Pathname)
35
207
  config_files(name).find(&:exist?)
36
208
  else
37
209
  name.exist? ? name.expand_path : nil
38
- end
210
+ end.yield_self { |file| file || fallback && config_file(fallback) }
39
211
  end
40
212
 
213
+ # The list of potential config files that will be searched for and the order in which they will be searched.
214
+ # @param name [Symbol, Object] Name of the config to load.
215
+ # Conversion to a file name will occur using {.yml_name_from AppConfigFor.yml_name_from}.
216
+ # If name is +nil+, {#config_names} will be used.
217
+ # If name is object that responds to +config_files+, it will be called instead.
218
+ # @return [Array<Pathname>]
41
219
  def config_files(name = nil)
42
- name = AppConfigFor.yml_name_from(name || self)
43
- config_directories.map { |directory| directory + name }
220
+ if name.respond_to?(:config_files) && name != self
221
+ name.config_files
222
+ else
223
+ names = (name && name != self && Array(name) || config_names).map { |name| AppConfigFor.yml_name_from(name) }
224
+ config_directories.map { |directory| names.map { |name| directory + name } }.flatten
225
+ end
44
226
  end
45
227
 
46
- def config_file?(name = nil)
47
- !config_file(name).blank?
228
+ # Does a config file exit?
229
+ # @param name [Symbol, Object] Name of the config to load.
230
+ # Conversion to a file name will occur using {.yml_name_from AppConfigFor.yml_name_from}.
231
+ # If name is +nil+ {#config_names} will be used.
232
+ # @param fallback [Symbol, Object] If not +nil+, attempt to load a fallback configuration if the requested one cannot be found.
233
+ # @return [Boolean]
234
+ def config_file?(name = nil, fallback = nil)
235
+ !config_file(name, fallback).blank?
48
236
  end
49
237
 
50
- def config_for(name, env: nil)
51
- config, shared = config_options(name).fetch_values((env || self.env).to_sym, :shared) {nil}
238
+ # Configuration settings for the current environment.
239
+ # Shared sections in the yml config file are automatically merged into the returned configuration.
240
+ # @param name [Symbol, Object] Name of the config to load.
241
+ # Conversion to a file name will occur using {.yml_name_from AppConfigFor.yml_name_from}.
242
+ # If name is +nil+ {#config_names} will be used.
243
+ # @param env [Symbol, String] name of environment to use. +nil+ will use the current environment settings from {#env}
244
+ # @param fallback [Symbol, Object] If not +nil+, attempt to load a fallback configuration if the requested one cannot be found.
245
+ # @return [ActiveSupport::OrderedOptions]
246
+ # @raise ConfigNotFound - No configuration file could be located.
247
+ # @raise LoadError - A configuration file was found but could not be properly read.
248
+ # @see yml_name_from How the name of the yml file is determined
249
+ # @see #env The current runtime environment
250
+ # @example
251
+ # config_for(:my_app) # Load my_app.yml and extract the section relative to the current environment.
252
+ # config_for(:my_app).log_level # Get the configured logging level from my_app.yml for the current environment.
253
+ # config_for("MyApp", env: 'test') # Load my_app.yml and extract the 'test' section.
254
+ #
255
+ # module Other
256
+ # class App
257
+ # end
258
+ # end
259
+ # # Load other_app.yml and extract the 'production' section.
260
+ # # Notice that Other::App does not need to extend AppConfigFor
261
+ # config_for(Other::App, env: :production)
262
+ def config_for(name, env: nil, fallback: nil)
263
+ config, shared = config_options(name, fallback).fetch_values((env || self.env).to_sym, :shared) { nil }
52
264
  config ||= shared
53
265
 
54
266
  if config.is_a?(Hash)
@@ -59,34 +271,201 @@ module AppConfigFor
59
271
  config
60
272
  end
61
273
 
62
- def config_options(name = nil)
63
- file = name.is_a?(Pathname) ? name : config_file(name)
64
- ActiveSupport::ConfigurationFile.parse(file.to_s).deep_symbolize_keys
274
+ # Clear all config names and set to the name given.
275
+ # Set the base name of the config file to use.
276
+ # @param new_config_name [Object] Any object. Actual name will be determined using {.yml_name_from AppConfigFor.yml_name_for}
277
+ # @return [Array<Object>] current config names
278
+ # @see yml_name_from How the name of the yml file is determined
279
+ def config_name=(new_config_name)
280
+ config_names(false).clear
281
+ add_config_names(new_config_name)
282
+ end
283
+ alias_method :config_names=, :config_name=
284
+
285
+ # Base names of the configuration file.
286
+ # Defaults to: +[self]+
287
+ def config_names(dup = true)
288
+ @config_names ||= [self]
289
+ dup ? @config_names.dup : @config_names
290
+ end
291
+
292
+ # Configuration for all environments parsed from the {#config_file}.
293
+ # @param name [Symbol, Object] Name of the config to load.
294
+ # Conversion to a file name will occur using {.yml_name_from AppConfigFor.yml_name_from}.
295
+ # If name is +nil+ {#config_names} will be used.
296
+ # @param fallback [Symbol, Object] If not +nil+, attempt to load a fallback configuration if the requested one cannot be found.
297
+ # @return [Hash]
298
+ # @raise ConfigNotFound - No configuration file could be located.
299
+ # @raise LoadError - A configuration file was found but could not be properly read.
300
+ def config_options(name = nil, fallback = nil)
301
+ file = config_file(name, fallback).to_s
302
+ ActiveSupport::ConfigurationFile.parse(file).deep_symbolize_keys
65
303
  rescue SystemCallError => exception
66
- raise ConfigNotFound.new(name.is_a?(Pathname) ? name : config_files(name), exception)
304
+ locations = name.is_a?(Pathname) ? Array(name) : config_files(name)
305
+ locations += config_files(fallback) if fallback
306
+ raise ConfigNotFound.new(locations, exception)
67
307
  rescue => exception
68
- raise LoadError.new(file, exception)
308
+ raise file ? LoadError.new(file, exception) : exception
69
309
  end
70
310
 
71
- def configured(env: nil)
72
- config_for(self, env: env)
311
+ # Convenience method for {config_for}(+self+). Caches the result for faster access.
312
+ # @param reload [Boolean] Update the cached config by rereading the configuration file.
313
+ # @return [ActiveSupport::OrderedOptions]
314
+ # @raise ConfigNotFound - No configuration file could be located.
315
+ # @raise LoadError - A configuration file was found but could not be properly read.
316
+ # @example
317
+ # module Sample
318
+ # class App
319
+ # extend AppConfigFor
320
+ # @@logger = Logger.new($stdout, level: configured.level)
321
+ # end
322
+ # end
323
+ # Sample::App.configured.url # Get the configured url from my_app.yml for the current environment
324
+ # @see #method_missing Accessing configuration values directly from the extending class/module
325
+ def configured(reload = false)
326
+ if reload || !@configured
327
+ # @disable_local_missing = true # Disable local method missing to prevent recursion
328
+ @configured = config_for(nil, env: env(reload))
329
+ # @disable_local_missing = false # Reenable local method missing since no exception occurred.
330
+ end
331
+ @configured
73
332
  end
74
333
 
334
+ # Convenience method for {configured}(+true+).
335
+ # @return [ActiveSupport::OrderedOptions]
336
+ # @raise ConfigNotFound - No configuration file could be located.
337
+ # @raise LoadError - A configuration file was found but could not be properly read.
338
+ # @example
339
+ # module Sample
340
+ # class App
341
+ # extend AppConfigFor
342
+ # mattr_accessor :logger, default: Logger.new($stdout)
343
+ # logger.level = configured.log_level
344
+ # end
345
+ # end
346
+ # # Switch to production
347
+ # ENV['SAMPLE_APP_ENV'] = 'production'
348
+ # # Update the log level with the production values
349
+ # Sample::App.logger.level = Sample::App.configured!.log_level
350
+ def configured!
351
+ configured(true)
352
+ end
353
+
354
+ # Check for the existence of a configuration setting. Handles exceptions and recursion.
355
+ # @param key [#to_s] Key to check for
356
+ # @return [Boolean]
357
+ # * +true+ - Configuration has the key
358
+ # * +false+ - If one of the following:
359
+ # 1. Configuration does not have the key
360
+ # 2. Called recursively while retrieving the configuration
361
+ # 3. An exception is raised while retrieving the configuration
362
+ # @note This is primarily used internally during {#respond_to_missing?} and {#method_missing} calls.
363
+ def configured?(key)
364
+ if @disable_local_missing
365
+ false
366
+ else
367
+ @disable_local_missing = true
368
+ begin
369
+ configured.has_key?(key.to_s.to_sym)
370
+ rescue Exception # One of the few times you ever want to catch this exception and not reraise it.
371
+ false
372
+ ensure
373
+ @disable_local_missing = false
374
+ end
375
+ end
376
+ end
377
+
378
+ # Returns the current runtime environment. Caches the result.
379
+ # @param reload [Boolean] Update the cached env by requerying the environment
380
+ # @return [ActiveSupport::EnvironmentInquirer]
381
+ # @example
382
+ # module Sample
383
+ # extend AppConfigFor
384
+ # end
385
+ # Sample.env # => 'development'
386
+ # Sample.env.development? # => true
387
+ # Sample.env.production? # => false
75
388
  def env(reload = false)
76
- @env = ActiveSupport::EnvironmentInquirer.new(AppConfigFor.env_name(env_prefixes)) if reload || @env.nil?
389
+ @env = ActiveSupport::EnvironmentInquirer.new(env_name) if reload || @env.nil?
77
390
  @env
78
391
  end
79
392
 
80
- def env_prefix_inheritance
81
- @env_prefix_inheritance ||= :namespace
393
+ # Convenience method for {env}(+true+).
394
+ # @return [ActiveSupport::EnvironmentInquirer]
395
+ # @example
396
+ # module Sample
397
+ # extend AppConfigFor
398
+ # end
399
+ # Sample.env # => 'development'
400
+ # Sample.env.development? # => true
401
+ # Sample.env.production? # => false
402
+ # # Switch to production
403
+ # ENV['SAMPLE_APP_ENV'] = 'production'
404
+ # Sample.env.production? # => false
405
+ # Sample.env!.production? # => true
406
+ def env!
407
+ env(true)
408
+ end
409
+
410
+ # Set the runtime environment (without affecting environment variables)
411
+ # @param environment [#to_s]
412
+ # @return [ActiveSupport::EnvironmentInquirer]
413
+ # @example
414
+ # ENV['SAMPLE_ENV'] = 'test'
415
+ # module Sample
416
+ # extend AppConfigFor
417
+ # end
418
+ # Sample.env # => 'test'
419
+ # Sample.env = 'development'
420
+ # Sample.env # => 'development'
421
+ # ENV['SAMPLE_ENV'] # => 'test'
422
+ def env=(environment)
423
+ @env = ActiveSupport::EnvironmentInquirer.new(environment.to_s)
424
+ end
425
+
426
+ # Current runtime environment inheritance style. Defaults to +:namespace+.
427
+ # @return [Symbol]
428
+ def env_inheritance
429
+ @env_inheritance ||= :namespace
82
430
  end
83
431
 
84
- def env_prefix_inheritance=(style)
85
- @env_prefix_inheritance = AppConfigFor.verified_style!(style)
432
+ # Set the runtime environment inheritance.
433
+ # @param style [#to_s] New inheritance style
434
+ # @return [Symbol]
435
+ # @raise InvalidEnvInheritanceStyle - Attempt to set a style that is not one of the {EnvInheritanceStyles}.
436
+ # @see .verified_style! Valid inheritance styles
437
+ def env_inheritance=(style)
438
+ @env_inheritance = AppConfigFor.verified_style!(style)
86
439
  end
87
440
 
441
+ # The name of the current runtime environment for this object.
442
+ #
443
+ # Convenience method for {.env_name AppConfigFor.env_name}({#env_prefixes env_prefixes})
444
+ #
445
+ # If no value can be found, the default is 'development'.
446
+ # @return [String] current runtime environment.
447
+ # @see #env_prefixes Environment variable prefixes
448
+ def env_name
449
+ AppConfigFor.env_name(env_prefixes)
450
+ end
451
+
452
+ # Prefixes used to determine the environment name.
453
+ #
454
+ # A prefix of :some_app will will cause AppConfigFor to react to the environment variable +'SOME_APP_ENV'+
455
+ # The order of the prefixes will be the order in which AppConfigFor searches the environment variables.
456
+ # A prefix for +self+ is automatically added at the time of extension/inclusion of AppConfigFor.
457
+ #
458
+ # @param all [Boolean] Combine current prefixes with inherited prefixes.
459
+ # @param dup [Boolean] Return a duplicate of the internal array to prevent accidental modification.
460
+ # @return [Array<Symbol>] Environment prefixes for this object.
461
+ # @see add_env_prefix Adding a prefix
462
+ # @see remove_env_prefix Removing a prefix
463
+ # @see env_name Current runtime environment
88
464
  def env_prefixes(all = true, dup = true)
89
- @env_prefixes ||= []
465
+ unless @env_prefixes
466
+ @env_prefixes = []
467
+ add_env_prefix
468
+ end
90
469
  if all
91
470
  @env_prefixes + AppConfigFor.progenitor_prefixes_of(self)
92
471
  else
@@ -94,6 +473,48 @@ module AppConfigFor
94
473
  end
95
474
  end
96
475
 
476
+ # Allow access to configuration getters and setters directly from the extending class/module.
477
+ # @example
478
+ # class Sample
479
+ # extend AppConfigFor
480
+ # end
481
+ #
482
+ # # Presuming config/sample.yml contains a configuration for 'log_level' and 'status' but no other keys.
483
+ # Sample.log_level # => :production
484
+ # Sample.log_level = :debug
485
+ # Sample.log_level # => :debug
486
+ #
487
+ # # You are allowed to set the value prior reading it should the need should arise.
488
+ # Sample.status = 'active'
489
+ # Sample.status # => 'active'
490
+ #
491
+ # # However, you cannot invent new keys with these methods.
492
+ # Sample.something_else # => NoMethodError(undefined method `something_else' for Sample)
493
+ # Sample.something_else = 1 # => NoMethodError(undefined method `something_else=' for Sample)
494
+ # @note Values can be written or read prior to the loading of the configuration presuming the configuration can load without error.
495
+ def method_missing(name, *args, &block)
496
+ if configured?(name.to_s.split('=').first.to_sym)
497
+ configured.send(name, *args, &block)
498
+ else
499
+ begin
500
+ super
501
+ rescue Exception => e
502
+ # Remove the call to super from the backtrace to make it more apparent where the failure occurred,
503
+ super_line = Regexp.new("#{__FILE__}:#{__LINE__ - 3}")
504
+ e.set_backtrace(ActiveSupport::BacktraceCleaner.new.tap { |bc| bc.add_silencer { |line| line =~ super_line } }.clean(e.backtrace))
505
+ raise e
506
+ end
507
+ end
508
+ end
509
+
510
+ # Remove an environmental prefix from the existing list.
511
+ # @param prefix [Symbol, Object] Prefix to remove.
512
+ # +nil+ is treated as +self+
513
+ # Non symbols are converted via {.prefix_from AppConfigFor.prefix_from}.
514
+ # @param all [Boolean] Remove this prefix throughout the entire inheritance chain.
515
+ # +USE WITH CAUTION:+ When +true+ this will affect other consumers of AppConfigFor by altering their env prefix values.
516
+ # @return [Array<Symbol>] Current prefixes (without inheritance)
517
+ # @see #env_prefixes Current prefixes
97
518
  def remove_env_prefix(prefix, all = false)
98
519
  if all
99
520
  remove_env_prefix(prefix)
@@ -101,40 +522,124 @@ module AppConfigFor
101
522
  else
102
523
  env_prefixes(false, false).delete(AppConfigFor.prefix_from(prefix))
103
524
  end
525
+ env_prefixes(all)
104
526
  end
105
527
 
528
+ # Return true if the missing method is a configuration getter or setter.
529
+ # @see #method_missing Accessing configuration values directly from the extending class/module
530
+ def respond_to_missing?(name, *args)
531
+ configured?(name.to_s.split('=').first.to_sym) || super
532
+ end
533
+
106
534
  class << self
107
535
 
536
+ # Add an additional environmental prefix to be used when determining current environment.
537
+ # @param prefix [Symbol, Object] Prefix to add.
538
+ # Non symbols are converted via {.prefix_from}.
539
+ # @param at_beginning [Boolean] where to insert the new prefix with respect to existing prefixes.
540
+ # * +true+ - Add to the beginning of the list.
541
+ # * +false+ - Add to the end of the list.
542
+ # @return [Array<Symbol>] Current prefixes (without inheritance)
543
+ # @see env_prefixes Current prefixes
544
+ # @note Prefixes added here will affect all consumers of AppConfigFor. For targeted changes see: {#add_env_prefix}
108
545
  def add_env_prefix(prefix, at_beginning = true)
109
546
  env_prefixes(false, false).send(at_beginning ? :unshift : :push, prefix_from(prefix)).uniq!
547
+ env_prefixes(false)
110
548
  end
111
549
 
550
+ # The name of the current runtime environment. This is value of the first non blank environment variable.
551
+ # If no value can be found, the default is 'development'.
552
+ # Prefixes like +:some_app+, +:rails+, and +:rack+ convert to +'SOME_APP_ENV'+, +'RAILS_ENV'+, and +'RACK_ENV'+ respectively.
553
+ # @param prefixes [Array<#to_s>] List of prefixes of environment variables to check.
554
+ # @return [String] current runtime environment.
555
+ # @see env_prefixes Current prefixes
112
556
  def env_name(prefixes = env_prefixes)
113
- prefixes.inject(nil) { |current_env, name| current_env || ENV["#{name.to_s.upcase}_ENV"].presence } || 'development'
557
+ Array(prefixes).inject(nil) { |current_env, name| current_env || ENV["#{name.to_s.upcase}_ENV"].presence } || 'development'
114
558
  end
115
559
 
116
- def env_prefixes(_all = true, dup = true)
117
- # all is ignored as we are at the end of the chain
560
+ # Prefixes used to determine the environment name.
561
+ #
562
+ # A prefix of :some_app will will cause AppConfigFor to react to the environment variable +'SOME_APP_ENV'+
563
+ # The order of the prefixes will be the order in which AppConfigFor searches the environment variables.
564
+ #
565
+ # @param _ Ignored. Unlike {#env_prefixes}, the first parameter is ignored as there is no inheritance at this point.
566
+ # @param dup[Boolean] Return a duplicate of the internal array to prevent accidental modification.
567
+ # @return [Array<Symbol>] Defaults to +[:rails, :rack]+
568
+ # @see env_name Current runtime environment
569
+ def env_prefixes(_ = true, dup = true)
118
570
  @env_prefixes ||= [:rails, :rack]
119
571
  dup ? @env_prefixes.dup : @env_prefixes
120
572
  end
121
573
 
574
+ # Lexical namespace of an object.
575
+ # Strings are considered to hold the #name of a Class or Module.
576
+ # Anything not a String, Class, or Module will return the namespace of the class of the object.
577
+ # @param object [Module, Class, String, Object]
578
+ # @return [Module, Class, nil] +nil+ is returned if there is no surrounding namespace.
579
+ # @example
580
+ # module Some
581
+ # class App
582
+ # end
583
+ # end
584
+ #
585
+ # namespace_of(Some::App) # => Some
586
+ # namespace_of('Some::App') # => Some
587
+ # namespace_of(Some::App.new) # => Some
588
+ # namespace_of(Some) # => nil
122
589
  def namespace_of(object)
123
- case object
124
- when String
125
- object
126
- when Module
127
- object.name
128
- else
129
- object.class.name
130
- end.deconstantize.safe_constantize
590
+ (String === object ? object : nearest_named_class(object).name).deconstantize.safe_constantize
131
591
  end
132
592
 
133
- # Not used internally, this is a convenience method to study what progenitors are used during namespace dives
593
+ # Array of all hierarchical lexical namespaces of an object. Uses {.namespace_of}
594
+ # @param object [Module, Class, String, Object]
595
+ # @return [Array<Module, Class>]
596
+ # @example
597
+ # module Some
598
+ # class App
599
+ # class Connection
600
+ # end
601
+ # end
602
+ # end
603
+ #
604
+ # namespaces_of(Some::App::Connection) # => [Some::App, Some]
605
+ # namespaces_of(Some) # => []
134
606
  def namespaces_of(object)
135
607
  (object = [namespace_of(object)]).each { |x| x && object << namespace_of(x) }[0..-2]
136
608
  end
137
-
609
+
610
+ # Locate the nearest class that is not anonymous.
611
+ # @param object [Object]
612
+ # @return [Class] The first non-anonymous class that is in the class hierarchy.
613
+ def nearest_named_class(object)
614
+ # Switch from an instance to a class
615
+ object = object.class unless object.is_a?(Module)
616
+ # Switch from anonymous module to a class unless it provides a name
617
+ object = object.class unless object.try(:name) || object.respond_to?(:superclass)
618
+ # Continue up the hierarchy while we are in an anonymous class
619
+ object = object.superclass while object.name.nil?
620
+ object
621
+ end
622
+
623
+ # Parent of an object.
624
+ # Parent of an object.
625
+ # While similar to inheritance it provides a meaningful value for strings and other objects.
626
+ # Classes return super classes.
627
+ # Strings are treated as a name of a class and an attempt is made to locate that class (not the superclass of the named class).
628
+ # All other objects return the class of the object.
629
+ # @param object [Class, String, Object]
630
+ # @return [Class, nil] +nil+ is returned if a string is given that is not the name of a class.
631
+ # @example
632
+ # module Some
633
+ # class Base
634
+ # end
635
+ # class App < Base
636
+ # end
637
+ # end
638
+ #
639
+ # parent_of(Some::App) # => Some::Base
640
+ # parent_of(Some::App.new) # => Some::App
641
+ # parent_of('Some::App') # => Some::App
642
+ # parent_of('wtf') # => nil
138
643
  def parent_of(object)
139
644
  case object
140
645
  when String
@@ -146,41 +651,102 @@ module AppConfigFor
146
651
  end
147
652
  end
148
653
 
149
- # Not used internally, this is a convenience method to study what progenitors are used during class dives
654
+ # List of all hierarchical parents of an object. Uses {.parents_of}
655
+ # @param object [Class, String, Object]
656
+ # @return [Array<Class>]
657
+ #
658
+ # @example
659
+ # module Some
660
+ # class Base
661
+ # end
662
+ # class App < Base
663
+ # end
664
+ # end
665
+ #
666
+ # parents_of(Some::App) # => [Some::Base, Object, BasicObject]
667
+ # parents_of(Some::App.new) # => [Some::App, Some::Base, Object, BasicObject]
668
+ # parents_of('Some::App') # => [Some::App, Some::Base, Object, BasicObject]
669
+ # parents_of('wtf') # => []
150
670
  def parents_of(object)
151
671
  (object = [parent_of(object)]).each { |x| x && object << parent_of(x) }[0..-2]
152
672
  end
153
673
 
674
+ # Converts an object to a prefix symbol.
675
+ # Non symbols are converted to underscored symbols with '/' characters changed to underscores.
676
+ # Conversion by object type is as follows:
677
+ # * Symbol -> symbol
678
+ # * Module -> module.name
679
+ # * Class -> class.name
680
+ # * String -> string
681
+ # * Pathname -> pathname.basename (Without an extension)
682
+ # * other -> other.class.name
683
+ # @param object [Symbol, Module, Class, String, Pathname, Object] object to convert to a prefix
684
+ # @return [Symbol]
685
+ # @example
686
+ # module Some
687
+ # class App
688
+ # end
689
+ # end
690
+ #
691
+ # # All of the following return :some_app
692
+ # prefix_from(Some::App)
693
+ # prefix_from('Some::App')
694
+ # prefix_from(Some::App.new)
695
+ # prefix_from(:some_app)
696
+ # prefix_from(Pathname.new('/foo/bar/some_app.yml'))
154
697
  def prefix_from(object)
155
698
  if object.is_a?(Symbol)
156
699
  object
157
700
  else
158
701
  case object
159
- when Module
160
- object.name
161
702
  when String
162
703
  object
163
704
  when Pathname
164
- object.basename.to_s
705
+ object.basename('.*').to_s
165
706
  else
166
- object.class.name
707
+ nearest_named_class(object).name
167
708
  end.underscore.gsub('/','_').to_sym
168
709
  end
169
710
  end
170
711
 
712
+ # The first env_prefix aware namespace/parent of an object.
713
+ # Search is dependant on the inheritance style given.
714
+ # @param object [Object] Object to retrieve the progenitor of.
715
+ # @param style [#to_s] Type of hierarchical traversal.
716
+ # @return [Object, nil] +nil+ is returned if there is no progenitor.
717
+ # @raise InvalidEnvInheritanceStyle - Attempt to use a style that is not one of the {EnvInheritanceStyles}.
718
+ # @see .verified_style! Valid inheritance styles
171
719
  def progenitor_of(object, style = nil)
172
720
  style = verified_style!(style, object)
173
- command = {namespace: :namespace_of, class: :parent_of}[style]
721
+ command = {namespace: :namespace_of, class: :parent_of}[style.to_s.split('_').first.to_sym]
174
722
  object && command && send(command, object).yield_self { |n| n && (n.respond_to?(:env_prefixes) ? n : progenitor_of(n)) }
175
723
  end
176
724
 
725
+ # Extract the env_prefixes from the progenitor of the given object.
726
+ # @param object [Object] Object to retrieve the {.progenitor_of} and subsequently the {#env_prefixes}.
727
+ # @param style [#to_s] Type of hierarchical traversal.
728
+ # @param all [Boolean] Return inherited prefixes.
729
+ # If there is no progenitor of the object and all is +true+ then {.env_prefixes AppConfigFor.env_prefixes} will be returned.
730
+ # @return [Array<Symbol>] Environment prefixes for this object.
731
+ # @raise InvalidEnvInheritanceStyle - Attempt to use a style that is not one of the {EnvInheritanceStyles}.
732
+ # @see .env_prefixes Default prefixes
733
+ # @see .verified_style! Valid inheritance styles
177
734
  def progenitor_prefixes_of(object, style = nil, all = true)
178
- Array(progenitor_of(object, style)&.env_prefixes(all))
735
+ Array((progenitor_of(object, style) || all && AppConfigFor).try(:env_prefixes, all))
179
736
  end
180
737
 
181
- def progenitors_of(object, style = nil, terminate = true)
738
+ # List of hierarchical progenitors of an object.
739
+ # Hierarchical precedence is controlled by the style.
740
+ # @param object [Object] Object to get the progenitors from
741
+ # @param style [#to_s] Type of hierarchical traversal.
742
+ # @param unique [Boolean] Remove duplicate progenitors.
743
+ # @return [Array<Object>]
744
+ # @raise InvalidEnvInheritanceStyle - Attempt to use a style that is not one of the {EnvInheritanceStyles}.
745
+ # @see progenitor_of Progenitor of an object
746
+ # @see .verified_style! Valid inheritance styles
747
+ def progenitors_of(object, style = nil, unique = true)
182
748
  style = verified_style!(style, object)
183
- terminate = terminate && style != :none
749
+ unique = unique && style != :none
184
750
  if object && style != :none
185
751
  styles = style.to_s.split('_')
186
752
  if styles.size > 1
@@ -190,47 +756,92 @@ module AppConfigFor
190
756
  end
191
757
  else
192
758
  []
193
- end.yield_self { |result| terminate ? result.reverse.uniq.reverse + [self] : result }
759
+ end.yield_self { |result| unique ? result.reverse.uniq.reverse + [self] : result }
194
760
  end
195
761
 
196
- def remove_env_prefix(prefix, all = false)
197
- env_prefixes(all, false).delete(prefix_from(prefix))
762
+ # Remove an environmental prefix from the existing list.
763
+ # @param prefix [Symbol, Object] Prefix to remove.
764
+ # Non symbols are converted via {.prefix_from AppConfigFor.prefix_from}.
765
+ # @param _ Ignored. Unlike {#remove_env_prefix}, the first parameter is ignored as there is no inheritance at this point.
766
+ # @return [Array<Symbol>] Current prefixes (without inheritance)
767
+ # @note Prefixes removed here will affect all consumers of AppConfigFor. For targeted changes see: {#remove_env_prefix}
768
+ def remove_env_prefix(prefix, _ = false)
769
+ env_prefixes(false, false).delete(prefix_from(prefix))
770
+ env_prefixes(false)
198
771
  end
199
772
 
200
- def verified_style!(style, object = nil)
201
- style ||= object.respond_to?(:env_prefix_inheritance) ? object.send(:env_prefix_inheritance) : :namespace
773
+ # Verifies the inheritance style. If style is nil, the object, if given, will be queried for its env_inheritance.
774
+ # Otherwise the style will default to +:namespace:+
775
+ # @param style [#to_s] Inheritance style to verify.
776
+ # @param object [Object] Object to query for env_inheritance if style is nil.
777
+ # return [Symbol] A valid inheritance style.
778
+ # @raise InvalidEnvInheritanceStyle - An invalid inheritance style was received.
779
+ def verified_style!(style = nil, object = nil)
780
+ style ||= object.respond_to?(:env_inheritance) && object.env_inheritance || :namespace
202
781
  style = style.try(:to_sym) || style.to_s.to_sym
203
- EnvPrefixInheritanceStyles.include?(style) ? style : raise(InvalidEnvInheritanceStyle.new(style))
782
+ EnvInheritanceStyles.include?(style) ? style : raise(InvalidEnvInheritanceStyle.new(style))
204
783
  end
205
784
 
785
+ # Determine the name of the yml file from the object given. No pathing is assumed.
786
+ # Anything not a Pathname is converted to an underscored string with '/' characters changed to underscores and a '.yml' extension
787
+ # @param object [Object] Object to determine a yml name from.
788
+ #
789
+ # Determination by object type is as follows:
790
+ # * Pathname -> pathname
791
+ # * Module -> module.name
792
+ # * Class -> class.name
793
+ # * String -> string
794
+ # * Symbol -> symbol.to_s
795
+ # * other -> other.class.name
796
+ # @return [String, Pathname]
797
+ # @example
798
+ # module Some
799
+ # class App
800
+ # end
801
+ # end
802
+ #
803
+ # # All of the following return 'some_app.yml'
804
+ # yml_name_from(Some::App)
805
+ # yml_name_from(Some::App.new)
806
+ # yml_name_from(:some_app)
807
+ # yml_name_from('Some/App')
808
+ # yml_name_from('Some::App')
809
+ #
810
+ # # Pathnames receive no conversion
811
+ # yml_name_from(Pathname.new('not/a/yml_file.txt')) # => #<Pathname:not/a/yml_file.txt>
206
812
  def yml_name_from(object)
207
813
  if object.is_a?(Pathname)
208
814
  object
209
815
  else
210
816
  case object
211
- when Module
212
- object.name
213
- when String, Symbol
817
+ when String
818
+ object
819
+ when Symbol
214
820
  object.to_s
215
821
  else
216
- object.class.name
822
+ nearest_named_class(object).name
217
823
  end.underscore.gsub('/','_') + '.yml'
218
824
  end
219
825
  end
220
-
826
+
221
827
  private
222
828
 
829
+ # Add the config directory from the gem installation if this is a gem.
830
+ def add_gem_directory(base)
831
+ gem = Gem.loaded_specs[base.name.underscore]
832
+ base.add_config_directory(gem.gem_dir + '/config') if gem
833
+ end
834
+
223
835
  def extended(base)
224
836
  # Todo: Add the ability to check the default environments directly from base if the methods don't yet exist.
225
837
  # ie: base.development? is the same as base.env.development?
226
- base.add_env_prefix
838
+ add_gem_directory(base)
227
839
  end
228
840
 
229
841
  def included(base)
230
- base.add_env_prefix
842
+ add_gem_directory(base)
231
843
  end
232
844
 
233
845
  end
234
846
 
235
- end
236
-
847
+ end
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: app_config_for
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank Hall
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-12 00:00:00.000000000 Z
11
+ date: 2022-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: '7.0'
22
+ version: '8'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '5.0'
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: '7.0'
32
+ version: '8'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: rake
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +78,8 @@ files:
72
78
  - Rakefile
73
79
  - lib/app_config_for.rb
74
80
  - lib/app_config_for/errors.rb
81
+ - lib/app_config_for/gem_version.rb
82
+ - lib/app_config_for/legacy_support.rb
75
83
  - lib/app_config_for/version.rb
76
84
  - sig/app_config_for.rbs
77
85
  homepage: https://github.com/ChapterHouse/app_config_for
@@ -79,8 +87,8 @@ licenses:
79
87
  - MIT
80
88
  metadata:
81
89
  homepage_uri: https://github.com/ChapterHouse/app_config_for
82
- source_code_uri: https://github.com/ChapterHouse/app_config_for/tree/v0.0.3
83
- changelog_uri: https://github.com/ChapterHouse/app_config_for/blob/v0.0.3/CHANGELOG.md
90
+ source_code_uri: https://github.com/ChapterHouse/app_config_for/tree/v0.0.6.2
91
+ changelog_uri: https://github.com/ChapterHouse/app_config_for/blob/v0.0.6.2/CHANGELOG.md
84
92
  post_install_message:
85
93
  rdoc_options: []
86
94
  require_paths:
@@ -89,7 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
89
97
  requirements:
90
98
  - - ">="
91
99
  - !ruby/object:Gem::Version
92
- version: 2.6.0
100
+ version: 2.3.6
93
101
  required_rubygems_version: !ruby/object:Gem::Requirement
94
102
  requirements:
95
103
  - - ">="