class SimplyGenius::Atmos::SettingsHash

Constants

INTERP_PATTERN
PATH_PATTERN

Attributes

_root_[RW]
enable_expansion[RW]
error_resolver[RW]

Public Class Methods

add_config(yml_file, key, value, additive: true) click to toggle source
# File lib/simplygenius/atmos/settings_hash.rb, line 169
def self.add_config(yml_file, key, value, additive: true)
  orig_config_with_comments = File.read(yml_file)

  comment_places = {}
  comment_lines = []
  orig_config_with_comments.each_line do |line|
    line.gsub!(/\s+$/, "\n")
    if line =~ /^\s*(#.*)?$/
      comment_lines << line
    else
      if comment_lines.present?
        comment_places[line.chomp] = comment_lines
        comment_lines = []
      end
    end
  end
  comment_places["<EOF>"] = comment_lines

  orig_config = SettingsHash.new((YAML.load_file(yml_file) rescue {}))
  # expansion disabled by default, but being explicit since we don't want
  # expansion when mutating config files from generators
  orig_config.enable_expansion = false
  orig_config.notation_put(key, value, additive: additive)
  new_config_no_comments = YAML.dump(orig_config.to_hash)
  new_config_no_comments.sub!(/\A---\n/, "")

  new_yml = ""
  new_config_no_comments.each_line do |line|
    line.gsub!(/\s+$/, "\n")
    cline = comment_places.keys.find {|k| line =~ /^#{k}/ }
    comments = comment_places[cline]
    comments.each {|comment| new_yml << comment } if comments
    new_yml << line
  end
  comment_places["<EOF>"].each {|comment| new_yml << comment }

  return new_yml
end

Public Instance Methods

[](key)
Also aliased as: orig_reader
Alias for: expanding_reader
each() { |key, self| ... } click to toggle source

allows expansion when iterating

# File lib/simplygenius/atmos/settings_hash.rb, line 58
def each
  each_key do |key|
    yield key, self[key]
  end
end
expand(value) click to toggle source
# File lib/simplygenius/atmos/settings_hash.rb, line 111
def expand(value)
  result = value
  case value
  when Hash
    value
  when String
    expand_string(value)
  when Enumerable
    value.map! {|v| expand(v)}
    # HACK: accounting for the case when someone wants to force an override using '^' as the first list item, when
    # there is no upstream to override (i.e. merge proc doesn't get triggered as key is unique, so just added verbatim)
    value.delete_at(0) if value[0] == "^"
    value
  else
    value
  end
end
expand_results(name, &blk) click to toggle source
# File lib/simplygenius/atmos/settings_hash.rb, line 21
def expand_results(name, &blk)
  # NOTE: we lookup locally first, then globally if a value is missing
  # locally.  To force a global lookup, use the explicit qualifier like
  # "_root_.path.to.config"

  value = blk.call(name)

  if value.nil? && _root_ && enable_expansion
    value = _root_[name]
  end

  if value.kind_of?(self.class) && value._root_.nil?
    value._root_ = _root_ || self
  end

  enable_expansion ? expand(value) : value
end
expand_string(obj) click to toggle source
# File lib/simplygenius/atmos/settings_hash.rb, line 82
def expand_string(obj)
  result = obj
  result.scan(INTERP_PATTERN).each do |substr, statement|
    # TODO: add an explicit check for cycles instead of relying on Stack error
    begin
      if substr.start_with?('##')
        val = substr[1..-1]
      else
        # TODO: be consistent with dot notation between eval and
        # notation_get.  eval ends up calling Hashie method_missing,
        # which returns nil if a key doesn't exist, causing a nil
        # exception for next item in chain, while notation_get returns
        # nil gracefully for the entire chain (preferred)
        val = eval(statement, binding, __FILE__)
      end
    rescue SystemStackError => e
      raise ConfigInterpolationError.new(format_error("Cycle in interpolated config", substr))
    rescue StandardError => e
      raise ConfigInterpolationError.new(format_error("Failing config statement", substr, e))
    end
    result = result.sub(substr, val.to_s)
  end

  result = true if result == "true"
  result = false if result == "false"

  result
end
expanding_reader(key) click to toggle source
# File lib/simplygenius/atmos/settings_hash.rb, line 39
def expanding_reader(key)
  expand_results(key) {|k| orig_reader(k) }
end
Also aliased as: []
fetch(key, *args) click to toggle source
Calls superclass method
# File lib/simplygenius/atmos/settings_hash.rb, line 43
def fetch(key, *args)
  expand_results(key) {|k| super(k, *args) }
end
format_error(msg, expr, ex=nil) click to toggle source
# File lib/simplygenius/atmos/settings_hash.rb, line 69
def format_error(msg, expr, ex=nil)
  file, line = nil, nil
  if error_resolver
    file, line = error_resolver.call(expr)
  end
  file_msg = file.nil? ? "" : " in #{File.basename(file)}:#{line}"
  msg = "#{msg} '#{expr}'#{file_msg}"
  if ex
    msg +=  " => #{ex.class} #{ex.message}"
  end
  return msg
end
notation_get(key) click to toggle source
# File lib/simplygenius/atmos/settings_hash.rb, line 129
def notation_get(key)
  path = key.to_s.split(PATH_PATTERN).compact
  path = path.collect {|p| p =~ /^\d+$/ ? p.to_i : p }
  result = nil

  begin
    result = deep_fetch(*path)
  rescue Hashie::Extensions::DeepFetch::UndefinedPathError => e
    logger.debug("Settings missing value for key='#{key}'")
  end

  return result
end
notation_put(key, value, additive: true) click to toggle source
# File lib/simplygenius/atmos/settings_hash.rb, line 143
def notation_put(key, value, additive: true)
  path = key.to_s.split(PATH_PATTERN).compact
  path = path.collect {|p| p =~ /^\d+$/ ? p.to_i : p }
  current_level = self
  path.each_with_index do |p, i|

    if i == path.size - 1
      if additive && current_level[p].is_a?(Array)
        current_level[p] = current_level[p] | Array(value)
      else
        current_level[p] = value
      end
    else
      if current_level[p].nil?
        if path[i+1].is_a?(Integer)
          current_level[p] = []
        else
          current_level[p] = {}
        end
      end
    end

    current_level = current_level[p]
  end
end
orig_reader(key)
Alias for: []
to_a() click to toggle source

allows expansion for to_a (which doesn't use each)

# File lib/simplygenius/atmos/settings_hash.rb, line 65
def to_a
  self.collect {|k, v| [k, v]}
end