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:
435 436 437 438 439 440 |
# File 'lib/sycl.rb', line 435 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
498 499 500 501 502 503 504 505 506 507 508 |
# 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 |
Class Method Details
.[](*args) ⇒ Object
:nodoc:
442 443 444 |
# File 'lib/sycl.rb', line 442 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.
456 457 458 459 460 |
# 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 |
Instance Method Details
#<=>(other) ⇒ Object
:nodoc:
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 598 599 600 601 |
# File 'lib/sycl.rb', line 573 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.
467 468 469 470 471 472 |
# File 'lib/sycl.rb', line 467 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.
555 556 557 558 559 560 561 562 563 564 565 |
# 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 |
#encode_with(coder) ⇒ Object
:nodoc:
713 714 715 716 |
# File 'lib/sycl.rb', line 713 def encode_with(coder) # :nodoc: coder.style = Psych::Nodes::Mapping::FLOW if @yaml_style == :inline coder.represent_map nil, sort end |
#get(path) ⇒ Object
515 516 517 518 519 520 521 522 523 524 525 526 527 528 |
# 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 |
#merge!(h) ⇒ Object
:nodoc:
481 482 483 484 |
# File 'lib/sycl.rb', line 481 def merge!(h) # :nodoc: h = Sycl::Hash.from_hash(h) unless h.is_a?(Sycl::Hash) super end |
#method(sym) ⇒ Object
:nodoc:
682 683 684 |
# File 'lib/sycl.rb', line 682 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.
606 607 608 |
# File 'lib/sycl.rb', line 606 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]}'
625 626 627 628 629 |
# 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) ⇒ 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.
536 537 538 539 540 541 542 543 544 545 546 547 548 549 |
# 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 |
#store(k, v) ⇒ Object
:nodoc:
474 475 476 477 478 479 |
# File 'lib/sycl.rb', line 474 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.
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 |
# 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 |
#update(h) ⇒ Object
:nodoc:
486 487 488 489 |
# File 'lib/sycl.rb', line 486 def update(h) # :nodoc: h = Sycl::Hash.from_hash(h) unless h.is_a?(Sycl::Hash) super end |
#yaml_postprocess(yaml) ⇒ Object
:nodoc:
667 668 669 |
# File 'lib/sycl.rb', line 667 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+$/, '') }
659 660 661 |
# File 'lib/sycl.rb', line 659 def yaml_postprocessor(&block) @yaml_postprocessor = block if block_given? end |
#yaml_preprocess! ⇒ Object
:nodoc:
663 664 665 |
# File 'lib/sycl.rb', line 663 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.
639 640 641 |
# File 'lib/sycl.rb', line 639 def yaml_preprocessor(&block) @yaml_preprocessor = block if block_given? end |