This repository has been archived by the owner on Jan 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
node.rb
155 lines (134 loc) · 4.66 KB
/
node.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# encoding: UTF-8
require "forwardable"
module Clerq
module Entities
# The basic block of hierarchy and the single Clerq entity
#
# Usage
# n = Node.new(title: 'Part I')
# n << Node.new(title: 'Item 1')
# n << Node.new(title: 'Item 2')
# n.each{|i| puts i.tile} # and all power of Enumerable
class Clerq::Entities::Node
extend Forwardable
include Enumerable
# @!attribute [r] parent
# @return [Node] the parent of the node
attr_reader :parent
# @!attribute [r] title
# @return [String] the title of the node
attr_reader :title
# @!attribute [r] body
# @return [String] the body of the node
attr_reader :body
# @!attribute [r] meta
# @return [Hash<Symbol, String>] the metadata of the node
attr_reader :meta
# @!attribute [w] id
# @return [Hash<Symbol, String>] the metadata of the node
attr_writer :id
def_delegators :@meta, :[], :[]=
def_delegator :@items, :delete, :delete_item
def_delegator :@items, :last, :last_item
protected :delete_item
def initialize(id: '', title: '', body: '', meta: {})
raise ArgumentError, "Invalid argument :id" unless id.is_a? String
raise ArgumentError, "Invalid argument :title" unless title.is_a? String
raise ArgumentError, "Invalid argument :body" unless body.is_a? String
raise ArgumentError, "Invalid argument :meta" unless meta.is_a? Hash
id = meta.delete(:id) if id.empty? && meta[:id]
meta.delete(:id) unless id.empty?
@parent = nil
@items = []
@id = id
@title = title
@body = body
@meta = meta
end
def <<(node)
raise ArgumentError, "Invalid argument :node" unless node.is_a? Node
node.parent = self
@items << node
node
end
# @param id [String] the id of child node; when it starts with '.',
# the method will find nodes that id ends with the param
# @return [Node, nil] child node by provided id; when id not found
# it will return ni
def item(id)
return @items.find{|r| r.id.end_with? id[1..-1]} if id.start_with? '.'
@items.find{|r| r.id.eql? id}
end
# @return [Array<String>] of ids from meta[:order_index]
def order_index
return [] unless @meta[:order_index]
@meta[:order_index].strip.gsub(/[\s]{2,}/, ' ').split(/\s/)
end
# @return [Array<Node>] list of child nodes; when the node
# metadate has :order_index arrtibute, the list will be
# ordered according the attribute value
def items
return @items if @items.empty? || order_index.empty?
[].tap do |ordered|
source = Array.new(@items)
order_index.each do |o|
e = source.delete(item(o))
ordered << e if e
end
ordered.concat(source)
end
end
# see Enumerable#each
def each(&block)
return to_enum(__callee__) unless block_given?
yield(self)
items.each{|n| n.each(&block) }
end
# @return [Node] the root node in the node hierarchy
def root
n = self
n = n.parent while n.parent
n
end
# @return [Integer] the node level in the node hierarchy
def nesting_level
@parent.nil? ? 0 : @parent.nesting_level + 1
end
# @return [Array<String>] macro links in the node #body
def links
return [] if @body.empty?
@body.scan(/\[\[([\w\.]*)\]\]/).flatten.uniq
end
# When the node id starts with '.', the method will
# prefix the node id with parent id
# @return [String] the id of the node
def id
@id.start_with?('.') && @parent ? @parent.id + @id : @id
end
# Find the first node in the node hierarchy by its id.
# Add '*' prefix to find id by ends_with?
# @param [String] id Node id
# @return [Node] or nil when node not found
def node(id)
if id.start_with? '*'
ai = id[1..-1]
return find {|n| n.id.end_with? ai}
end
find{|n| n.id.eql? id}
end
# Break of the node from parent hierarhy
# @return self
def orphan!
return unless @parent
@parent.delete_item(self)
@parent = nil
self
end
protected
def parent=(node)
raise ArgumentError, "Invalid parameter :node" unless node.is_a? Node
@parent = node
end
end
end
end