giblish 0.8.2 → 2.0.0.pre.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/unit_tests.yml +30 -0
- data/.gitignore +7 -3
- data/.ruby-version +1 -1
- data/Changelog.adoc +59 -0
- data/README.adoc +261 -0
- data/docs/concepts/text_search.adoc +213 -0
- data/docs/concepts/text_search_im/cgi-search_request.puml +35 -0
- data/docs/concepts/text_search_im/cgi-search_request.svg +397 -0
- data/docs/concepts/text_search_im/search_request.puml +40 -0
- data/docs/concepts/text_search_im/search_request.svg +408 -0
- data/docs/howtos/trigger_generation.adoc +180 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/Render Documents.png +0 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/View Documents.png +0 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/deploy_with_hooks.graphml +0 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/deploy_with_hooks.svg +0 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/deploy_with_jenkins.graphml +0 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/deploy_with_jenkins.svg +0 -0
- data/docs/howtos/trigger_generation_im/docgen_github.puml +51 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/giblish_deployment.graphml +0 -0
- data/docs/howtos/trigger_generation_im/post-receive-example.sh +50 -0
- data/docs/reference/box_flow_spec.adoc +22 -0
- data/docs/reference/search_spec.adoc +185 -0
- data/giblish.gemspec +54 -32
- data/lib/giblish/adocsrc_providers.rb +23 -0
- data/lib/giblish/application.rb +214 -41
- data/lib/giblish/cmdline.rb +273 -259
- data/lib/giblish/config_utils.rb +41 -0
- data/lib/giblish/configurator.rb +163 -0
- data/lib/giblish/conversion_info.rb +120 -0
- data/lib/giblish/docattr_providers.rb +125 -0
- data/lib/giblish/docid/docid.rb +181 -0
- data/lib/giblish/github_trigger/webhook_manager.rb +64 -0
- data/lib/giblish/gitrepos/checkoutmanager.rb +124 -0
- data/lib/giblish/{gititf.rb → gitrepos/gititf.rb} +30 -4
- data/lib/giblish/gitrepos/gitsummary.erb +61 -0
- data/lib/giblish/gitrepos/gitsummaryprovider.rb +78 -0
- data/lib/giblish/gitrepos/history_pb.rb +41 -0
- data/lib/giblish/indexbuilders/d3treegraph.rb +88 -0
- data/lib/giblish/indexbuilders/depgraphbuilder.rb +109 -0
- data/lib/giblish/indexbuilders/dotdigraphadoc.rb +174 -0
- data/lib/giblish/indexbuilders/standard_index.erb +10 -0
- data/lib/giblish/indexbuilders/subtree_indices.rb +132 -0
- data/lib/giblish/indexbuilders/templates/circles.html.erb +111 -0
- data/lib/giblish/indexbuilders/templates/flame.html.erb +61 -0
- data/lib/giblish/indexbuilders/templates/tree.html.erb +366 -0
- data/lib/giblish/indexbuilders/templates/treemap.html.erb +127 -0
- data/lib/giblish/indexbuilders/verbatimtree.rb +94 -0
- data/lib/giblish/pathtree.rb +473 -74
- data/lib/giblish/resourcepaths.rb +150 -0
- data/lib/giblish/search/expand_adoc.rb +55 -0
- data/lib/giblish/search/headingindexer.rb +312 -0
- data/lib/giblish/search/request_manager.rb +110 -0
- data/lib/giblish/search/searchquery.rb +68 -0
- data/lib/giblish/search/textsearcher.rb +349 -0
- data/lib/giblish/subtreeinfobuilder.rb +77 -0
- data/lib/giblish/treeconverter.rb +272 -0
- data/lib/giblish/utils.rb +142 -294
- data/lib/giblish/version.rb +1 -1
- data/lib/giblish.rb +10 -7
- data/scripts/hooks/post-receive.example +66 -0
- data/{docgen/scripts/githook_examples → scripts/hooks}/post-update.example +0 -0
- data/{docgen → scripts}/resources/css/adoc-colony.css +0 -0
- data/scripts/resources/css/giblish-serif.css +419 -0
- data/scripts/resources/css/giblish.css +1979 -419
- data/{docgen → scripts}/resources/fonts/Ubuntu-B.ttf +0 -0
- data/{docgen → scripts}/resources/fonts/Ubuntu-BI.ttf +0 -0
- data/{docgen → scripts}/resources/fonts/Ubuntu-R.ttf +0 -0
- data/{docgen → scripts}/resources/fonts/Ubuntu-RI.ttf +0 -0
- data/{docgen → scripts}/resources/fonts/mplus1p-regular-fallback.ttf +0 -0
- data/{docgen → scripts}/resources/images/giblish_logo.png +0 -0
- data/{docgen → scripts}/resources/images/giblish_logo.svg +0 -0
- data/{docgen → scripts}/resources/themes/giblish.yml +0 -0
- data/scripts/wserv_development.rb +32 -0
- data/web_apps/cgi_search/gibsearch.rb +43 -0
- data/web_apps/gh_webhook_trigger/config.ru +2 -0
- data/web_apps/gh_webhook_trigger/gh_webhook_trigger.rb +73 -0
- data/web_apps/gh_webhook_trigger/public/dummy.txt +3 -0
- data/web_apps/sinatra_search/config.ru +2 -0
- data/web_apps/sinatra_search/public/dummy.txt +3 -0
- data/web_apps/sinatra_search/sinatra_search.rb +34 -0
- data/web_apps/sinatra_search/tmp/restart.txt +0 -0
- metadata +188 -85
- data/.rubocop.yml +0 -7
- data/.travis.yml +0 -3
- data/Changelog +0 -16
- data/Gemfile +0 -4
- data/README.adoc +0 -1
- data/Rakefile +0 -41
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/data/testdocs/malformed/no_header.adoc +0 -5
- data/data/testdocs/toplevel.adoc +0 -19
- data/data/testdocs/wellformed/adorned_purpose.adoc +0 -17
- data/data/testdocs/wellformed/docidtest/docid_1.adoc +0 -24
- data/data/testdocs/wellformed/docidtest/docid_2.adoc +0 -8
- data/data/testdocs/wellformed/simple.adoc +0 -14
- data/data/testdocs/wellformed/source_highlighting/highlight_source.adoc +0 -38
- data/docgen/resources/css/giblish.css +0 -1979
- data/docgen/scripts/Jenkinsfile +0 -18
- data/docgen/scripts/gen_adoc_org.sh +0 -58
- data/docs/README.adoc +0 -387
- data/docs/setup_server.adoc +0 -202
- data/lib/giblish/buildgraph.rb +0 -216
- data/lib/giblish/buildindex.rb +0 -459
- data/lib/giblish/core.rb +0 -451
- data/lib/giblish/docconverter.rb +0 -308
- data/lib/giblish/docid.rb +0 -180
- data/lib/giblish/docinfo.rb +0 -75
- data/lib/giblish/indexheadings.rb +0 -251
- data/lib/giblish-search.cgi +0 -459
- data/scripts/hooks/post-receive +0 -57
- data/scripts/publish_html.sh +0 -99
data/lib/giblish/pathtree.rb
CHANGED
@@ -1,119 +1,518 @@
|
|
1
|
-
|
1
|
+
require "pathname"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
#
|
5
|
+
# Provides a tree structure where each node is the basename of either
|
6
|
+
# a directory or a file. The pathname of a node is the concatenation of
|
7
|
+
# all basenames from the root node to the node in question, given as a
|
8
|
+
# Pathname object.
|
9
|
+
#
|
10
|
+
# Each node must have a unique pathname within the tree it is part of.
|
11
|
+
#
|
12
|
+
# A node can contain an associated 'data' object.
|
13
|
+
#
|
14
|
+
# The following paths:
|
2
15
|
# basedir/file_1
|
3
16
|
# basedir/file_2
|
4
17
|
# basedir/dir1/file_3
|
5
18
|
# basedir/dir1/file_4
|
6
|
-
#
|
19
|
+
# basedir/dir2/dir3/file_5
|
20
|
+
#
|
21
|
+
# are thus represented by the following path tree:
|
7
22
|
#
|
8
|
-
# into the following tree:
|
9
23
|
# basedir
|
10
24
|
# file_1
|
11
25
|
# file_2
|
12
26
|
# dir1
|
13
27
|
# file_3
|
14
28
|
# file_4
|
15
|
-
# basedir2
|
16
29
|
# dir2
|
17
30
|
# dir3
|
18
31
|
# file_5
|
32
|
+
#
|
33
|
+
# == Tree info
|
34
|
+
# see https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/
|
35
|
+
#
|
19
36
|
class PathTree
|
20
|
-
attr_reader :name, :
|
37
|
+
attr_reader :data, :name, :children, :parent, :abs_root
|
38
|
+
attr_writer :parent, :data
|
39
|
+
|
40
|
+
def initialize(path, data = nil, parent = nil)
|
41
|
+
p = clean(path)
|
42
|
+
raise ArgumentError, "Can not instantiate node with path == '.'" if p.to_s == "."
|
43
|
+
raise ArgumentError, "Trying to create a non-root node using an absolute path" if p.absolute? && !parent.nil?
|
44
|
+
|
45
|
+
head = p.descend.first
|
21
46
|
|
22
|
-
|
47
|
+
@name = head
|
23
48
|
@children = []
|
24
|
-
@
|
25
|
-
@
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
@name = tail.shift
|
30
|
-
if tail.length.positive?
|
31
|
-
@children << PathTree.new(tail, data)
|
32
|
-
else
|
49
|
+
@data = nil
|
50
|
+
@parent = parent
|
51
|
+
|
52
|
+
tail = p.relative_path_from(head)
|
53
|
+
if tail.to_s == "."
|
33
54
|
@data = data
|
55
|
+
return
|
34
56
|
end
|
57
|
+
|
58
|
+
add_descendants(tail, data)
|
35
59
|
end
|
36
60
|
|
37
|
-
|
38
|
-
|
39
|
-
|
61
|
+
# duplicate this node and all its children but keep the same data references
|
62
|
+
# as the originial nodes.
|
63
|
+
#
|
64
|
+
# parent:: the parent node of the copy, default = nil (the copy
|
65
|
+
# is a root node)
|
66
|
+
# returns:: a copy of this node and all its descendents. The copy will
|
67
|
+
# share any 'data' references with the original.
|
68
|
+
def dup(parent: nil)
|
69
|
+
d = PathTree.new(@name.dup, @data, parent)
|
40
70
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
71
|
+
@children.each { |c| d.children << c.dup(parent: d) }
|
72
|
+
d
|
73
|
+
end
|
74
|
+
|
75
|
+
def name=(name)
|
76
|
+
name = Pathname.new(name)
|
77
|
+
|
78
|
+
if !parent.nil? && @parent.children.any? { |c| c.name == name }
|
79
|
+
raise ArgumentError, "Can not rename to #{name}. An existing node already use that name"
|
80
|
+
end
|
81
|
+
|
82
|
+
@name = name
|
83
|
+
end
|
84
|
+
|
85
|
+
# return:: a String with the path segment for this node
|
86
|
+
def segment
|
87
|
+
@name.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
# return:: a Pathname with the complete path from the root of the
|
91
|
+
# tree where this node is a member to this node (inclusive).
|
92
|
+
def pathname
|
93
|
+
return @name if @parent.nil?
|
94
|
+
|
95
|
+
(@parent.pathname / @name).cleanpath
|
96
|
+
end
|
97
|
+
|
98
|
+
# create a subtree from the given path and add it to this node
|
99
|
+
#
|
100
|
+
# return:: the leaf node for the added subtree
|
101
|
+
def add_descendants(path, data = nil)
|
102
|
+
p = clean(path)
|
103
|
+
raise ArgumentError, "Can not add absolute path as descendant!!" if p.absolute?
|
104
|
+
|
105
|
+
# invoked with 'current' name, ignore
|
106
|
+
return self if p.to_s == "."
|
107
|
+
|
108
|
+
head = p.descend.first
|
109
|
+
tail = p.relative_path_from(head)
|
110
|
+
last_segment = tail.to_s == "."
|
111
|
+
|
112
|
+
ch = get_child(head)
|
113
|
+
if ch.nil?
|
114
|
+
@children << PathTree.new(head, last_segment ? data : nil, self)
|
115
|
+
ch = @children.last
|
116
|
+
end
|
117
|
+
|
118
|
+
last_segment ? @children.last : ch.add_descendants(tail, data)
|
119
|
+
end
|
120
|
+
|
121
|
+
# adds a new path to the root of the tree where this node is a member
|
122
|
+
# and associates the given data to the leaf of that path.
|
123
|
+
def add_path(path, data = nil)
|
124
|
+
p = clean(path)
|
125
|
+
raise ArgumentError, "Trying to add already existing path: #{path}" unless node(p, from_root: true).nil?
|
126
|
+
|
127
|
+
# prune any part of the given path that already exists in this
|
128
|
+
# tree
|
129
|
+
p.ascend do |q|
|
130
|
+
n = node(q, from_root: true)
|
131
|
+
next if n.nil?
|
132
|
+
|
133
|
+
t = PathTree.new(p.relative_path_from(q).to_s, data)
|
134
|
+
n.append_tree(t)
|
135
|
+
return self
|
136
|
+
end
|
137
|
+
|
138
|
+
# no part of the given path existed within the tree
|
139
|
+
raise ArgumentError, "Trying to add path with other root is not supported"
|
140
|
+
end
|
141
|
+
|
142
|
+
# Visits depth-first by root -> left -> right
|
143
|
+
#
|
144
|
+
# level:: the number of hops from the root node
|
145
|
+
# block:: the user supplied block that is executed for every visited node
|
146
|
+
#
|
147
|
+
# the level and node are given as block parameters
|
148
|
+
#
|
149
|
+
# === Returns
|
150
|
+
# A new array containing the values returned by the block
|
151
|
+
#
|
152
|
+
# === Examples
|
153
|
+
# Get an array with name of each node together with the level of the node
|
154
|
+
# traverse_preorder{ |level, n| "#{level} #{n.segment}" }
|
155
|
+
#
|
156
|
+
def traverse_preorder(level = 0, &block)
|
157
|
+
result = Array[yield(level, self)]
|
158
|
+
@children.each do |c|
|
159
|
+
result.append(*c.traverse_preorder(level + 1, &block))
|
160
|
+
end
|
161
|
+
result
|
162
|
+
end
|
163
|
+
|
164
|
+
# Visits depth-first by left -> right -> root
|
165
|
+
#
|
166
|
+
# level:: the number of hops from the root node
|
167
|
+
# block:: the user supplied block that is executed for every visited node
|
168
|
+
#
|
169
|
+
# the level and node are given as block parameters
|
170
|
+
#
|
171
|
+
# === Returns
|
172
|
+
# A new array containing the values returned by the block
|
173
|
+
#
|
174
|
+
# === Examples
|
175
|
+
#
|
176
|
+
# Get an array of each node together with the level of the node
|
177
|
+
# traverse_postorder{ |level, n| "#{level} #{n.segment}" }
|
178
|
+
def traverse_postorder(level = 0, &block)
|
179
|
+
result = []
|
180
|
+
@children.each do |c|
|
181
|
+
result.concat(c.traverse_postorder(level + 1, &block))
|
47
182
|
end
|
183
|
+
result << yield(level, self)
|
48
184
|
end
|
49
185
|
|
50
|
-
#
|
51
|
-
#
|
186
|
+
# Visits bredth-first left -> right for each level top-down
|
187
|
+
#
|
188
|
+
# level:: the number of hops from the root node
|
189
|
+
# block:: the user supplied block that is executed for every visited node
|
190
|
+
#
|
191
|
+
# the level and node are given as block parameters
|
52
192
|
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
# the level and node are given as block parameters
|
193
|
+
# === Returns
|
194
|
+
# A new array containing the values returned by the block
|
56
195
|
#
|
57
|
-
# Examples
|
58
|
-
#
|
59
|
-
#
|
60
|
-
def
|
196
|
+
# === Examples
|
197
|
+
# Get an array with the name of each node together with the level of the node
|
198
|
+
# traverse_levelorder { |level, n| "#{level} #{n.segment}" }
|
199
|
+
def traverse_levelorder(level = 0, &block)
|
200
|
+
result = []
|
201
|
+
# the node of the original call
|
202
|
+
result << yield(level, self) if level == 0
|
203
|
+
|
204
|
+
# this level
|
205
|
+
@children.each do |c|
|
206
|
+
result << yield(level + 1, c)
|
207
|
+
end
|
208
|
+
|
209
|
+
# next level
|
61
210
|
@children.each do |c|
|
62
|
-
|
63
|
-
c.traverse_top_down(level + 1, &block)
|
211
|
+
result.concat(c.traverse_levelorder(level + 1, &block))
|
64
212
|
end
|
213
|
+
|
214
|
+
result
|
65
215
|
end
|
66
216
|
|
67
|
-
#
|
217
|
+
# Sort the nodes on each level in the tree in lexical order but put
|
68
218
|
# leafs before non-leafs.
|
69
|
-
def
|
70
|
-
@children.sort!
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
219
|
+
def sort_leaf_first!
|
220
|
+
@children.sort! { |a, b| leaf_first(a, b) }
|
221
|
+
@children.each(&:sort_leaf_first!)
|
222
|
+
self
|
223
|
+
end
|
224
|
+
|
225
|
+
# returns:: the number of nodes in the subtree with this node as
|
226
|
+
# root
|
227
|
+
def count
|
228
|
+
result = 0
|
229
|
+
traverse_preorder do |level, node|
|
230
|
+
result += 1
|
78
231
|
end
|
79
|
-
|
232
|
+
result
|
80
233
|
end
|
81
234
|
|
82
|
-
#
|
83
|
-
#
|
84
|
-
# Returns: true if the node is a leaf, false otherwise
|
235
|
+
# return:: true if the node is a leaf, false otherwise
|
85
236
|
def leaf?
|
86
237
|
@children.length.zero?
|
87
238
|
end
|
88
239
|
|
240
|
+
# return:: an array with Pathnames of each full
|
241
|
+
# path for the leaves in this tree
|
242
|
+
def leave_pathnames(prune: false)
|
243
|
+
paths = []
|
244
|
+
traverse_postorder do |l, n|
|
245
|
+
next unless n.leaf?
|
246
|
+
|
247
|
+
paths << (prune ? n.pathname.relative_path_from(pathname) : n.pathname)
|
248
|
+
end
|
249
|
+
paths
|
250
|
+
end
|
251
|
+
|
252
|
+
# return:: true if this node does not have a parent node
|
253
|
+
def root?
|
254
|
+
@parent.nil?
|
255
|
+
end
|
256
|
+
|
257
|
+
# return:: the root node of the tree where this node is a member
|
258
|
+
def root
|
259
|
+
return self if root?
|
260
|
+
|
261
|
+
@parent.root
|
262
|
+
end
|
263
|
+
|
264
|
+
# Finds the node corresponding to the given path.
|
265
|
+
#
|
266
|
+
# path:: a String or Pathname with the path to search for
|
267
|
+
# from_root:: if true start the search from the root of the tree where
|
268
|
+
# this node is a member. If false, start the search from this node's
|
269
|
+
# children.
|
270
|
+
#
|
271
|
+
# return:: the node with the given path or nil if the path
|
272
|
+
# does not exist within this pathtree
|
273
|
+
def node(path, from_root: false)
|
274
|
+
p = clean(path)
|
275
|
+
root = nil
|
276
|
+
|
277
|
+
traverse_preorder do |level, node|
|
278
|
+
q = from_root ? node.pathname : node.pathname.relative_path_from(pathname)
|
279
|
+
if q == p
|
280
|
+
root = node
|
281
|
+
break
|
282
|
+
end
|
283
|
+
end
|
284
|
+
root
|
285
|
+
end
|
286
|
+
|
287
|
+
# adds a copy of the given Pathtree as a subtree to this node. the subtree can not
|
288
|
+
# contain nodes that will end up having the same pathname as any existing
|
289
|
+
# node in the target tree. Note that 'data' attributes will not be copied. The copied
|
290
|
+
# Pathtree nodes will thus point to the same data attributes as the original.
|
291
|
+
#
|
292
|
+
# == Example
|
293
|
+
#
|
294
|
+
# 1. Add my/new/tree to /1/2 -> /1/2/my/new/tree
|
295
|
+
# 2. Add /my/new/tree to /1/2 -> ArgumentError - can not add root as subtree
|
296
|
+
# 3. Trying to add 'new/tree' to '/my' node in a tree with '/my/new/tree' raises
|
297
|
+
# ArgumentError since the pathname that would result already exists within the
|
298
|
+
# target tree.
|
299
|
+
def append_tree(root_node)
|
300
|
+
raise ArgumentError, "Trying to append a root node as subtree!" if root_node.pathname.root?
|
301
|
+
|
302
|
+
# make a copy to make sure it is a self-sustaining PathTree
|
303
|
+
c = root_node.dup
|
304
|
+
|
305
|
+
# get all leaf paths prepended with this node's name to check for
|
306
|
+
# previous existance in this tree.
|
307
|
+
p = c.leave_pathnames.collect { |p| Pathname.new(@name) / p }
|
308
|
+
|
309
|
+
# duplicate ourselves to compare paths
|
310
|
+
t = dup
|
311
|
+
|
312
|
+
# check that no path in c would collide with existing paths
|
313
|
+
common = Set.new(t.leave_pathnames) & Set.new(p)
|
314
|
+
unless common.empty?
|
315
|
+
str = common.collect { |p| p.to_s }.join(",")
|
316
|
+
raise ArgumentError, "Can not append tree due to conflicting paths: #{str}"
|
317
|
+
end
|
318
|
+
|
319
|
+
# hook the subtree into this tree
|
320
|
+
@children << c
|
321
|
+
c.parent = self
|
322
|
+
end
|
323
|
+
|
324
|
+
# Splits the node's path into
|
325
|
+
# - a 'stem', the common path to all nodes in this tree that are on the
|
326
|
+
# same level as this node or closer to the root.
|
327
|
+
# - a 'crown', the remaining path when the stem has been removed from this
|
328
|
+
# node's pathname
|
329
|
+
#
|
330
|
+
# === Example
|
331
|
+
# n.split_stem for the following tree:
|
332
|
+
#
|
333
|
+
# base
|
334
|
+
# |- dir
|
335
|
+
# |- leaf_1
|
336
|
+
# |- branch
|
337
|
+
# |- leaf_2
|
338
|
+
#
|
339
|
+
# yields
|
340
|
+
# ["base/dir", "leaf_1"] when n == leaf_1
|
341
|
+
# ["base/dir", "branch/leaf_2"] when n == leaf_2
|
342
|
+
# ["base", "dir"] when n == "dir"
|
343
|
+
# [nil, "base"] when n == "base"
|
344
|
+
#
|
345
|
+
# return:: [stem, crown]
|
346
|
+
def split_stem
|
347
|
+
r = root
|
348
|
+
s = pathname.descend do |stem|
|
349
|
+
n = r.node(stem, from_root: true)
|
350
|
+
break n if n.children.count != 1 || n == self
|
351
|
+
end
|
352
|
+
|
353
|
+
if s == self
|
354
|
+
[root? ? nil : s.parent.pathname, @name]
|
355
|
+
else
|
356
|
+
[s.pathname, pathname.relative_path_from(s.pathname)]
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
# return:: a Pathname containing the relative path to this node as seen from the
|
361
|
+
# given node
|
362
|
+
def relative_path_from(node)
|
363
|
+
pathname.relative_path_from(node.pathname)
|
364
|
+
end
|
365
|
+
|
366
|
+
# Builds a PathTree with its root as the given file system dir or file
|
367
|
+
#
|
368
|
+
# fs_point:: an absolute or relative path to a file or directory that
|
369
|
+
# already exists in the file system.
|
370
|
+
# prune:: if false, add the entire, absolute, path to the fs_point to
|
371
|
+
# the PathTree. If true, use only the basename of the fs_point as the
|
372
|
+
# root of the PathTree
|
373
|
+
#
|
374
|
+
# You can submit a filter predicate that determine if a specific path
|
375
|
+
# shall be part of the PathTree or not ->(Pathname) { return true/false}
|
376
|
+
#
|
377
|
+
# return:: the node corresponding to the given fs_point in the resulting
|
378
|
+
# pathtree or nil if no nodes matched the given predicate filter
|
379
|
+
#
|
380
|
+
# === Example
|
381
|
+
#
|
382
|
+
# Build a pathtree containing all files under the "mydir" directory that
|
383
|
+
# ends with '.jpg'. The resulting tree will contain the absolute path
|
384
|
+
# to 'mydir' as nodes (eg '/home/gunnar/mydir')
|
385
|
+
#
|
386
|
+
# t = PathTree.build_from_fs("./mydir",true ) { |p| p.extname == ".jpg" }
|
387
|
+
def self.build_from_fs(fs_point, prune: false)
|
388
|
+
top_node = Pathname.new(fs_point).cleanpath
|
389
|
+
raise ArgumentError, "The path '#{fs_point}' does not exist in the file system!" unless top_node.exist?
|
390
|
+
|
391
|
+
t = nil
|
392
|
+
top_node.find do |path|
|
393
|
+
p = Pathname.new(path)
|
394
|
+
|
395
|
+
if (block_given? && yield(p)) || !block_given?
|
396
|
+
t.nil? ? t = PathTree.new(p) : t.add_path(p)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
return nil if t.nil?
|
400
|
+
|
401
|
+
# always return the entry node but prune the parents if
|
402
|
+
# users wishes
|
403
|
+
entry_node = t.node(top_node, from_root: true)
|
404
|
+
(prune ? entry_node.dup : entry_node)
|
405
|
+
end
|
406
|
+
|
407
|
+
# delegate method calls not implemented by PathTree to the associated 'data'
|
408
|
+
# object
|
409
|
+
def method_missing(m, *args, &block)
|
410
|
+
return super if data.nil?
|
411
|
+
|
412
|
+
data.send(m, *args, &block)
|
413
|
+
end
|
414
|
+
|
415
|
+
def respond_to_missing?(method_name, include_private = false)
|
416
|
+
return super(method_name, include_private) if data.nil?
|
417
|
+
|
418
|
+
data.respond_to?(method_name)
|
419
|
+
end
|
420
|
+
|
421
|
+
def to_s
|
422
|
+
traverse_preorder do |level, n|
|
423
|
+
str = " " * 4 * level + "|-- " + n.segment.to_s
|
424
|
+
str += " <#{n.data}>" unless n.data.nil?
|
425
|
+
str
|
426
|
+
end.join("\n")
|
427
|
+
end
|
428
|
+
|
429
|
+
# Return a new PathTree with the nodes whith pathname matching the
|
430
|
+
# given regex.
|
431
|
+
#
|
432
|
+
# The copy will point to the same node data as the original.
|
433
|
+
#
|
434
|
+
# regex:: a Regex matching the pathname of the nodes to be included in
|
435
|
+
# the copy
|
436
|
+
# prune:: remove all parents to this node in the returned copy
|
437
|
+
#
|
438
|
+
# === Returns
|
439
|
+
# the entry node in a new PathTree with the nodes with pathnames matching the given regex
|
440
|
+
# or nil if no nodes match
|
441
|
+
def match(regex, prune: false)
|
442
|
+
copy = nil
|
443
|
+
|
444
|
+
traverse_preorder do |level, n|
|
445
|
+
p = n.pathname
|
446
|
+
next unless regex&.match?(p.to_s)
|
447
|
+
|
448
|
+
copy.nil? ? copy = PathTree.new(p, n.data) : copy.add_path(p, n.data)
|
449
|
+
end
|
450
|
+
return nil if copy.nil?
|
451
|
+
|
452
|
+
# always return the entry node but return a pruned version if
|
453
|
+
# the user wishes
|
454
|
+
entry_node = copy.node(pathname, from_root: true)
|
455
|
+
(prune ? entry_node.dup : entry_node)
|
456
|
+
end
|
457
|
+
|
458
|
+
# Return a new PathTree with the nodes matching the given block
|
459
|
+
#
|
460
|
+
# The copy will point to the same node data as the original.
|
461
|
+
#
|
462
|
+
# prune:: prune all parents to this node from the returned copy
|
463
|
+
#
|
464
|
+
# === Block
|
465
|
+
#
|
466
|
+
# The given block will receive the level (from the entry node) and
|
467
|
+
# the node itself for each node.
|
468
|
+
#
|
469
|
+
# === Returns
|
470
|
+
# the entry node to the new Pathtree or nil if no nodes matched the
|
471
|
+
# given block.
|
472
|
+
#
|
473
|
+
# === Example
|
474
|
+
#
|
475
|
+
# copy = original.filter { |l, n| n.data == "smurf" }
|
476
|
+
#
|
477
|
+
# The above will return a tree with nodes whose data is equal to 'smurf'
|
478
|
+
def filter(prune: false)
|
479
|
+
raise InvalidArgument, "No block given!" unless block_given?
|
480
|
+
|
481
|
+
# build the filtered copy
|
482
|
+
copy = nil
|
483
|
+
traverse_preorder do |level, n|
|
484
|
+
if yield(level, n)
|
485
|
+
p = n.pathname
|
486
|
+
copy.nil? ? copy = PathTree.new(p, n.data) : copy.add_path(p, n.data)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
return nil if copy.nil?
|
491
|
+
|
492
|
+
# always return the entry node but return a pruned version if
|
493
|
+
# the user wishes
|
494
|
+
entry_node = copy.node(pathname, from_root: true)
|
495
|
+
(prune ? entry_node.dup : entry_node)
|
496
|
+
end
|
497
|
+
|
89
498
|
private
|
90
499
|
|
91
|
-
def
|
92
|
-
|
93
|
-
ch.length.zero? ? nil : ch[0]
|
500
|
+
def clean(path)
|
501
|
+
Pathname.new(path).cleanpath
|
94
502
|
end
|
95
|
-
end
|
96
503
|
|
97
|
-
|
98
|
-
if
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
root = PathTree.new
|
111
|
-
paths.each do |p|
|
112
|
-
puts "adding path: #{p}"
|
113
|
-
root.add_path p
|
114
|
-
end
|
115
|
-
root.sort_children
|
116
|
-
root.traverse_top_down do |level, node|
|
117
|
-
puts "#{' ' * level} - #{node.name}"
|
504
|
+
def leaf_first(left, right)
|
505
|
+
if left.leaf? != right.leaf?
|
506
|
+
# always return leaf before non-leaf
|
507
|
+
return left.leaf? ? -1 : 1
|
508
|
+
end
|
509
|
+
|
510
|
+
# for two non-leafs, return lexical order
|
511
|
+
left.segment <=> right.segment
|
512
|
+
end
|
513
|
+
|
514
|
+
def get_child(segment_name)
|
515
|
+
ch = @children.select { |c| c.segment == segment_name.to_s }
|
516
|
+
ch.length.zero? ? nil : ch[0]
|
118
517
|
end
|
119
518
|
end
|