Module: Collapsium::EnvironmentOverride

Extended by:
Support::ArrayMethods, Support::HashMethods, Support::Methods
Includes:
RecursiveDup
Defined in:
lib/collapsium/environment_override.rb

Overview

The EnvironmentOverride module wraps read access methods to return the contents of environment variables derived from the key instead of the value contained in the Hash.

The environment variable to use is derived from the key by replacing all consecutive occurrences of non-alphanumeric characters with an underscore (_), and converting the alphabetic characters to upper case. Leading and trailing underscores will be stripped.

For example, the key “[email protected]” will become “SOME_EMAIL_ORG”.

If PathedAccess is also used, the :path_prefix of nested hashes will be consulted after converting it in the same manner. NOTE: include EnvironmentOverride after PathedAccess for both to work well together.

Constant Summary collapse

ENV_ACCESS_READER =

Returns a proc read access.

proc do |wrapped_method, *args, &block|
  # If there are no arguments, there's nothing to do with paths. Just
  # delegate to the hash.
  if args.empty?
    next wrapped_method.call(*args, &block)
  end

  # The method's receiver is encapsulated in the wrapped_method; we'll
  # use it a few times so let's reduce typing. This is essentially the
  # equivalent of `self`.
  receiver = wrapped_method.receiver

  # All KEYED_READ_METHODS have a key as the first argument.
  key = args[0]

  # Grab matching environment variable names. If PathedAccess is
  # supported, we'll try environment variables of the path, starting
  # with the full qualified path and ending with just the last key
  # component.
  env_keys = EnvironmentOverride.environment_variables_for_key(receiver, key)

  # When we have the keys (in priority order), try to see if the
  # environment yields something useful.
  value = EnvironmentOverride.override_value(env_keys, receiver, key)

  if not value.nil?
    # We can't just return the value, because that doesn't respect the
    # method being called. We also can't store the value, because that
    # would not reset the Hash after the environment variable was
    # cleared.
    #
    # We can deal with this by duplicating the receiver, writing the value
    # we found into the appropriate key, and then sending the
    # wrapped_method to the duplicate.
    double = receiver.recursive_dup
    double[key] = value
    meth = double.method(wrapped_method.name)
    next meth.call(*args, &block)
  end

  # Otherwise, fall back on the super method.
  next wrapped_method.call(*args, &block)
end.freeze

Constants included from Support::Methods

Support::Methods::BUILTINS, Support::Methods::WRAPPER_HASH

Constants included from Support::ArrayMethods

Support::ArrayMethods::INDEXED_READ_METHODS, Support::ArrayMethods::INDEXED_WRITE_METHODS, Support::ArrayMethods::READ_METHODS, Support::ArrayMethods::WRITE_METHODS

Constants included from Support::HashMethods

Support::HashMethods::KEYED_READ_METHODS, Support::HashMethods::KEYED_WRITE_METHODS, Support::HashMethods::READ_METHODS, Support::HashMethods::WRITE_METHODS

Constants included from ViralCapabilities

ViralCapabilities::DEFAULT_ANCESTORS, ViralCapabilities::ENHANCED_MARKER, ViralCapabilities::READ_METHODS, ViralCapabilities::WRITE_METHODS

Class Method Summary collapse

Methods included from Support::Methods

builtins, loop_detected?, repeated, resolve_helpers, wrap_method, wrappers

Methods included from RecursiveDup

#recursive_dup

Methods included from ViralCapabilities

call_virality, copy_mods, enhance_array_value, enhance_hash_value, enhance_value, #extended, #included, #prepended, set_ancestors

Class Method Details

.enhance(base) ⇒ Object



103
104
105
106
107
108
109
110
111
# File 'lib/collapsium/environment_override.rb', line 103

def enhance(base)
  # Make the capabilities of classes using EnvironmentOverride viral.
  base.extend(ViralCapabilities)

  # Wrap read accessor functions to deal with paths
  (INDEXED_READ_METHODS + KEYED_READ_METHODS).each do |method|
    wrap_method(base, method, raise_on_missing: false, &ENV_ACCESS_READER)
  end
end

.environment_variables_for_key(receiver, key) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/collapsium/environment_override.rb', line 113

def environment_variables_for_key(receiver, key)
  env_keys = []
  if receiver.respond_to?(:path_prefix)

    # If we have a prefix, use it.
    components = []
    if not receiver.path_prefix.nil?
      components += receiver.path_components(receiver.path_prefix)
    end

    # The key has its own components. If the key components and prefix
    # components overlap (it happens), ignore the duplication.
    key_comps = receiver.path_components(key.to_s)
    if key_comps.first == components.last
      components.pop
    end
    components += key_comps

    # Start with most qualified, shifting off the first component
    # until we reach just the last component.
    loop do
      path = receiver.normalize_path(components)
      env_keys << path
      components.shift
      if components.empty?
        break
      end
    end
  else
    env_keys = [key.to_s]
  end
  env_keys.map! { |k| key_to_env(k) }
  env_keys.reject!(&:empty?)
  env_keys.uniq!

  return env_keys
end

.extended(base) ⇒ Object



95
96
97
# File 'lib/collapsium/environment_override.rb', line 95

def extended(base)
  enhance(base)
end

.included(base) ⇒ Object

proc



91
92
93
# File 'lib/collapsium/environment_override.rb', line 91

def included(base)
  enhance(base)
end

.key_to_env(key) ⇒ Object



188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/collapsium/environment_override.rb', line 188

def key_to_env(key)
  # First, convert to upper case
  env_key = key.upcase

  # Next, replace non-alphanumeric characters to underscore. This also
  # collapses them into a single undescore.
  env_key.gsub!(/[^[:alnum:]]+/, '_')

  # Strip leading and trailing underscores.
  env_key.gsub!(/^_*(.*?)_*$/, '\1')

  return env_key
end

.override_value(variables, receiver, key) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/collapsium/environment_override.rb', line 151

def override_value(variables, receiver, key)
  value = nil
  variables.each do |env_key|
    # Grab the environment value; skip if there's nothing there.
    env_value = ENV[env_key]
    if env_value.nil?
      next
    end

    # If the environment variable parses as JSON, that's great, we'll use
    # the parsed result. Otherwise use it as a string.
    require 'json'
    # rubocop:disable Lint/HandleExceptions
    parsed = env_value
    begin
      parsed = JSON.parse(env_value)
    rescue JSON::ParserError
      # Do nothing. We just use the env_value verbatim.
    end
    # rubocop:enable Lint/HandleExceptions

    # Excellent, we've got an environment variable. Only use it if it
    # changes something, though. We'll temporarily unset the environment
    # variable while fetching the old value.
    ENV.delete(env_key)
    old_value = receiver[key]
    ENV[env_key] = env_value # not the parsed value!

    if parsed != old_value
      value = parsed
    end
    break
  end

  return value
end

.prepended(base) ⇒ Object



99
100
101
# File 'lib/collapsium/environment_override.rb', line 99

def prepended(base)
  enhance(base)
end