Class: Sycl::Hash

Inherits:
Hash
  • Object
show all
Includes:
Comparable
Defined in:
lib/sycl.rb

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.

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.

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

Defined Under Namespace

Classes: MockNativeType

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Hash

Returns a new instance of Hash.



275
276
277
278
279
280
# File 'lib/sycl.rb', line 275

def initialize(*args)
  @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

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



322
323
324
325
326
327
328
329
330
331
332
# File 'lib/sycl.rb', line 322

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



282
283
284
# File 'lib/sycl.rb', line 282

def self.[](*args)
  Sycl::Hash.from_hash super
end

.from_hash(h) ⇒ Object



290
291
292
293
294
# File 'lib/sycl.rb', line 290

def self.from_hash(h)
  retval = Sycl::Hash.new
  h.each { |k, v| retval[k] = Sycl::from_object(v) }
  retval
end

.load_file(f) ⇒ Object



286
287
288
# File 'lib/sycl.rb', line 286

def self.load_file(f)
  Sycl::Hash.from_hash YAML::load_file f
end

Instance Method Details

#<=>(another) ⇒ Object



397
398
399
# File 'lib/sycl.rb', line 397

def <=>(another)
  self.to_str <=> another.to_str
end

#[]=(k, v) ⇒ Object Also known as: store

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.



301
302
303
304
305
306
# File 'lib/sycl.rb', line 301

def []=(k, v)
  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.



379
380
381
382
383
384
385
386
387
388
389
# File 'lib/sycl.rb', line 379

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



460
461
462
463
# File 'lib/sycl.rb', line 460

def encode_with(coder)
  coder.style = Psych::Nodes::Mapping::FLOW if @yaml_style == :inline
  coder.represent_map nil, sort
end

#get(path) ⇒ Object

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

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



339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/sycl.rb', line 339

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 Also known as: update



309
310
311
312
# File 'lib/sycl.rb', line 309

def merge!(h)
  h = Sycl::Hash.from_hash(h) unless h.is_a?(Sycl::Hash)
  super
end

#method(sym) ⇒ Object



449
450
451
# File 'lib/sycl.rb', line 449

def method(sym)
  sym == :to_yaml ? MockNativeType.new : super
end

#render_inline!Object

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



409
410
411
# File 'lib/sycl.rb', line 409

def render_inline!
  @yaml_style = :inline
end

#render_values_inline!Object



413
414
415
416
417
# File 'lib/sycl.rb', line 413

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.



360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/sycl.rb', line 360

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_strObject



401
402
403
# File 'lib/sycl.rb', line 401

def to_str
  self.keys.sort.first
end

#to_yaml(opts = {}) ⇒ Object



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/sycl.rb', line 466

def to_yaml(opts = {})
  yaml_preprocess!
  if defined?(YAML::ENGINE) && YAML::ENGINE.yamler == 'psych'
    opts ||= {}
    opts[:line_width] ||= 999999
    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_postprocess(yaml) ⇒ Object



434
435
436
# File 'lib/sycl.rb', line 434

def yaml_postprocess(yaml)
  @yaml_postprocessor ? @yaml_postprocessor.call(yaml) : yaml
end

#yaml_postprocessor(&block) ⇒ Object



426
427
428
# File 'lib/sycl.rb', line 426

def yaml_postprocessor(&block)
  @yaml_postprocessor = block if block_given?
end

#yaml_preprocess!Object



430
431
432
# File 'lib/sycl.rb', line 430

def yaml_preprocess!
  @yaml_preprocessor.call(self) if @yaml_preprocessor
end

#yaml_preprocessor(&block) ⇒ Object

Hooks to run before and after YAML dumping



422
423
424
# File 'lib/sycl.rb', line 422

def yaml_preprocessor(&block)
  @yaml_preprocessor = block if block_given?
end