Module: Psychgus

Extended by:
PsychDropIn
Includes:
Stylables, Stylers
Defined in:
lib/psychgus.rb,
lib/psychgus/styler.rb,
lib/psychgus/stylers.rb,
lib/psychgus/version.rb,
lib/psychgus/blueberry.rb,
lib/psychgus/stylables.rb,
lib/psychgus/ext/core_ext.rb,
lib/psychgus/ext/node_ext.rb,
lib/psychgus/super_sniffer.rb,
lib/psychgus/ext/yaml_tree_ext.rb,
lib/psychgus/styled_tree_builder.rb,
lib/psychgus/super_sniffer/parent.rb,
lib/psychgus/styled_document_stream.rb

Overview

– This file is part of Psychgus. Copyright © 2019 Bradley Whited

SPDX-License-Identifier: LGPL-3.0-or-later ++

Defined Under Namespace

Modules: Blueberry, Ext, PsychDropIn, Stylables, Styler, Stylers Classes: StyledDocumentStream, StyledTreeBuilder, SuperSniffer

Constant Summary collapse

NODE_CLASS_ALIASES =
{Doc: :Document,Map: :Mapping,Seq: :Sequence}.freeze
OPTIONS_ALIASES =
{canon: :canonical,indent: :indentation}.freeze
MAPPING_ANY =
node_const(:mapping,:any)
MAPPING_BLOCK =
node_const(:mapping,:block)
MAPPING_FLOW =
node_const(:mapping,:flow)
MAP_ANY =
MAPPING_ANY
MAP_BLOCK =
MAPPING_BLOCK
MAP_FLOW =
MAPPING_FLOW
SCALAR_ANY =
node_const(:scalar,:any)
SCALAR_PLAIN =
node_const(:scalar,:plain)
SCALAR_SINGLE_QUOTED =
node_const(:scalar,:single_quoted)
SCALAR_DOUBLE_QUOTED =
node_const(:scalar,:double_quoted)
SCALAR_LITERAL =
node_const(:scalar,:literal)
SCALAR_FOLDED =
node_const(:scalar,:folded)
SEQUENCE_ANY =
node_const(:sequence,:any)
SEQUENCE_BLOCK =
node_const(:sequence,:block)
SEQUENCE_FLOW =
node_const(:sequence,:flow)
SEQ_ANY =
SEQUENCE_ANY
SEQ_BLOCK =
SEQUENCE_BLOCK
SEQ_FLOW =
SEQUENCE_FLOW
STREAM_ANY =
node_const(:stream,:any)
STREAM_UTF8 =
node_const(:stream,:utf8)
STREAM_UTF16LE =
node_const(:stream,:utf16le)
STREAM_UTF16BE =
node_const(:stream,:utf16be)
VERSION =

Version of this gem in “#.#.#” format.

'1.3.5'

Class Method Summary collapse

Methods included from PsychDropIn

add_builtin_type, add_domain_type, add_tag, load, load_file, load_stream, remove_type, safe_load, to_json

Class Method Details

.dump(object, io = nil, **options) ⇒ String, Object

Convert object to YAML and dump to io.

object, io, and options are used like in Psych.dump so can be a drop-in replacement for Psych.

Parameters:

  • object (Object)

    the Object to convert to YAML and dump

  • io (nil, IO, Hash) (defaults to: nil)

    the IO to dump the YAML to or the options Hash; if nil, will use StringIO

  • options (Hash)

    the options (or keyword args) to use; see dump_stream

Returns:

  • (String, Object)

    the result of converting object to YAML using the params

See Also:



381
382
383
# File 'lib/psychgus.rb', line 381

def self.dump(object,io=nil,**options)
  return dump_stream(object,io: io,**options)
end

.dump_file(filename, *objects, mode: 'w', perm: nil, opt: nil, **options) ⇒ Object

Convert objects to YAML and dump to a file.

Examples:

Psychgus.dump_file('my_dir/my_file.yaml',my_object1,my_object2,mode: 'w:UTF-16',
                   stylers: MyStyler.new())
Psychgus.dump_file('my_file.yaml',my_object,stylers: [MyStyler1.new(),MyStyler2.new()])

Parameters:

  • filename (String)

    the name of the file (and path) to dump to

  • objects (Object, Array<Object>)

    the Object(s) to convert to YAML and dump

  • mode (String, Integer) (defaults to: 'w')

    the IO open mode to use; examples:

    ‘w:UTF-8’

    create a new file or truncate an existing file and use UTF-8 encoding;

    ‘a:UTF-16’

    create a new file or append to an existing file and use UTF-16 encoding

  • perm (Integer) (defaults to: nil)

    the permission bits to use (platform dependent)

  • opt (Hash) (defaults to: nil)

    Hash of keyword args to pass to File.open()

  • options (Hash)

    the options (or keyword args) to use; see dump_stream

See Also:



407
408
409
410
411
412
413
# File 'lib/psychgus.rb', line 407

def self.dump_file(filename,*objects,mode: 'w',perm: nil,opt: nil,**options)
  opt = Hash(opt)

  File.open(filename,mode,perm,**opt) do |file|
    file.write(dump_stream(*objects,**options))
  end
end

.dump_stream(*objects, io: nil, stylers: nil, deref_aliases: false, **options) ⇒ String, Object

Convert objects to YAML and dump to io.

io and options are used like in Psych.dump so can be a drop-in replacement for Psych.

Parameters:

  • objects (Object, Array<Object>)

    the Object(s) to convert to YAML and dump

  • io (nil, IO, Hash) (defaults to: nil)

    the IO to dump the YAML to or the options Hash; if nil, will use StringIO

  • stylers (nil, Styler, Array<Styler>) (defaults to: nil)

    the Styler(s) to use when converting to YAML

  • deref_aliases (true, false) (defaults to: false)

    whether to dereference aliases; output the actual value instead of the alias

  • options (Hash)

    the options (or keyword args) to use when converting to YAML:

    :indent

    Alias for :indentation. :indentation will override this.

    :indentation

    Default: 2. Number of space characters used to indent. Acceptable value should be in 0..9 range, else ignored.

    :line_width

    Default: 0 (meaning “wrap at 81”). Max character to wrap line at.

    :canon

    Alias for :canonical. :canonical will override this.

    :canonical

    Default: false. Write “canonical” YAML form (very verbose, yet strictly formal).

    :header

    Default: false. Write %YAML [version] at the beginning of document.

Returns:

  • (String, Object)

    the result of converting object to YAML using the params

See Also:



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/psychgus.rb', line 441

def self.dump_stream(*objects,io: nil,stylers: nil,deref_aliases: false,**options)
  # If you call this method with only a Hash that uses symbols as keys,
  # then options will be set to the Hash, instead of objects.
  #
  # For example, the below will be stored in options, not objects:
  # - dump_stream({:coffee => {:roast => [],:style => []}})
  #
  # This if-statement is guaranteed because dump_stream([]) and dump_stream(nil)
  # will produce [[]] and [nil], which are not empty.
  #
  # dump_stream() w/o any args is the only problem, but resolved w/ [nil].
  if objects.empty?
    objects = options.empty? ? [nil] : [options]
    options = {}
  end

  if io.is_a?(Hash)
    options = io
    io = nil
  end

  if !options.empty?
    OPTIONS_ALIASES.each do |option_alias,actual_option|
      if options.key?(option_alias) && !options.key?(actual_option)
        options[actual_option] = options[option_alias]
      end
    end
  end

  visitor = Psych::Visitors::YAMLTree.create(options,StyledTreeBuilder.new(*stylers,
    deref_aliases: deref_aliases))

  if objects.empty?
    # Else, will throw a cryptic NoMethodError:
    # - "psych/tree_builder.rb:in `set_end_location':
    #    undefined method `end_line=' for nil:NilClass (NoMethodError)"
    #
    # This should never occur because of the if-statement at the top of this method.
    visitor << nil
  else
    objects.each do |object|
      visitor << object
    end
  end

  return visitor.tree.yaml(io,options)
end

.hierarchy(*objects, **kargs) ⇒ String

Get a visual hierarchy of the levels as a String.

This is useful for determining the correct level/position when writing a Styler.

Examples:

require 'psychgus'

burgers = {
  :burgers => {
    :classic => {:sauce  => %w(Ketchup Mustard),
                 :cheese => 'American',
                 :bun    => 'Sesame Seed'},
    :bbq     => {:sauce  => 'Honey BBQ',
                 :cheese => 'Cheddar',
                 :bun    => 'Kaiser'},
    :fancy   => {:sauce  => 'Spicy Wasabi',
                 :cheese => 'Smoked Gouda',
                 :bun    => 'Hawaiian'}
  },
  :toppings => [
    'Mushrooms',
    %w(Lettuce Onions Pickles Tomatoes),
    [%w(Ketchup Mustard), %w(Salt Pepper)]
  ]
}

puts Psychgus.hierarchy(burgers)

# Output:
# ---
# (level:position):current_node - <parent:(parent_level:parent_position)>
# ---
# (1:1):Psych::Nodes::Stream - <root:(0:0)>
# (1:1):Psych::Nodes::Document - <stream:(1:1)>
# (1:1):Psych::Nodes::Mapping - <doc:(1:1)>
#  (2:1)::burgers - <map:(1:1)>
#   (3:1):Psych::Nodes::Mapping - <:burgers:(2:1)>
#    (4:1)::classic - <map:(3:1)>
#     (5:1):Psych::Nodes::Mapping - <:classic:(4:1)>
#      (6:1)::sauce - <map:(5:1)>
#       (7:1):Psych::Nodes::Sequence - <:sauce:(6:1)>
#        (8:1):Ketchup - <seq:(7:1)>
#        (8:2):Mustard - <seq:(7:1)>
#      (6:2)::cheese - <map:(5:1)>
#       (7:1):American - <:cheese:(6:2)>
#      (6:3)::bun - <map:(5:1)>
#       (7:1):Sesame Seed - <:bun:(6:3)>
#    (4:2)::bbq - <map:(3:1)>
#     (5:1):Psych::Nodes::Mapping - <:bbq:(4:2)>
#      (6:1)::sauce - <map:(5:1)>
#       (7:1):Honey BBQ - <:sauce:(6:1)>
#      (6:2)::cheese - <map:(5:1)>
#       (7:1):Cheddar - <:cheese:(6:2)>
#      (6:3)::bun - <map:(5:1)>
#       (7:1):Kaiser - <:bun:(6:3)>
#    (4:3)::fancy - <map:(3:1)>
#     (5:1):Psych::Nodes::Mapping - <:fancy:(4:3)>
#      (6:1)::sauce - <map:(5:1)>
#       (7:1):Spicy Wasabi - <:sauce:(6:1)>
#      (6:2)::cheese - <map:(5:1)>
#       (7:1):Smoked Gouda - <:cheese:(6:2)>
#      (6:3)::bun - <map:(5:1)>
#       (7:1):Hawaiian - <:bun:(6:3)>
#  (2:2)::toppings - <map:(1:1)>
#   (3:1):Psych::Nodes::Sequence - <:toppings:(2:2)>
#    (4:1):Mushrooms - <seq:(3:1)>
#    (4:2):Psych::Nodes::Sequence - <seq:(3:1)>
#     (5:1):Lettuce - <seq:(4:2)>
#     (5:2):Onions - <seq:(4:2)>
#     (5:3):Pickles - <seq:(4:2)>
#     (5:4):Tomatoes - <seq:(4:2)>
#    (4:3):Psych::Nodes::Sequence - <seq:(3:1)>
#     (5:1):Psych::Nodes::Sequence - <seq:(4:3)>
#      (6:1):Ketchup - <seq:(5:1)>
#      (6:2):Mustard - <seq:(5:1)>
#     (5:2):Psych::Nodes::Sequence - <seq:(4:3)>
#      (6:1):Salt - <seq:(5:2)>
#      (6:2):Pepper - <seq:(5:2)>

Parameters:

Returns:

  • (String)

    the visual hierarchy of levels

See Also:



575
576
577
578
579
580
581
# File 'lib/psychgus.rb', line 575

def self.hierarchy(*objects,**kargs)
  styler = Stylers::HierarchyStyler.new(**kargs)

  dump_stream(*objects,stylers: styler,**kargs)

  return styler.to_s
end

.node_class(name) ⇒ Class

Get a Class (constant) from Psych::Nodes.

Some names have aliases:

:doc => :document
:map => :mapping
:seq => :sequence

Parameters:

  • name (Symbol, String)

    the name of the class from Psych::Nodes

Returns:

  • (Class)

    a class from Psych::Nodes

See Also:



317
318
319
320
321
322
323
324
# File 'lib/psychgus.rb', line 317

def self.node_class(name)
  name = name.to_sym.capitalize

  actual_name = NODE_CLASS_ALIASES[name]
  name = actual_name unless actual_name.nil?

  return Psych::Nodes.const_get(name)
end

.node_const(class_name, const_name, lenient = true) ⇒ Integer, Object

Get a constant from a Psych::Nodes class (using node_class).

Parameters:

  • class_name (Symbol, String)

    the name of the class to get using node_class

  • const_name (Symbol, String)

    the constant to get from the class

  • lenient (true, false) (defaults to: true)

    if true, will return 0 if not const_defined?(), else raise an error

Returns:

  • (Integer, Object)

    the constant value from the class (usually an int)

See Also:



335
336
337
338
339
340
341
# File 'lib/psychgus.rb', line 335

def self.node_const(class_name,const_name,lenient=true)
  node_class = node_class(class_name)
  const_name = const_name.to_sym.upcase

  return 0 if lenient && !node_class.const_defined?(const_name,true)
  return node_class.const_get(const_name,true)
end

.parse(yaml, **kargs) ⇒ Psych::Nodes::Document

Parse yaml into a Psych::Nodes::Document.

If you’re just going to call to_ruby(), then using this method is unnecessary, and the styler(s) will do nothing for you.

Parameters:

  • yaml (String)

    the YAML to parse

  • kargs (Hash)

    the keyword args to use; see parse_stream

Returns:

  • (Psych::Nodes::Document)

    the parsed Document node

See Also:



596
597
598
599
600
601
602
# File 'lib/psychgus.rb', line 596

def self.parse(yaml,**kargs)
  parse_stream(yaml,**kargs) do |node|
    return node
  end

  return false
end

.parse_file(filename, fallback: false, mode: 'r:BOM|UTF-8', opt: nil, **kargs) ⇒ Psych::Nodes::Document

Parse a YAML file into a Psych::Nodes::Document.

If you’re just going to call to_ruby(), then using this method is unnecessary, and the styler(s) will do nothing for you.

Parameters:

  • filename (String)

    the name of the YAML file (and path) to parse

  • fallback (Object) (defaults to: false)

    the return value when nothing is parsed

  • mode (String, Integer) (defaults to: 'r:BOM|UTF-8')

    the IO open mode to use; example: ‘r:BOM|UTF-8’

  • opt (Hash) (defaults to: nil)

    Hash of keyword args to pass to File.open()

  • kargs (Hash)

    the keyword args to use; see parse_stream

Returns:

  • (Psych::Nodes::Document)

    the parsed Document node

See Also:

  • parse_stream
  • Psych.parse_file
  • Psych::Nodes::Document
  • File.open
  • IO.new


622
623
624
625
626
627
628
629
630
# File 'lib/psychgus.rb', line 622

def self.parse_file(filename,fallback: false,mode: 'r:BOM|UTF-8',opt: nil,**kargs)
  opt = Hash(opt)

  result = File.open(filename,mode,**opt) do |file|
    parse(file,filename: filename,**kargs)
  end

  return result || fallback
end

.parse_stream(yaml, filename: nil, stylers: nil, deref_aliases: false, **options, &block) ⇒ Psych::Nodes::Stream

Parse yaml into a Psych::Nodes::Stream for one document or for multiple documents in one YAML.

If you’re just going to call to_ruby(), then using this method is unnecessary, and the styler(s) will do nothing for you.

Examples:

burgers = <<EOY
---
Burgers:
  Classic:
    BBQ: {Sauce: Honey BBQ, Cheese: Cheddar, Bun: Kaiser}
---
Toppings:
- [Mushrooms, Mustard]
- [Salt, Pepper, Pickles]
---
`Invalid`
EOY

i = 0

begin
  Psychgus.parse_stream(burgers,filename: 'burgers.yaml') do |document|
    puts "Document ##{i += 1}"
    puts document.to_ruby
  end
rescue Psych::SyntaxError => err
  puts "File: #{err.file}"
end

# Output:
#   Document #1
#   {"Burgers"=>{"Classic"=>{"BBQ"=>{"Sauce"=>"Honey BBQ", "Cheese"=>"Cheddar", "Bun"=>"Kaiser"}}}}
#   Document #2
#   {"Toppings"=>[["Mushrooms", "Mustard"], ["Salt", "Pepper", "Pickles"]]}
#   File: burgers.yaml

Parameters:

  • yaml (String)

    the YAML to parse

  • filename (String) (defaults to: nil)

    the filename to pass as file to the Error potentially raised

  • stylers (nil, Styler, Array<Styler>) (defaults to: nil)

    the Styler(s) to use when parsing the YAML

  • deref_aliases (true, false) (defaults to: false)

    whether to dereference aliases; output the actual value instead of the alias

  • block (Proc)

    an optional block for parsing multiple documents

Returns:

  • (Psych::Nodes::Stream)

    the parsed Stream node

See Also:



682
683
684
685
686
687
688
689
690
691
692
693
694
# File 'lib/psychgus.rb', line 682

def self.parse_stream(yaml,filename: nil,stylers: nil,deref_aliases: false,**options,&block)
  if block_given?
    parser = Psych::Parser.new(StyledDocumentStream.new(*stylers,deref_aliases: deref_aliases,**options,
      &block))

    return parser.parse(yaml,filename)
  else
    parser = self.parser(stylers: stylers,deref_aliases: deref_aliases,**options)
    parser.parse(yaml,filename)

    return parser.handler.root
  end
end

.parser(stylers: nil, deref_aliases: false, **options) ⇒ Psych::Parser

Create a new styled Psych::Parser for parsing YAML.

Examples:

class CoffeeStyler
  include Psychgus::Styler

  def style_sequence(sniffer,node)
    node.style = Psychgus::SEQUENCE_FLOW
  end
end

coffee = <<EOY
Coffee:
  Roast:
    - Light
    - Medium
    - Dark
  Style:
    - Cappuccino
    - Latte
    - Mocha
EOY

parser = Psychgus.parser(stylers: CoffeeStyler.new)
parser.parse(coffee)
puts parser.handler.root.to_yaml

# Output:
#   Coffee:
#     Roast: [Light, Medium, Dark]
#     Style: [Cappuccino, Latte, Mocha]

Parameters:

  • stylers (nil, Styler, Array<Styler>) (defaults to: nil)

    the Styler(s) to use when parsing the YAML

  • deref_aliases (true, false) (defaults to: false)

    whether to dereference aliases; output the actual value instead of the alias

Returns:

  • (Psych::Parser)

    the new styled Parser

See Also:



736
737
738
# File 'lib/psychgus.rb', line 736

def self.parser(stylers: nil,deref_aliases: false,**options)
  return Psych::Parser.new(StyledTreeBuilder.new(*stylers,deref_aliases: deref_aliases,**options))
end