class Sycl::Hash

A Sycl::Hash is like a Hash, but creating one from an hash blesses any child Array or Hash objects into Sycl::Array or Sycl::Hash objects. All the normal Hash methods are supported, and automatically promote any inputs into Sycl equivalents. The following example illustrates this:

h = Sycl::Hash.new
h['a'] = { 'b' => { 'c' => 'Hello, world!' } }

puts h.a.b.c   # outputs 'Hello, world!'

Hash contents can be accessed via “dot notation” (h.foo.bar means the same as h['bar']). However, h.foo.bar dies if h does not exist, so get() and set() methods exist: h.get('foo.bar') will return nil instead of dying if h does not exist. There is also a convenient deep_merge() that is like Hash#merge(), but also descends into and merges child nodes of the new hash.

A Sycl::Hash supports YAML preprocessing and postprocessing, and having individual nodes marked as being rendered in inline style. YAML output is also always sorted by key.

h = Sycl::Hash.from_hash({'b' => 'bravo', 'a' => 'alpha'})
h.render_inline!
h.yaml_preprocessor { |x| x.values.each { |e| e.capitalize! } }
h.yaml_postprocessor { |yaml| yaml.sub(/\A---\s+/, '') }

puts h['a']        # outputs 'alpha'
puts h.keys.first  # outputs 'a' or 'b' depending on Hash order
puts h.to_yaml     # outputs '{a: Alpha, b: Bravo}'

Public Class Methods

from_hash(h) click to toggle source

Create a Sycl::Array from a normal Hash or Hash-like object. Every child Array or Hash gets promoted to a Sycl::Array or Sycl::Hash.

# File lib/sycl.rb, line 456
def self.from_hash(h)
  retval = Sycl::Hash.new
  h.each { |k, v| retval[k] = Sycl::from_object(v) }
  retval
end
load_file(f) click to toggle source

Like Sycl::load_file(), a shortcut method to create a Sycl::Hash from loading and parsing YAML from a file.

# File lib/sycl.rb, line 449
def self.load_file(f)
  Sycl::Hash.from_hash YAML::load_file f
end

Public Instance Methods

deep_merge(h) click to toggle source

Deep merge two hashes (the new hash wins on conflicts). Hash or and Array objects in the new hash are promoted to Sycl variants.

# File lib/sycl.rb, line 555
def deep_merge(h)
  self.merge(h) do |key, v1, v2|
    if v1.is_a?(::Hash) && v2.is_a?(Sycl::Hash)
      self[key].deep_merge(v2)
    elsif v1.is_a?(::Hash) && v2.is_a?(::Hash)
      self[key].deep_merge(Sycl::Hash.from_hash(v2))
    else
      self[key] = Sycl::from_object(v2)
    end
  end
end
get(path) click to toggle source

Safe dotted notation reads: h.get('foo.bar') == h['bar'].

This will return nil instead of dying if h does not exist.

# File lib/sycl.rb, line 515
def get(path)
  path = path.split(/\./) if path.is_a?(String)
  candidate = self
  while !path.empty?
    key = path.shift
    if candidate[key]
      candidate = candidate[key]
    else
      candidate = nil
      last
    end
  end
  candidate
end
method_missing(method_symbol, *args, &block) click to toggle source

Allow method call syntax: h.foo.bar.baz == h['bar’].

Accessing hash keys whose names overlap with names of Ruby Object built-in methods (id, type, etc.) will still need to be passed in with bracket notation (h instead of h.type).

# File lib/sycl.rb, line 498
def method_missing(method_symbol, *args, &block)
  key = method_symbol.to_s
  set = key.chomp!('=')
  if set
    self[key] = args.first
  elsif self.key?(key)
    self[key]
  else
    nil
  end
end
render_inline!() click to toggle source

Make this hash, and its children, rendered in inline/flow style. The default is to render arrays in block (multi-line) style.

# File lib/sycl.rb, line 606
def render_inline!
  @yaml_style = :inline
end
render_values_inline!() click to toggle source

Keep rendering this hash in block (multi-line) style, but, make this array's children rendered in inline/flow style.

Example:

h = Sycl::Hash.new
h['one'] = 'two'
h['three'] = %w{four five}
h.yaml_postprocessor { |yaml| yaml.sub(/\A---\s+/, '') }

h.render_values_inline!
puts h.to_yaml  # output: "one: two\nthree: [five four]"
h.render_inline!
puts h.to_yaml  # output: '{one: two, three: [five four]}'
# File lib/sycl.rb, line 625
def render_values_inline!
  self.values.each do |v|
    v.render_inline! if v.respond_to?(:render_inline!)
  end
end
set(path, value) click to toggle source

Dotted writes: h.set('foo.bar' => 'baz') means h['bar'] = 'baz'.

This will auto-vivify any missing intervening hash keys, and also promote Hash and Array objects in the input to Scyl variants.

# File lib/sycl.rb, line 536
def set(path, value)
  path = path.split(/\./) if path.is_a?(String)
  target = self
  while path.size > 1
    key = path.shift
    if !(target.key?(key) && target[key].is_a?(::Hash))
      target[key] = Sycl::Hash.new
    else
      target[key] = Sycl::Hash.from_hash(target[key])
    end
    target = target[key]
  end
  target[path.first] = value
end
to_yaml(opts = {}) click to toggle source

Render this object as YAML. Before rendering, run the object through any yaml_preprocessor() code block. After rendering, filter the YAML text through any yaml_postprocessor() code block.

Nodes marked with render_inline!() or render_values_inline!() will be output in flow/inline style, all hashes and arrays will be sorted, and we set a long line width to more or less support line wrap under the Psych library.

Calls superclass method
# File lib/sycl.rb, line 696
def to_yaml(opts = {})
  yaml_preprocess!
  if defined?(YAML::ENGINE) && YAML::ENGINE.yamler == 'psych'
    opts ||= {}
    opts[:line_width] ||= 999999  # Psych doesn't let you disable line wrap
    yaml = super
  else
    yaml = YAML::quick_emit(self, opts) do |out|
      out.map(nil, @yaml_style || to_yaml_style) do |map|
        sort.each { |k, v| map.add(k, v) }
      end
    end
  end
  yaml_postprocess yaml
end
yaml_postprocessor(&block) click to toggle source

Set a postprocessor hook which runs after YML is dumped, for example, via to_yaml() or Sycl::dump(). The hook is a block that gets the YAML text string as an argument, and returns a new, possibly different, YAML text string.

A common example use case is to suppress the initial document separator, which is just visual noise when humans are viewing or editing a single YAML file:

a.yaml_postprocessor { |yaml| yaml.sub(/\A---\s+/, '') }

Your conventions might also prohibit trailing whitespace, which at least the Syck library will tack on the end of YAML hash keys:

a.yaml_postprocessor { |yaml| yaml.gsub(/:\s+$/, '') }
# File lib/sycl.rb, line 659
def yaml_postprocessor(&block)
  @yaml_postprocessor = block if block_given?
end
yaml_preprocessor(&block) click to toggle source

Set a preprocessor hook which runs before each time YAML is dumped, for example, via to_yaml() or Sycl::dump(). The hook is a block that gets the object itself as an argument. The hook can then set render_inline!() or similar style arguments, prune nil or empty leaf values from hashes, or do whatever other styling needs to be done before a Sycl object is rendered as YAML.

# File lib/sycl.rb, line 639
def yaml_preprocessor(&block)
  @yaml_preprocessor = block if block_given?
end