class Psychgus::SuperSniffer

This is used in {StyledTreeBuilder} to “sniff” information about the YAML.

Then this information can be used in a {Styler} and/or a {Blueberry}.

Most information is straightforward:

{#parent} is the current {SuperSniffer::Parent} of the node being processed, which is an empty Parent for the first node (not nil).

{#parents} are all of the (grand){SuperSniffer::Parent}(s) for the current node, which is an Array that just contains an empty Parent for the first node.

A parent is a Mapping or Sequence, or a Key (Scalar) in a Mapping.

{#level} and {#position} can be best understood by an example.

If you have this YAML:

Burgers:
   Classic:
     Sauce:  [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
   - [Lettuce, Onions, Pickles, Tomatoes]
   - [[Ketchup,Mustard], [Salt,Pepper]]

Then the levels and positions will be as follows:

# (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)>

“The Super Sniffer” is the nickname for Gus's nose from the TV show Psych because he has a very refined sense of smell.

@note You should never call the methods that are not readers, like {#add_alias}, {#start_mapping}, etc.

unless you are extending this class (creating a subclass).

@author Jonathan Bradley Whited @since 1.0.0

@see StyledTreeBuilder @see Styler @see Blueberry#psychgus_stylers

Constants

EMPTY

Attributes

aliases[R]
documents[R]
level[R]
mappings[R]
nodes[R]
parent[R]
parents[R]
position[R]
scalars[R]
sequences[R]
streams[R]

Public Class Methods

new() click to toggle source

Initialize this class for sniffing.

# File lib/psychgus/super_sniffer.rb, line 158
def initialize
  @aliases = []
  @documents = []
  @level = 0
  @mappings = []
  @nodes = []
  @parent = nil
  @parents = []
  @position = 0
  @scalars = []
  @sequences = []
  @streams = []

  # Do not pass in "top_level: true"
  start_parent(nil,debug_tag: :root)
end

Public Instance Methods

add_alias(node) click to toggle source

Add a Psych::Nodes::Alias to this class only (not to the YAML).

A {Styler} should probably never call this.

@param node [Psych::Nodes::Alias] the alias to add

@see add_child

# File lib/psychgus/super_sniffer.rb, line 182
def add_alias(node)
  add_child(node)
  @aliases.push(node)
end
add_scalar(node) click to toggle source

Add a Psych::Nodes::Scalar to this class only (not to the YAML).

A {Styler} should probably never call this.

@param node [Psych::Nodes::Scalar] the scalar to add

@see add_child

# File lib/psychgus/super_sniffer.rb, line 194
def add_scalar(node)
  add_child(node)
  @scalars.push(node)
end
end_document() click to toggle source

End a Psych::Nodes::Document started with {#start_document}.

Pops off a parent from {#parents} and sets {#parent} to the last one. {#level} and {#position} are reset according to the last parent.

A {Styler} should probably never call this.

# File lib/psychgus/super_sniffer.rb, line 205
def end_document
  end_parent(top_level: true)
end
end_mapping() click to toggle source

End a Psych::Nodes::Mapping started with {#start_mapping}.

Pops off a parent from {#parents} and sets {#parent} to the last one. {#level} and {#position} are reset according to the last parent.

A {Styler} should probably never call this.

@see end_parent

# File lib/psychgus/super_sniffer.rb, line 217
def end_mapping
  end_parent(mapping_value: true)
end
end_sequence() click to toggle source

End a Psych::Nodes::Sequence started with {#start_sequence}.

Pops off a parent from {#parents} and sets {#parent} to the last one. {#level} and {#position} are reset according to the last parent.

A {Styler} should probably never call this.

@see end_parent

# File lib/psychgus/super_sniffer.rb, line 229
def end_sequence
  end_parent(mapping_value: true)
end
end_stream() click to toggle source

End a Psych::Nodes::Stream started with {#start_stream}.

Pops off a parent from {#parents} and sets {#parent} to the last one. {#level} and {#position} are reset according to the last parent.

A {Styler} should probably never call this.

# File lib/psychgus/super_sniffer.rb, line 239
def end_stream
  end_parent(top_level: true)
end
start_document(node) click to toggle source

Start a Psych::Nodes::Document.

Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}. {#level} and {#position} are incremented/set accordingly.

A {Styler} should probably never call this.

@param node [Psych::Nodes::Document] the Document to start

@see start_parent

# File lib/psychgus/super_sniffer.rb, line 253
def start_document(node)
  start_parent(node,debug_tag: :doc,top_level: true)
  @documents.push(node)
end
start_mapping(node) click to toggle source

Start a Psych::Nodes::Mapping.

Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}. {#level} and {#position} are incremented/set accordingly.

A {Styler} should probably never call this.

@param node [Psych::Nodes::Mapping] the Mapping to start

@see start_parent

# File lib/psychgus/super_sniffer.rb, line 268
def start_mapping(node)
  start_parent(node,debug_tag: :map,child_type: :key)
  @mappings.push(node)
end
start_sequence(node) click to toggle source

Start a Psych::Nodes::Sequence.

Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}. {#level} and {#position} are incremented/set accordingly.

A {Styler} should probably never call this.

@param node [Psych::Nodes::Sequence] the Sequence to start

@see start_parent

# File lib/psychgus/super_sniffer.rb, line 283
def start_sequence(node)
  start_parent(node,debug_tag: :seq)
  @sequences.push(node)
end
start_stream(node) click to toggle source

Start a Psych::Nodes::Stream.

Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}. {#level} and {#position} are incremented/set accordingly.

A {Styler} should probably never call this.

@param node [Psych::Nodes::Stream] the Stream to start

@see start_parent

# File lib/psychgus/super_sniffer.rb, line 298
def start_stream(node)
  start_parent(node,debug_tag: :stream,top_level: true)
  @streams.push(node)
end

Protected Instance Methods

add_child(node) click to toggle source

Add a non-parent node.

This will increment {#position} accordingly, and if the child is a Key to a Mapping, create a fake “{SuperSniffer::Parent}”.

@param node [Psych::Nodes::Node] the non-parent Node to add

@see end_mapping_value

# File lib/psychgus/super_sniffer.rb, line 313
def add_child(node)
  if !@parent.nil?
    # Fake a "parent" if necessary
    case @parent.child_type
    when :key
      start_mapping_key(node)
      return
    when :value
      end_mapping_value
      return
    else
      @parent.child_position += 1
    end
  end

  @position += 1

  @nodes.push(node)
end
end_mapping_value() click to toggle source

End a fake “{SuperSniffer::Parent}” that is a Key/Value to a Mapping.

@see add_child

# File lib/psychgus/super_sniffer.rb, line 336
def end_mapping_value
  end_parent # Do not pass in "mapping_value: true" and/or "top_level: true"

  @parent.child_type = :key unless @parent.nil?
end
end_parent(mapping_value: false,top_level: false) click to toggle source

End a {SuperSniffer::Parent}.

Pops off a parent from {#parents} and sets {#parent} to the last one. {#level} and {#position} are reset according to the last parent.

@param mapping_value [true,false] true if parent can be the value of a Mapping's key @param top_level [true,false] true if a top-level parent (i.e., encapsulating the main data)

# File lib/psychgus/super_sniffer.rb, line 349
def end_parent(mapping_value: false,top_level: false)
  @parents.pop
  @parent = @parents.last

  @level = top_level ? 1 : (@level - 1)

  if !@parent.nil?
    @parent.child_position += 1
    @position = @parent.child_position

    # add_child() will not be called again, so end a fake "parent" manually with a fake "value"
    # - This is necessary for any parents that can be the value of a map's key (e.g., Sequence)
    end_mapping_value if mapping_value && !@parent.child_type.nil?
  end
end
start_mapping_key(node) click to toggle source

Start a fake “{SuperSniffer::Parent}” that is a Key/Value to a Mapping.

Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}. {#level} and {#position} are incremented/set accordingly.

@param node [Psych::Nodes::Node] the Node to start

@see start_parent

# File lib/psychgus/super_sniffer.rb, line 373
def start_mapping_key(node)
  debug_tag = nil

  # Value must be first because Scalar also has an anchor
  if node.respond_to?(:value)
    debug_tag = node.value
  elsif node.respond_to?(:anchor)
    debug_tag = node.anchor
  end

  debug_tag = :noface if debug_tag.nil?

  start_parent(node,debug_tag: debug_tag,child_type: :value)
end
start_parent(node,top_level: false,**extra) click to toggle source

Start a {SuperSniffer::Parent}.

Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}. {#level} and {#position} are incremented/set accordingly.

@param node [Psych::Nodes::Node] the parent Node to start @param top_level [true,false] true if a top-level parent (i.e., encapsulating the main data) @param extra [Hash] the extra keyword args to pass to {SuperSniffer::Parent#initialize}

@see SuperSniffer::Parent#initialize

# File lib/psychgus/super_sniffer.rb, line 398
def start_parent(node,top_level: false,**extra)
  @parent = Parent.new(self,node,**extra)

  @parents.push(@parent)
  @nodes.push(node) unless node.nil?

  if top_level
    @level = 1
    @position = @parent.position
  else
    @level += 1
    @position = 1
  end
end