Class: Pine::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/sansom/pine/node.rb

Constant Summary collapse

LineageError =
Class.new StandardError
WILDCARD_REGEX =
/<(\w*)\b[^>]*>/.freeze
URLPATHSAFE_REGEX =
/[^a-zA-Z0-9_-]/.freeze
ROOT =
"/".freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name = ROOT, wc_delim = ":", swc_delim = "<") ⇒ Node

Pine::Node.new “asdf”, “$”, “?” # creates a node with $ as the wildcard delimiter and ? as the semiwildcard delimiter Pine::Node.new “asdf”, “#” # creates a node with # as the wildcard delimiter and the default semiwildcard delimiter Pine::Node.new “asdf” # creates a node with the default delimiters Pine::Node.new # creates root node Delimiters can be any length



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/sansom/pine/node.rb', line 26

def initialize name=ROOT, wc_delim=":", swc_delim="<"
  raise ArgumentError, "Delimiters must not be safe characters in a URL path." if wc_delim.match URLPATHSAFE_REGEX rescue false
  raise ArgumentError, "Delimiters must not be safe characters in a URL path." if swc_delim.match URLPATHSAFE_REGEX rescue false
  @name = name.freeze
  @children = {}
  @wildcard_children = {}
  @blocks = {}
  @subsansoms = []
  @wildcard_delimeter = wc_delim
  @semiwildcard_delimeter = swc_delim
  
  unless root?
    if @name.start_with? wildcard_delimeter
      @wildcard_range = Range.new(0, -1).freeze
      @wildcard = @name[wildcard_delimeter.length..-1].freeze
      @start_seq = "".freeze
      @end_seq = "".freeze
    else
      r = ['<','>'].include?(semiwildcard_delimeter) ? WILDCARD_REGEX : /#{swc_delim}(\w*)\b[^#{swc_delim}]*#{swc_delim}/
      m = @name.match r
      unless m.nil?
        o = m.offset 1
        @wildcard_range = Range.new(o.first-1, (-1*(m.string.length-o.last+1))+1).freeze # calc `last` rel to the last char idx
        @wildcard = @name[wildcard_range.first+semiwildcard_delimeter.length..wildcard_range.last-semiwildcard_delimeter.length].freeze
        @start_seq = @name[0..wildcard_range.first-1].freeze
        @end_seq = wildcard_range.last == -1 ? "" : @name[wildcard_range.last+1..-1].freeze
      end
    end
  end
  
  @min_length = dynamic? ? start_seq.length + end_seq.length : name.length
end

Instance Attribute Details

#blocksObject (readonly)

mapping



17
18
19
# File 'lib/sansom/pine/node.rb', line 17

def blocks
  @blocks
end

#end_seqObject (readonly)

stored information used to match wildcards



18
19
20
# File 'lib/sansom/pine/node.rb', line 18

def end_seq
  @end_seq
end

#min_lengthObject (readonly)

stored information used to match wildcards



18
19
20
# File 'lib/sansom/pine/node.rb', line 18

def min_length
  @min_length
end

#nameObject (readonly)

node “payload” data



14
15
16
# File 'lib/sansom/pine/node.rb', line 14

def name
  @name
end

#parentObject

node reference system



15
16
17
# File 'lib/sansom/pine/node.rb', line 15

def parent
  @parent
end

#rack_appObject (readonly)

mapping



17
18
19
# File 'lib/sansom/pine/node.rb', line 17

def rack_app
  @rack_app
end

#semiwildcard_delimeterObject (readonly)

delimiter for wildcard syntax



19
20
21
# File 'lib/sansom/pine/node.rb', line 19

def semiwildcard_delimeter
  @semiwildcard_delimeter
end

#start_seqObject (readonly)

stored information used to match wildcards



18
19
20
# File 'lib/sansom/pine/node.rb', line 18

def start_seq
  @start_seq
end

#subsansomsObject (readonly)

mapping



17
18
19
# File 'lib/sansom/pine/node.rb', line 17

def subsansoms
  @subsansoms
end

#wildcardObject (readonly)

wildcard data



16
17
18
# File 'lib/sansom/pine/node.rb', line 16

def wildcard
  @wildcard
end

#wildcard_delimeterObject (readonly)

delimiter for wildcard syntax



19
20
21
# File 'lib/sansom/pine/node.rb', line 19

def wildcard_delimeter
  @wildcard_delimeter
end

#wildcard_rangeObject (readonly)

wildcard data



16
17
18
# File 'lib/sansom/pine/node.rb', line 16

def wildcard_range
  @wildcard_range
end

Instance Method Details

#<=>(another) ⇒ Object

TODO: check correctness of return values

Raises:

  • (LinneageError)


69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/sansom/pine/node.rb', line 69

def <=> another
  return 0 if n == another
  
  n = self
  n = n.parent until n == another || n.root?
  return 1 if n == another
  
  n = another
  n = n.parent until n == self || n.root?
  return -1 if n == self
  
  raise LinneageError, "Node not in tree."
end

#==(another) ⇒ Object



63
64
65
66
# File 'lib/sansom/pine/node.rb', line 63

def == another
  parent == another.parent &&
  name == another.name
end

#_hchildrenObject



164
# File 'lib/sansom/pine/node.rb', line 164

def _hchildren; @children; end

#_hwcchildrenObject



165
# File 'lib/sansom/pine/node.rb', line 165

def _hwcchildren; @wildcard_children; end

#_set_parent(p) ⇒ Object

returns new parent so its chainable



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/sansom/pine/node.rb', line 168

def _set_parent p
  return if @parent == p
  
  # remove from old parent's children structure
  unless @parent.nil?
    @parent._hchildren.delete name unless dynamic?
    @parent._hwcchildren.delete name if dynamic?
  end
  
  # add to new parent's children structure
  if wildcard?
    p._hchildren.reject! { |_,c| c.leaf? }
    p._hwcchildren.reject! { |_,c| c.leaf? }
  end
  p._hwcchildren[name] = self
  
  @parent = p # set new parent
end

#add_child!(comp) ⇒ Object Also known as: <<

chainable

Raises:

  • (ArgumentError)


155
156
157
158
159
160
# File 'lib/sansom/pine/node.rb', line 155

def add_child! comp
  raise ArgumentError, "Invalid path component." if comp.nil? || comp.empty?
  c = self[comp] || self.class.new(comp)
  c._set_parent self
  c
end

#ancestor?(another) ⇒ Boolean

Returns:

  • (Boolean)


103
104
105
106
107
# File 'lib/sansom/pine/node.rb', line 103

def ancestor? another
  n = self
  n = n.parent until n == another || n.root?
  n == another
end

#ancestorsObject



109
110
111
112
113
# File 'lib/sansom/pine/node.rb', line 109

def ancestors
  n = self
  n = n.parent until n.root?
  n
end

#child(comp) ⇒ Object Also known as: []

WARNING: Sansom’s biggest bottleneck Partially chainable: No guarantee the returned value responds to :child or :[]

Raises:

  • (ArgumentError)


144
145
146
147
148
149
150
# File 'lib/sansom/pine/node.rb', line 144

def child comp
  raise ArgumentError, "Invalid path component." if comp.nil? || comp.empty?
  case
  when @children.empty? && @wildcard_children.empty? then nil
  when @children.member?(comp) then @children[comp]
  else @wildcard_children.values.detect { |c| c.matches? comp } end
end

#child?(another) ⇒ Boolean

Returns:

  • (Boolean)


99
100
101
# File 'lib/sansom/pine/node.rb', line 99

def child? another
  another.ancestor? self
end

#childrenObject



91
92
93
# File 'lib/sansom/pine/node.rb', line 91

def children
  hash_children.values
end

#detach!Object



83
84
85
# File 'lib/sansom/pine/node.rb', line 83

def detach!
  _set_parent nil
end

#dynamic?Boolean

returns true if self is either a wildcard or a semiwildcard

Returns:

  • (Boolean)


132
133
134
# File 'lib/sansom/pine/node.rb', line 132

def dynamic?
  !wildcard_range.nil?
end

#hash_childrenObject



95
96
97
# File 'lib/sansom/pine/node.rb', line 95

def hash_children
  Hash[@children.to_a + @wildcard_children.to_a]
end

#inspectObject



59
60
61
# File 'lib/sansom/pine/node.rb', line 59

def inspect
  "#<#{self.class}: #{name.inspect}, #{dynamic? ? "Wildcard: '" + wildcard + "' #{wildcard_range.inspect}, " : "" }#{@children.count} children, #{leaf? ? "leaf" : "internal node"}>"
end

#leaf?Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/sansom/pine/node.rb', line 119

def leaf?
  children.empty? && subsansoms.empty? && rack_app.nil?
end

#matches?(comp) ⇒ Boolean

Bottleneck for wildcard-heavy apps

Returns:

  • (Boolean)


137
138
139
140
# File 'lib/sansom/pine/node.rb', line 137

def matches? comp
  return comp == name unless dynamic?
  comp.length >= min_length && comp.start_with?(start_seq) && comp.end_with?(end_seq)
end

#root?Boolean

Returns:

  • (Boolean)


115
116
117
# File 'lib/sansom/pine/node.rb', line 115

def root?
  name == ROOT
end

#semiwildcard?Boolean

Returns:

  • (Boolean)


123
124
125
# File 'lib/sansom/pine/node.rb', line 123

def semiwildcard?
  !wildcard_range.nil? && wildcard_range.size != 0
end

#siblingsObject



87
88
89
# File 'lib/sansom/pine/node.rb', line 87

def siblings
  parent.children.dup - self
end

#wildcard?Boolean

Returns:

  • (Boolean)


127
128
129
# File 'lib/sansom/pine/node.rb', line 127

def wildcard?
  !wildcard_range.nil? && wildcard_range.size == 0
end