Class: Sycl::Hash
Overview
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}'
Defined Under Namespace
Classes: MockNativeType
Class Method Summary collapse
-
.[](*args) ⇒ Object
:nodoc:.
-
.from_hash(h) ⇒ Object
Create a Sycl::Array from a normal Hash or Hash-like object.
-
.load_file(f) ⇒ Object
Like Sycl::load_file(), a shortcut method to create a Sycl::Hash from loading and parsing YAML from a file.
Instance Method Summary collapse
-
#<=>(other) ⇒ Object
:nodoc:.
-
#[]=(k, v) ⇒ Object
Make sure that if we write to this hash, we promote any inputs to their Sycl equivalents.
-
#deep_merge(h) ⇒ Object
Deep merge two hashes (the new hash wins on conflicts).
-
#encode_with(coder) ⇒ Object
:nodoc:.
-
#get(path) ⇒ Object
Safe dotted notation reads: h.get(‘foo.bar’) == h[‘bar’].
-
#initialize(*args) ⇒ Hash
constructor
:nodoc:.
-
#merge!(h) ⇒ Object
:nodoc:.
-
#method(sym) ⇒ Object
:nodoc:.
- #method_missing(method_symbol, *args, &block) ⇒ Object
-
#render_inline! ⇒ Object
Make this hash, and its children, rendered in inline/flow style.
-
#render_values_inline! ⇒ Object
Keep rendering this hash in block (multi-line) style, but, make this array’s children rendered in inline/flow style.
-
#set(path, value) ⇒ Object
Dotted writes: h.set(‘foo.bar’ => ‘baz’) means h[‘bar’] = ‘baz’.
-
#store(k, v) ⇒ Object
:nodoc:.
-
#to_yaml(opts = {}) ⇒ Object
Render this object as YAML.
-
#update(h) ⇒ Object
:nodoc:.
-
#yaml_postprocess(yaml) ⇒ Object
:nodoc:.
-
#yaml_postprocessor(&block) ⇒ Object
Set a postprocessor hook which runs after YML is dumped, for example, via to_yaml() or Sycl::dump().
-
#yaml_preprocess! ⇒ Object
:nodoc:.
-
#yaml_preprocessor(&block) ⇒ Object
Set a preprocessor hook which runs before each time YAML is dumped, for example, via to_yaml() or Sycl::dump().
Constructor Details
#initialize(*args) ⇒ Hash
:nodoc:
431 432 433 434 435 436 |
# File 'lib/sycl.rb', line 431 def initialize(*args) # :nodoc: @yaml_preprocessor = nil @yaml_postprocessor = nil @yaml_style = nil super end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_symbol, *args, &block) ⇒ Object
494 495 496 497 498 499 500 501 502 503 504 |
# File 'lib/sycl.rb', line 494 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 |
Class Method Details
.[](*args) ⇒ Object
:nodoc:
438 439 440 |
# File 'lib/sycl.rb', line 438 def self.[](*args) # :nodoc: Sycl::Hash.from_hash super end |
.from_hash(h) ⇒ Object
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.
452 453 454 455 456 |
# File 'lib/sycl.rb', line 452 def self.from_hash(h) retval = Sycl::Hash.new h.each { |k, v| retval[k] = Sycl::from_object(v) } retval end |
Instance Method Details
#<=>(other) ⇒ Object
:nodoc:
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 |
# File 'lib/sycl.rb', line 569 def <=>(other) # :nodoc: self_keys = self.keys.sort other_keys = other.respond_to?(:keys) ? other.keys.sort : other.respond_to?(:sort) ? other.sort : other.respond_to?(:to_s) ? [other.to_s] : other ? [other] : [] while true if self_keys.empty? && other_keys.empty? return 0 elsif self_keys.empty? return 1 elsif other_keys.empty? return -1 else self_key = self_keys.shift other_key = other_keys.shift if self_key != other_key return self_key <=> other_key elsif other.is_a?(Hash) && self[self_key] != other[other_key] if self[self_ key].respond_to?(:<=>) return self[self_key] <=> other[other_key] else return self[self_key].to_s <=> other[other_key].to_s end end end end end |
#[]=(k, v) ⇒ Object
Make sure that if we write to this hash, we promote any inputs to their Sycl equivalents. This lets dot notation, styled YAML, and other Sycl goodies continue.
463 464 465 466 467 468 |
# File 'lib/sycl.rb', line 463 def []=(k, v) # :nodoc: unless v.is_a?(Sycl::Hash) || v.is_a?(Sycl::Array) v = Sycl::from_object(v) end super end |
#deep_merge(h) ⇒ Object
Deep merge two hashes (the new hash wins on conflicts). Hash or and Array objects in the new hash are promoted to Sycl variants.
551 552 553 554 555 556 557 558 559 560 561 |
# File 'lib/sycl.rb', line 551 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 |
#encode_with(coder) ⇒ Object
:nodoc:
709 710 711 712 |
# File 'lib/sycl.rb', line 709 def encode_with(coder) # :nodoc: coder.style = Psych::Nodes::Mapping::FLOW if @yaml_style == :inline coder.represent_map nil, sort end |
#get(path) ⇒ Object
511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
# File 'lib/sycl.rb', line 511 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 |
#merge!(h) ⇒ Object
:nodoc:
477 478 479 480 |
# File 'lib/sycl.rb', line 477 def merge!(h) # :nodoc: h = Sycl::Hash.from_hash(h) unless h.is_a?(Sycl::Hash) super end |
#method(sym) ⇒ Object
:nodoc:
678 679 680 |
# File 'lib/sycl.rb', line 678 def method(sym) # :nodoc: sym == :to_yaml ? MockNativeType.new : super end |
#render_inline! ⇒ Object
Make this hash, and its children, rendered in inline/flow style. The default is to render arrays in block (multi-line) style.
602 603 604 |
# File 'lib/sycl.rb', line 602 def render_inline! @yaml_style = :inline end |
#render_values_inline! ⇒ Object
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]}'
621 622 623 624 625 |
# File 'lib/sycl.rb', line 621 def render_values_inline! self.values.each do |v| v.render_inline! if v.respond_to?(:render_inline!) end end |
#set(path, value) ⇒ Object
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.
532 533 534 535 536 537 538 539 540 541 542 543 544 545 |
# File 'lib/sycl.rb', line 532 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 |
#store(k, v) ⇒ Object
:nodoc:
470 471 472 473 474 475 |
# File 'lib/sycl.rb', line 470 def store(k, v) # :nodoc: unless v.is_a?(Sycl::Hash) || v.is_a?(Sycl::Array) v = Sycl::from_object(v) end super end |
#to_yaml(opts = {}) ⇒ Object
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.
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 |
# File 'lib/sycl.rb', line 692 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 |
#update(h) ⇒ Object
:nodoc:
482 483 484 485 |
# File 'lib/sycl.rb', line 482 def update(h) # :nodoc: h = Sycl::Hash.from_hash(h) unless h.is_a?(Sycl::Hash) super end |
#yaml_postprocess(yaml) ⇒ Object
:nodoc:
663 664 665 |
# File 'lib/sycl.rb', line 663 def yaml_postprocess(yaml) # :nodoc: @yaml_postprocessor ? @yaml_postprocessor.call(yaml) : yaml end |
#yaml_postprocessor(&block) ⇒ Object
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+$/, '') }
655 656 657 |
# File 'lib/sycl.rb', line 655 def yaml_postprocessor(&block) @yaml_postprocessor = block if block_given? end |
#yaml_preprocess! ⇒ Object
:nodoc:
659 660 661 |
# File 'lib/sycl.rb', line 659 def yaml_preprocess! # :nodoc: @yaml_preprocessor.call(self) if @yaml_preprocessor end |
#yaml_preprocessor(&block) ⇒ Object
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.
635 636 637 |
# File 'lib/sycl.rb', line 635 def yaml_preprocessor(&block) @yaml_preprocessor = block if block_given? end |