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
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
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 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
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
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
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
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
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
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.
# 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
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
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