Class: Sycamore::Path

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Enumerable
Defined in:
lib/sycamore/path.rb,
lib/sycamore/path_root.rb

Overview

TODO:

Measure the performance and memory consumption in comparison with a pure Array-based implementation (where tree nodes are duplicated), esp. in the most common use case of property-value structures.

A compact, immutable representation of Tree paths, i.e. node sequences.

This class is optimized for its usage in Tree#each_path, where it can efficiently represent the whole tree as a set of paths by sharing the parent paths. It is not intended to be instantiated by the user.

Examples:

tree = Tree[foo: [:bar, :baz]]
path1, path2 = tree.paths.to_a
path1 == Sycamore::Path[:foo, :bar] # => true
path2 == Sycamore::Path[:foo, :baz] # => true
path1.parent.equal? path2.parent # => true

Direct Known Subclasses

Root

Defined Under Namespace

Classes: Root

Constant Summary collapse

ROOT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Root.instance

Instance Attribute Summary collapse

Construction collapse

Elements collapse

Equality collapse

Conversion collapse

Instance Attribute Details

#nodeObject (readonly)

Returns the value of attribute node.



28
29
30
# File 'lib/sycamore/path.rb', line 28

def node
  @node
end

#parentObject (readonly)

Returns the value of attribute parent.



28
29
30
# File 'lib/sycamore/path.rb', line 28

def parent
  @parent
end

Class Method Details

.of(path, nodes) ⇒ Path .of(nodes) ⇒ Path Also known as: []

Creates a new path.

Depending on whether the first argument is a Sycamore::Path, the new Path is #branched from this path or the root.

Overloads:

  • .of(path, nodes) ⇒ Path

    Returns the #branched path from the given path, with the given nodes expanded.

    Parameters:

    • path (Path)

      the path from which should be #branched

    • nodes (nodes)

    Returns:

    • (Path)

      the #branched path from the given path, with the given nodes expanded

  • .of(nodes) ⇒ Path

    Returns the #branched path from the root, with the given nodes.

    Parameters:

    • nodes (nodes)

    Returns:



63
64
65
66
67
68
69
# File 'lib/sycamore/path.rb', line 63

def self.of(*args)
  if (parent = args.first).is_a? Path
    parent.branch(*args[1..-1])
  else
    root.branch(*args)
  end
end

.rootObject

Returns the root of all Paths.

Returns:

  • the root of all Paths



44
45
46
# File 'lib/sycamore/path.rb', line 44

def self.root
  ROOT
end

Instance Method Details

#==(other) ⇒ Boolean

Returns if the other is an Enumerable with the same nodes in the same order.

Parameters:

  • other (Object)

Returns:

  • (Boolean)

    if the other is an Enumerable with the same nodes in the same order



226
227
228
229
230
# File 'lib/sycamore/path.rb', line 226

def ==(other)
  other.is_a?(Enumerable) and self.length == other.length and begin
    i = other.each ; all? { |node| node == i.next }
  end
end

#branch(*nodes) ⇒ Path Also known as: +, /

Returns a new path based on this path, but with the given nodes extended.

Examples:

path = Sycamore::Path[:foo, :bar]
path.branch(:baz, :qux) ==
  Sycamore::Path[:foo, :bar, :baz, :qux]  # => true
path / :baz / :qux ==
  Sycamore::Path[:foo, :bar, :baz, :qux]  # => true

Parameters:

  • nodes (nodes)

    an arbitrary number of nodes

Returns:

Raises:

  • (InvalidNode)

    if one or more of the given nodes is an Enumerable



98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/sycamore/path.rb', line 98

def branch(*nodes)
  return branch(*nodes.first) if nodes.size == 1 and nodes.first.is_a? Enumerable

  parent = self
  nodes.each do |node|
    raise InvalidNode, "#{node} in Path #{nodes.inspect} is not a valid tree node" if
      node.is_a? Enumerable
    parent = Path.__send__(:new, parent, node)
  end

  parent
end

#each_node {|node| ... } ⇒ Object #each_nodeEnumerator<node> Also known as: each

Iterates over all nodes on this path.

Overloads:

  • #each_node {|node| ... } ⇒ Object

    Yields:

    • (node)

      each node

  • #each_nodeEnumerator<node>

    Returns:

    • (Enumerator<node>)


162
163
164
165
166
167
168
169
# File 'lib/sycamore/path.rb', line 162

def each_node(&block)
  return enum_for(__callee__) unless block_given?

  if @parent
    @parent.each_node(&block)
    yield @node
  end
end

#eql?(other) ⇒ Boolean

Returns if the other is a Path with the same nodes in the same order.

Parameters:

  • other (Object)

Returns:

  • (Boolean)

    if the other is a Path with the same nodes in the same order



215
216
217
218
219
220
# File 'lib/sycamore/path.rb', line 215

def eql?(other)
  other.is_a?(self.class) and
    self.length == other.length and begin
      i = other.each ; all? { |node| node.eql? i.next }
    end
end

#hashFixnum

Returns hash code for this path.

Returns:

  • (Fixnum)

    hash code for this path



207
208
209
# File 'lib/sycamore/path.rb', line 207

def hash
  to_a.hash ^ self.class.hash
end

#inspectString

Returns a more verbose string representation of this path.

Returns:

  • (String)

    a more verbose string representation of this path



261
262
263
# File 'lib/sycamore/path.rb', line 261

def inspect
  "#<Sycamore::Path[#{each_node.map(&:inspect).join(',')}]>"
end

#join(separator = '/') ⇒ String

Note:

Since the root path with no node is at the beginning of each path, the returned string always begins with the given separator.

Returns a string created by converting each node on this path to a string, separated by the given separator.

Examples:

Sycamore::Path[1,2,3].join       # => '/1/2/3'
Sycamore::Path[1,2,3].join('|')  # => '|1|2|3'

Parameters:

  • separator (String) (defaults to: '/')

Returns:

  • (String)

    a string created by converting each node on this path to a string, separated by the given separator



247
248
249
# File 'lib/sycamore/path.rb', line 247

def join(separator = '/')
  @parent.join(separator) + separator + node.to_s
end

#lengthInteger Also known as: size

Returns the number of nodes on this path.

Returns:

  • (Integer)

    the number of nodes on this path



145
146
147
148
149
# File 'lib/sycamore/path.rb', line 145

def length
  i, parent = 1, self
  i += 1 until (parent = parent.parent).root?
  i
end

#present_in?(struct) ⇒ Boolean Also known as: in?

If a given structure contains this path.

Examples:

hash = {foo: {bar: :baz}}
Sycamore::Path[:foo, :bar].present_in? hash  # => true
Sycamore::Path[:foo, :bar].present_in? Tree[hash]  # => true

Parameters:

  • struct (Object)

Returns:

  • (Boolean)

    if the given structure contains the nodes on this path



184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/sycamore/path.rb', line 184

def present_in?(struct)
  each do |node|
    case
      when struct.is_a?(Enumerable)
        return false unless struct.include? node
        struct = (Tree.like?(struct) ? struct[node] : Nothing )
      else
        return false unless struct.eql? node
        struct = Nothing
    end
  end
  true
end

#root?Boolean

Returns if this is the root path.

Returns:

  • (Boolean)

    if this is the root path



138
139
140
# File 'lib/sycamore/path.rb', line 138

def root?
  false
end

#to_sString

Returns a compact string representation of this path.

Returns:

  • (String)

    a compact string representation of this path



254
255
256
# File 'lib/sycamore/path.rb', line 254

def to_s
  "#<Path: #{join}>"
end

#up(distance = 1) ⇒ Path

Returns the n-th last parent path.

Examples:

path = Sycamore::Path[:foo, :bar, :baz]
path.up     # => Sycamore::Path[:foo, :bar]
path.up(2)  # => Sycamore::Path[:foo]
path.up(3)  # => Sycamore::Path[]

Parameters:

  • distance (Integer) (defaults to: 1)

    the number of nodes to go up

Returns:

  • (Path)

    the n-th last parent path

Raises:

  • (TypeError)


124
125
126
127
128
129
130
131
132
133
# File 'lib/sycamore/path.rb', line 124

def up(distance = 1)
  raise TypeError, "expected an integer, but got #{distance.inspect}" unless
    distance.is_a? Integer

  case distance
    when 1 then @parent
    when 0 then self
    else parent.up(distance - 1)
  end
end