Module: HierarchicalConfig

Extended by:
T::Sig
Defined in:
lib/hierarchical_config/version.rb,
lib/hierarchical_config.rb

Overview

typed: true

Defined Under Namespace

Modules: ConfigStruct

Constant Summary collapse

REQUIRED =
:REQUIRED
ClassOrModule =
T.type_alias{T.any(Class, Module)}
VERSION =
'0.13.2'.freeze
@@root_index =

rubocop:disable Style/ClassVars

T.let(0, Integer)

Class Method Summary collapse

Class Method Details

.build_config(current_item, name, parent_class) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/hierarchical_config.rb', line 143

def build_config(current_item, name, parent_class)
  case current_item
  when Hash
    return current_item.symbolize_keys if current_item.keys.to_a.any?{|k| k =~ /^[0-9]/ || k =~ /[- ]/}

    current_type = parent_class.const_get(inflect_typename(name))
    current_type.new(Hash[current_item.map{|key, value| [key.to_sym, build_config(value, key, current_type)]}]) # rubocop:disable Style/HashConversion
  when Array
    current_item.each_with_index.map do |item, index|
      build_config(item, "#{name}_#{index}", parent_class)
    end.freeze
  else
    current_item.freeze
  end
end

.build_new_rootObject



160
161
162
163
# File 'lib/hierarchical_config.rb', line 160

def build_new_root
  @@root_index += 1 # rubocop:disable Style/ClassVars
  const_set("ConfigRoot#{@@root_index}", Class.new)
end

.build_types(current_item, name, parent_class) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
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
# File 'lib/hierarchical_config.rb', line 101

def build_types(current_item, name, parent_class)
  case current_item
  when Hash
    new_type_name = inflect_typename(name)

    return Hash if current_item.keys.to_a.any?{|k| k =~ /^[0-9]/ || k =~ /[- ]/}

    new_type =
      if parent_class.const_defined?(new_type_name, false)
        parent_class.const_get(new_type_name, false)
      else
        parent_class.const_set(new_type_name, Class.new(T::Struct).tap{|c| c.include ConfigStruct})
      end

    current_item.each do |key, value|
      next if new_type.props.key?(key.to_sym)

      new_type.const key.to_sym, build_types(value, key, new_type)
      new_type.send(:define_method, "#{key}?") do
        !!send(key)
      end
    end

    new_type
  when Array
    types = current_item.each_with_index.map do |item, index|
      build_types(item, "#{name}_#{index}", parent_class)
    end
    case types.size
    when 0
      T.untyped
    when 1
      T::Array[types.first]
    else
      T::Array[T.unsafe(T).any(*types)]
    end
  else
    current_item.class
  end
end

.detect_errors(value, path) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/hierarchical_config.rb', line 83

def detect_errors(value, path)
  errors = T.let([], T::Array[String])
  case value
  when Hash
    value.each do |key, item|
      errors += detect_errors(item, "#{path}.#{key}")
    end
  when Array
    value.each_with_index do |item, index|
      errors += detect_errors(item, "#{path}[#{index}]")
    end
  when REQUIRED
    errors << "#{path} is REQUIRED"
  end
  errors
end

.load_config(name, dir, environment, preprocess_with = :erb, root_class = build_new_root) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/hierarchical_config.rb', line 174

def load_config(name, dir, environment, preprocess_with = :erb, root_class = build_new_root)
  primary_config_file   = "#{dir}/#{name}.yml"
  overrides_config_file = "#{dir}/#{name}-overrides.yml"

  config_hash = load_hash_for_env(primary_config_file, environment, preprocess_with)

  if File.exist?(overrides_config_file)
    overrides_config_hash = load_hash_for_env(overrides_config_file, environment, preprocess_with)
    config_hash = deep_merge(config_hash, overrides_config_hash)
  end

  errors = detect_errors(config_hash, name)
  raise errors.map{|error| "#{error} for #{environment}"}.inspect unless errors.empty?

  build_types(config_hash, name, root_class)

  build_config(config_hash, name, root_class)
end

.load_hash_for_env(file, environment, preprocess_with) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/hierarchical_config.rb', line 200

def load_hash_for_env(file, environment, preprocess_with)
  file_contents = File.read(file)
  yaml_contents = case preprocess_with
                  when :erb
                    ERB.new(file_contents).result
                  when nil
                    file_contents
                  else
                    raise "Unknown preprocessor <#{preprocess_with}>"
                  end
  yaml_config   = YAML.safe_load(yaml_contents)

  ordered_stanza_labels = []
  ordered_stanza_labels << 'defaults' if yaml_config.key? 'defaults'
  ordered_stanza_labels += yaml_config.keys.grep(/^defaults\[.*#{environment}/).sort_by{|a| a.count(',')}
  ordered_stanza_labels << environment if yaml_config.key? environment

  config = deep_merge_hashes_in_keys(ordered_stanza_labels, yaml_config)

  env_config_labels = []
  env_config_labels << 'env_vars' if yaml_config.key? 'env_vars'
  env_config_labels += yaml_config.keys.grep(/^env_vars\[.*#{environment}/).sort_by{|a| a.count(',')}

  env_config = deep_merge_hashes_in_keys(env_config_labels, yaml_config)
  env_config = fill_in_env_vars(env_config)

  deep_merge(config, env_config)
rescue StandardError => e
  raise <<-ERROR
    Error loading config from file #{file}.
    #{$ERROR_INFO.inspect}
    #{$ERROR_POSITION}
    #{e}
  ERROR
end