jekyll-semtree 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44fd1bb2e5b2159e83705c9a4593da991a7e4195379b6f22774d693f481863e1
4
- data.tar.gz: 80454edb2aeaee8ee3dbbb590baae6f9c9dfaa7eccf5113f7bf2a87ed35654ba
3
+ metadata.gz: ec2c16946b942b0e6466e9fff5d94e1109fdd7eb6c7b2330ee760cd8e9cb1c5f
4
+ data.tar.gz: 2831eeca5e0722a33ac0d17eff83952245da02a9153173f9d6a8080f8ef27b3a
5
5
  SHA512:
6
- metadata.gz: 2a0938a421d109591927fd21d166746b4f06b86c5980f606fe0bd12938c3fa5ee0797d64b5ff74e01ab524f1af7b26e135ecdc5aeb80c61c50a7004b56739b3b
7
- data.tar.gz: 4a171bf8adfdcebbd61301309eb721ae26840d75c383e9711f3287e5e930efb66423537ef3b7d1ef0b2156750d31fa84ea36fb420c68be371882cb87ef62b04d
6
+ metadata.gz: 6e34ef808a05bab7f70619b733bc96e5885b25ac1c223ec9290835cdca48e46b704586b6c18d57db77c10032babfbce459b60c45c8675b6ed0959fe3e72d6ce3
7
+ data.tar.gz: ef22fa79b8e9e3bb6d0af9e63f48abdcd4c1e6b20628d452cb75d765e56dba90f3e0e999accaa9359df69e5feff942558f123537832666e54386663e8f0dbf7b
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Context
4
+ attr_reader :site
5
+
6
+ def initialize(site)
7
+ @site = site
8
+ end
9
+
10
+ def registers
11
+ { :site => site }
12
+ end
13
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../util/regex"
3
+
4
+ module Jekyll
5
+ module SemTree
6
+
7
+ # note: this is a copy/paste from Jekyll-SemTree;
8
+ # if that plugin is installed, this is all redundant code.
9
+ #
10
+ # this class is responsible for answering any questions
11
+ # related to jekyll markdown documents
12
+ # that are meant to be processed by the wikirefs plugin.
13
+ #
14
+ # the following methods are specifically to address two things:
15
+ # 1. ruby's 'find' / 'detect' function does not throw errors if
16
+ # there are multiple matches. fail fast, i want to know if there
17
+ # are duplicates.
18
+ # (not using sets because i don't want to clobber existing documents)
19
+ # 2. handle all jekyll documents in one place. i don't want to
20
+ # have to filter all documents for target markdown documents
21
+ # every time i need to check if a file exists.
22
+ #
23
+ # there is probably a better way to do this...i would prefer to have
24
+ # a plugin-wide function that just wraps all of this and can be called
25
+ # from anywhere in the plugin...but ruby is not a functional language...
26
+ # gotta have classes...
27
+ #
28
+ class DocManager
29
+ CONVERTER_CLASS = Jekyll::Converters::Markdown
30
+
31
+ def initialize(site)
32
+ return if $wiki_conf.disabled?
33
+
34
+ markdown_converter = site.find_converter_instance(CONVERTER_CLASS)
35
+ # filter docs based on configs
36
+ docs = []
37
+ docs += site.pages if !$wiki_conf.exclude?(:pages)
38
+ docs += site.docs_to_write.filter { |d| !$wiki_conf.exclude?(d.type) }
39
+ @md_docs = docs.filter { |doc| markdown_converter.matches(doc.extname) }
40
+ if @md_docs.nil? || @md_docs.empty?
41
+ Jekyll.logger.warn("Jekyll-SemTree: No documents to process.")
42
+ end
43
+
44
+ @static_files ||= site.static_files
45
+ end
46
+
47
+ # accessors
48
+
49
+ def all
50
+ return @md_docs
51
+ end
52
+
53
+ def get_doc_by_fname(filename)
54
+ Jekyll.logger.error("Jekyll-SemTree: Must provide a 'filename'") if filename.nil? || filename.empty?
55
+ docs = @md_docs.select{ |d| File.basename(d.basename, File.extname(d.basename)) == filename }
56
+ return nil if docs.nil? || docs.empty? || docs.size > 1
57
+ return docs[0]
58
+ end
59
+
60
+ def get_doc_by_fpath(file_path)
61
+ Jekyll.logger.error("Jekyll-SemTree: Must provide a 'file_path'") if file_path.nil? || file_path.empty?
62
+ docs = @md_docs.select{ |d| d.relative_path == (file_path + ".md") }
63
+ return nil if docs.nil? || docs.empty? || docs.size > 1
64
+ return docs[0]
65
+ end
66
+
67
+ def get_doc_by_url(url)
68
+ Jekyll.logger.error("Jekyll-SemTree: Must provide a 'url'") if url.nil? || url.empty?
69
+ docs = @md_docs.select{ |d| d.url == url }
70
+ return nil if docs.nil? || docs.empty? || docs.size > 1
71
+ return docs[0]
72
+ end
73
+
74
+ def get_doc_content(filename)
75
+ doc = self.get_doc_by_fname(filename)
76
+ return nil if docs.nil?
77
+ return doc.content
78
+ end
79
+
80
+ def get_image_by_fname(filename)
81
+ Jekyll.logger.error("Jekyll-SemTree: Must provide a 'filename'") if filename.nil? || filename.empty?
82
+ return nil if @static_files.size == 0 || !SUPPORTED_IMG_FORMATS.any?{ |ext| ext == File.extname(filename).downcase }
83
+ docs = @static_files.select{ |d| File.basename(d.relative_path) == filename }
84
+ return nil if docs.nil? || docs.empty? || docs.size > 1
85
+ return docs[0]
86
+ end
87
+
88
+ # validators
89
+
90
+ def file_exists?(filename, file_path=nil)
91
+ Jekyll.logger.error("Jekyll-SemTree: Must provide a 'filename'") if filename.nil? || filename.empty?
92
+ if file_path.nil?
93
+ return false if get_doc_by_fname(filename).nil? && get_image_by_fname(filename).nil?
94
+ return true
95
+ else
96
+ return false if get_doc_by_fpath(file_path).nil?
97
+ return true
98
+ end
99
+ end
100
+
101
+ # todo: wiki headers/blocks
102
+
103
+ # def doc_has_header?(doc, header)
104
+ # Jekyll.logger.error("Jekyll-SemTree: Must provide a 'header'") if header.nil? || header.empty?
105
+ # # leading + trailing whitespace is ignored when matching headers
106
+ # header_results = doc.content.scan(REGEX_ATX_HEADER).flatten.map { |htxt| htxt.downcase.strip }
107
+ # setext_header_results = doc.content.scan(REGEX_SETEXT_HEADER).flatten.map { |htxt| htxt.downcase.strip }
108
+ # return header_results.include?(header.downcase.strip) || setext_header_results.include?(header.downcase.strip)
109
+ # end
110
+
111
+ # def doc_has_block_id?(doc, block_id)
112
+ # Jekyll.logger.error("Jekyll-SemTree: Must provide a 'block_id'") if block_id.nil? || block_id.empty?
113
+ # # leading + trailing whitespace is ignored when matching blocks
114
+ # block_id_results = doc.content.scan(REGEX_BLOCK).flatten.map { |bid| bid.strip }
115
+ # return block_id_results.include?(block_id)
116
+ # end
117
+ end
118
+
119
+ end
120
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ require "jekyll"
3
+
4
+ # appending to built-in jekyll site object to pass data to jekyll-d3
5
+
6
+ module Jekyll
7
+
8
+ class Site
9
+ # 'doc_mngr' only necessary if 'jekyll-wikirefs' not installed
10
+ attr_accessor :doc_mngr, :tree
11
+ end
12
+
13
+ end
@@ -0,0 +1,478 @@
1
+ # frozen_string_literal: true
2
+ require "jekyll"
3
+
4
+ module Jekyll
5
+
6
+ class Tree
7
+
8
+ OPEN_BRACKETS = '[['
9
+ CLOSE_BRACKETS = ']]'
10
+
11
+ MARKDOWN_BULLET_ASTERISK = '* '
12
+ MARKDOWN_BULLET_DASH = '- '
13
+ MARKDOWN_BULLET_PLUS = '+ '
14
+
15
+ REGEX = {
16
+ LEVEL: /^[ \t]*/, # TODO: link
17
+ TEXT_WITH_LOC: /([^\\:^|\[\]]+)-(\d+)-(\d+)/i,
18
+ TEXT_WITH_ID: /([^\\:^|\[\]]+)-\(([A-Za-z0-9]{5})\)/i,
19
+ WIKITEXT_WITH_ID: /([+*-]) \[\[([^\\:\\^|\[\]]+)-\(([A-Za-z0-9]{5})\)\]\]/i,
20
+ WHITESPACE: /^\s*$/,
21
+ }.freeze
22
+
23
+ def is_markdown_bullet(text)
24
+ return [
25
+ MARKDOWN_BULLET_ASTERISK,
26
+ MARKDOWN_BULLET_DASH,
27
+ MARKDOWN_BULLET_PLUS,
28
+ ].include?(text)
29
+ end
30
+
31
+ attr_accessor :chunk_size # size of indentation for each tree level (set by the first indentation found)
32
+ attr_accessor :duplicates # duplicate node names in the tree
33
+ attr_accessor :level_max #
34
+ attr_accessor :nodes # the tree nodes
35
+ attr_accessor :petiole_map # a hash where each key is each node in the tree and the value is the index file that contains that node/doc
36
+ attr_accessor :root # name of the root node/document
37
+ attr_accessor :trunk # list of index doc fnames
38
+ attr_accessor :virtual_trunk # whether or not the trunk/index documents should be included in the tree data
39
+
40
+ def initialize(content, root_doc, virtual_trunk = false)
41
+ # init
42
+ # tree properties
43
+ @chunk_size = -1
44
+ @level_max = -1
45
+ @duplicates = []
46
+ @mkdn_list = true
47
+ @virtual_trunk = virtual_trunk
48
+ # tree nodes
49
+ @nodes = []
50
+ @petiole_map = {}
51
+ @root = ''
52
+ @trunk = []
53
+
54
+ # go
55
+ root_fname = File.basename(root_doc.basename, File.extname(root_doc.basename))
56
+ # tree_data.each do |data|
57
+ # if doc != root_doc
58
+ # # jekyll pages don't have the slug attribute: https://github.com/jekyll/jekyll/blob/master/lib/jekyll/page.rb#L8
59
+ # if doc.type == :pages
60
+ # page_basename = File.basename(doc.name, File.extname(doc.name))
61
+ # doc.data['slug'] = Jekyll::Utils.slugify(page_basename)
62
+ # end
63
+ # end
64
+ # end
65
+
66
+ # prep
67
+ lines = []
68
+ # single file
69
+ if content.is_a?(String)
70
+ lines = content.split("\n")
71
+ set_units(lines)
72
+ return build_tree('root', { 'root' => lines })
73
+ # multiple files
74
+ elsif content.is_a?(Hash)
75
+ unless root_fname
76
+ puts 'Cannot parse multiple files without a "root" defined'
77
+ return
78
+ end
79
+ unless content.keys.include?(root_fname)
80
+ raise "content hash does not contain: '#{root_fname}'; keys are: #{content.keys.join(', ')}"
81
+ end
82
+ lines = content[root_fname].split("\n")
83
+ set_units(lines)
84
+ content_hash = {}
85
+ content.each do |filename, file_content|
86
+ content_hash[filename] = file_content.split("\n")
87
+ end
88
+ self.clear
89
+ return build_tree(root_fname, deepcopy(content_hash))
90
+ else
91
+ raise "content is not a string or hash: #{content}"
92
+ end
93
+ # print_tree(root)
94
+ end
95
+
96
+ def build_tree(cur_key, content, ancestors = [], total_level = 0)
97
+ @trunk = content.keys
98
+ # if the trunk isn't virtual, handle index/trunk file
99
+ unless @virtual_trunk
100
+ node = TreeNode.new(
101
+ cur_key,
102
+ ancestors.map { |n| raw_text(n.text) },
103
+ total_level,
104
+ )
105
+ if total_level == 0
106
+ add_root(cur_key)
107
+ else
108
+ add_branch(cur_key, node.ancestors)
109
+ end
110
+ ancestors << node
111
+ total_level += 1
112
+ end
113
+ # handle file...
114
+ lines = content[cur_key]
115
+ lines.each_with_index do |line, i|
116
+ text = line.gsub(REGEX[:LEVEL], '')
117
+ next if text.nil? || text.empty?
118
+ if @nodes.map(&:text).include?(raw_text(text))
119
+ @duplicates << raw_text(text)
120
+ next
121
+ end
122
+ # calculate numbers
123
+ line_num = i + 1
124
+ level_match = line.match(REGEX[:LEVEL])
125
+ # number of spaces
126
+ next if level_match.nil?
127
+ size = get_whitespace_size(level_match[0])
128
+ level = get_level(size) + total_level
129
+ @chunk_size = 2 if @chunk_size < 0
130
+ # root
131
+ if total_level == 0 && i == 0
132
+ node = TreeNode.new(
133
+ text,
134
+ [],
135
+ level,
136
+ line_num,
137
+ )
138
+ add_root(raw_text(node.text))
139
+ ancestors << node
140
+ # node
141
+ else
142
+ # connect subtree via 'virtual' semantic-tree node
143
+ # TODO: if cur_key == raw_text(text), print a warning: don't do that.
144
+ if cur_key != raw_text(text) && content.keys.include?(raw_text(text))
145
+ # virtual_levels += @chunk_size # This line is commented out as in the original TypeScript
146
+ ancestors = calc_ancestry(level, ancestors)
147
+ build_tree(raw_text(text), content, deepcopy(ancestors), get_level(size))
148
+ next
149
+ end
150
+ node = TreeNode.new(
151
+ text,
152
+ [],
153
+ level,
154
+ line_num,
155
+ )
156
+ node.text = raw_text(node.text)
157
+ ancestors = calc_ancestry(level, ancestors)
158
+ node.ancestors = ancestors.map { |p| raw_text(p.text) }
159
+ ancestors << node
160
+ add_branch(node.text, node.ancestors, cur_key)
161
+ end
162
+ end
163
+ content.delete(cur_key)
164
+ if content.any? && total_level == 0
165
+ return "Some files were not processed: #{content.keys.join(', ')}"
166
+ end
167
+ if content.empty?
168
+ if @duplicates.any?
169
+ duplicates = @duplicates.uniq
170
+ error_msg = "Tree did not build, duplicate nodes found:\n\n"
171
+ error_msg += duplicates.join(', ') + "\n\n"
172
+ clear
173
+ return error_msg
174
+ end
175
+ return @nodes.dup
176
+ end
177
+ end
178
+
179
+ # helper methods
180
+
181
+ def add_root(text)
182
+ @root = text
183
+ @nodes << TreeNode.new(text)
184
+ @petiole_map[text] = text
185
+ end
186
+
187
+ def add_branch(text, ancestry_titles, trnk_fname = nil)
188
+ trnk_fname ||= text
189
+ ancestry_titles.each_with_index do |ancestry_title, i|
190
+ if i < (ancestry_titles.length - 1)
191
+ node = @nodes.find { |n| n.text == ancestry_title }
192
+ if node && !node.children.include?(ancestry_titles[i + 1])
193
+ node.children << ancestry_titles[i + 1]
194
+ end
195
+ else
196
+ node = @nodes.find { |n| n.text == ancestry_title }
197
+ if node && !node.children.include?(text)
198
+ node.children << text
199
+ end
200
+ end
201
+ end
202
+ @nodes << TreeNode.new(text, ancestry_titles)
203
+ @petiole_map[text] = trnk_fname
204
+ end
205
+
206
+ def calc_ancestry(level, ancestors)
207
+ parent = ancestors.last
208
+ is_child = (parent.level == (level - 1))
209
+ is_sibling = (parent.level == level)
210
+ # child:
211
+ # - [[parent]]
212
+ # - [[child]]
213
+ if is_child
214
+ # continue...
215
+ # sibling:
216
+ # - [[sibling]]
217
+ # - [[sibling]]
218
+ elsif is_sibling
219
+ # we can safely throw away the last node name because
220
+ # it can't have children if we've already decreased the level
221
+ ancestors.pop
222
+ # unrelated (great+) (grand)parent:
223
+ # - [[descendent]]
224
+ # - [[great-grandparent]]
225
+ else # (parent.level < level)
226
+ level_diff = parent.level - level
227
+ (1..(level_diff + 1)).each do
228
+ ancestors.pop
229
+ end
230
+ end
231
+ return ancestors
232
+ end
233
+
234
+ # util methods
235
+
236
+ def raw_text(full_text)
237
+ # strip markdown list marker if it exists
238
+ if @mkdn_list && is_markdown_bullet(full_text[0..1])
239
+ full_text = full_text[2..-1]
240
+ end
241
+ # strip wikistring special chars and line breaks
242
+ # using gsub to replace substrings in Ruby
243
+ full_text.gsub!(OPEN_BRACKETS, '')
244
+ full_text.gsub!(CLOSE_BRACKETS, '')
245
+ full_text.gsub!(/\r?\n|\r/, '')
246
+ return full_text
247
+ end
248
+
249
+ def define_level_size(whitespace)
250
+ if whitespace[0] == ' '
251
+ return whitespace.length
252
+ elsif whitespace[0] == "\t"
253
+ tab_size = 4
254
+ return tab_size
255
+ else
256
+ # puts "defineLevelSize: unknown whitespace: #{whitespace}"
257
+ return -1
258
+ end
259
+ end
260
+
261
+ def get_whitespace_size(whitespace)
262
+ if whitespace.include?(' ')
263
+ return whitespace.length
264
+ elsif whitespace.include?("\t")
265
+ tab_size = 4
266
+ return whitespace.length * tab_size
267
+ else
268
+ # puts "getWhitespaceSize: unknown whitespace: #{whitespace}"
269
+ return whitespace.length
270
+ end
271
+ end
272
+
273
+ def get_level(size)
274
+ (size / @chunk_size) + 1
275
+ end
276
+
277
+ def clear
278
+ @root = ''
279
+ @nodes = []
280
+ @petiole_map = {}
281
+ @duplicates = []
282
+ end
283
+
284
+ def deepcopy(obj)
285
+ Marshal.load(Marshal.dump(obj))
286
+ end
287
+
288
+ def set_units(lines)
289
+ # calculate number of spaces per level and size of deepest level
290
+ lines.each do |line|
291
+ level_match = line.match(REGEX[:LEVEL])
292
+ # calculates number of spaces
293
+ if level_match
294
+ if @chunk_size < 0
295
+ @chunk_size = define_level_size(level_match[0])
296
+ end
297
+ level = get_level(level_match[0].length)
298
+ else
299
+ next
300
+ end
301
+ @level_max = level > @level_max ? level : @level_max
302
+ end
303
+ end
304
+
305
+ # metadata methods
306
+
307
+ def get_all_lineage_ids(target_node_id, node=@nodes.detect { |n| n.text == @root }, ancestors=[], descendents=[], found=false)
308
+ # found target node, stop adding ancestors and build descendents
309
+ if target_node_id == node.id || target_node_id == node.text || found
310
+ node.children.each do |child|
311
+ child_node = @nodes.detect { |n| n.text == child }
312
+ # if the child document is an empty string, it is a missing node
313
+ if child_node.missing
314
+ descendents << child_node.text
315
+ else
316
+ descendents << child_node.id
317
+ end
318
+ self.get_all_lineage_ids(target_node_id, child_node, ancestors.clone, descendents, found=true)
319
+ end
320
+ return ancestors, descendents
321
+ # target node not yet found, build ancestors
322
+ else
323
+ # if the node document is an empty string, it is a missing node
324
+ if node.missing
325
+ ancestors << node.text
326
+ else
327
+ ancestors << node.id
328
+ end
329
+ results = []
330
+ node.children.each do |child|
331
+ child_node = @nodes.detect { |n| n.text == child }
332
+ results.concat(self.get_all_lineage_ids(target_node_id, child_node, ancestors.clone))
333
+ end
334
+ return results.select { |r| !r.nil? }
335
+ end
336
+ end
337
+
338
+ def get_sibling_ids(target_node_id, node=@nodes.detect { |n| n.text == @root }, parent=nil)
339
+ return [] if node.text === @root
340
+ # found target node
341
+ if target_node_id == node.id || target_node_id == node.text
342
+ return parent.children.select { |c| c.id }
343
+ # target node not yet found
344
+ else
345
+ node.children.each do |child|
346
+ child_node = @nodes.detect { |n| n.text == child }
347
+ self.get_sibling_ids(target_node_id, child_node, node)
348
+ end
349
+ end
350
+ end
351
+
352
+ # find the parent and children of the 'target_doc'.
353
+ def find_doc_ancestors_and_children_metadata(target_doc)
354
+ fname = File.basename(target_doc.basename, File.extname(target_doc.basename))
355
+ node = @nodes.detect { |n| n.text == fname }
356
+ return node.ancestors, node.children
357
+ end
358
+
359
+ def in_tree?(fname)
360
+ return @nodes.map(&:text).include?(fname)
361
+ end
362
+
363
+ # ...for debugging
364
+
365
+ def to_s
366
+ puts build_tree_str
367
+ end
368
+
369
+ def print_nodes
370
+ puts "# Tree Nodes: "
371
+ @nodes.each do |node|
372
+ puts "# #{node.to_s}"
373
+ end
374
+ end
375
+
376
+ def build_tree_str(cur_node_name = @root, prefix = '')
377
+ output = "#{cur_node_name}\n"
378
+ node = @nodes.find { |n| n.text == cur_node_name }
379
+ if node.nil?
380
+ puts `SemTree.build_tree_str: error: nil node for name '#{cur_node_name}'`
381
+ return output
382
+ end
383
+ node.children.each_with_index do |child, index|
384
+ is_last_child = index == node.children.length - 1
385
+ child_prefix = prefix + (is_last_child ? '└── ' : '├── ')
386
+ grandchild_prefix = prefix + (is_last_child ? ' ' : '| ')
387
+ subtree = build_tree_str(child, grandchild_prefix)
388
+ output += "#{child_prefix}#{subtree}"
389
+ end
390
+ return output
391
+ end
392
+ end
393
+
394
+ # class TreeNode
395
+ # attr_accessor :ancestors # array of strings for the text of each node from root to current (leaf)
396
+ # attr_accessor :children # array of strings for the text of each child node
397
+ # attr_accessor :doc # the associated jekyll document
398
+ # attr_accessor :text # text of the node -- used for primary identification in tree and must be uniquely named
399
+ # attr_accessor :url # jekyll blog url for the given node/page
400
+
401
+ # def initialize(text, ancestors=[], children=[], url="", doc="")
402
+ # @text = text
403
+ # @ancestors = ancestors
404
+ # # optional
405
+ # @children = children
406
+ # @url = url.nil? ? "" : url
407
+ # @doc = doc
408
+ # end
409
+
410
+ # def missing
411
+ # return @doc == ""
412
+ # end
413
+
414
+ # def type
415
+ # return @doc.type
416
+ # end
417
+
418
+ # def to_s
419
+ # "<Node text: '#{@text}', ancestors: #{@ancestors}, children: #{@children}"
420
+ # end
421
+ # end
422
+
423
+ class TreeNode
424
+ attr_accessor :id # node id -- should be unique
425
+ attr_accessor :ancestors # array of strings for the text of each node from root to current (leaf)
426
+ attr_accessor :children # array of strings for the text of each child node
427
+ attr_accessor :doc # the associated jekyll document
428
+ attr_accessor :level # level in the tree
429
+ attr_accessor :line # line of the index file content
430
+ # does not include stripped content (e.g. yaml);
431
+ # value for index docs is -1
432
+ attr_accessor :text # text of the node -- used for primary identification in tree and must be uniquely named
433
+ attr_accessor :url # jekyll blog url for the given node/page
434
+
435
+ def initialize(text, ancestors=[], level=-1, line=-1, children=[], doc="")
436
+ # mandatory
437
+ @text = text
438
+ @ancestors = ancestors
439
+ # optional
440
+ @children = children
441
+ @level = level
442
+ @line = line
443
+ @doc = doc
444
+ end
445
+
446
+ def missing
447
+ return @doc == ''
448
+ # return (@doc.nil? || (@doc.class == String))
449
+ end
450
+
451
+ def to_s
452
+ "<Node text: '#{@text}', ancestors: #{@ancestors}, children: #{@children}"
453
+ end
454
+
455
+ # doc properties
456
+
457
+ def id
458
+ return (self.missing) ? @text : @doc.url
459
+ end
460
+
461
+ def url
462
+ return (self.missing) ? @text : @doc.url
463
+ end
464
+
465
+ def title
466
+ return (self.missing) ? @text : @doc.data['title']
467
+ end
468
+
469
+ def type
470
+ return (self.missing) ? 'zombie' : @doc.type
471
+ end
472
+
473
+ # for legacy 'jekyll-namespaces' calls
474
+ def namespace
475
+ return false
476
+ end
477
+ end
478
+ end
@@ -3,7 +3,7 @@
3
3
  module Jekyll
4
4
  module SemTree
5
5
 
6
- VERSION = "0.0.1"
6
+ VERSION = "0.0.2"
7
7
 
8
8
  end
9
9
  end
@@ -1,10 +1,166 @@
1
1
  # frozen_string_literal: true
2
+ require "jekyll"
2
3
 
4
+ require_relative "jekyll-semtree/patch/context"
5
+ require_relative "jekyll-semtree/patch/site"
6
+ require_relative "jekyll-semtree/tree"
3
7
  require_relative "jekyll-semtree/version"
4
8
 
9
+
5
10
  module Jekyll
6
11
  module SemTree
7
- class Error < StandardError; end
8
- # Your code goes here...
12
+
13
+ class Generator < Jekyll::Generator
14
+ # for testing
15
+ attr_reader :config
16
+
17
+ CONVERTER_CLASS = Jekyll::Converters::Markdown
18
+ # config keys
19
+ CONFIG_KEY = "semtree"
20
+ ENABLED_KEY = "enabled"
21
+ EXCLUDE_KEY = "exclude"
22
+ DOCTYPE_KEY = "doctype"
23
+ PAGE_KEY = "map"
24
+ ROOT_KEY = "root"
25
+ VIRTUAL_TRUNK_KEY = "virtual_trunk"
26
+
27
+ def initialize(config)
28
+ @config ||= config
29
+ end
30
+
31
+ def generate(site)
32
+ return if disabled?
33
+
34
+ # setup site
35
+ @site = site
36
+ @context ||= Context.new(site)
37
+
38
+ # setup docs (based on configs)
39
+ # unless @site.keys.include('doc_mngr') # may have been installed by jekyll-wikirefs or jekyll-wikilinks already
40
+ # require_relative "jekyll-semtree/patch/doc_manager"
41
+ # Jekyll::Hooks.register :site, :post_read do |site|
42
+ # if !self.disabled?
43
+ # site.doc_mngr = Jekyll::SemTree::DocManager.new(site)
44
+ # end
45
+ # end
46
+ # end
47
+
48
+ # setup markdown docs
49
+ docs = []
50
+ docs += site.pages # if !$wiki_conf.exclude?(:pages)
51
+ docs += site.docs_to_write.filter { |d| !self.excluded?(d.type) }
52
+ @md_docs = docs.filter { |doc| self.markdown_extension?(doc.extname) }
53
+ if @md_docs.empty?
54
+ Jekyll.logger.warn("Jekyll-SemTree: No semtree files to process.")
55
+ end
56
+
57
+ # tree setup
58
+ # root
59
+ root_doc = @md_docs.detect { |d| d.data['slug'] == self.option_root_name }
60
+ if root_doc.nil?
61
+ Jekyll.logger.error("Jekyll-SemTree: No root doc detected.")
62
+ end
63
+ # trunk / index docs
64
+ index_docs = @site.docs_to_write.filter { |d| d.type.to_s == self.option_doctype_name }
65
+ if index_docs.empty?
66
+ Jekyll.logger.error("Jekyll-SemTree: No trunk (index docs) detected.")
67
+ end
68
+ # tree content hash
69
+ tree_hash = build_tree_hash(@site.collections[self.option_doctype_name])
70
+ if tree_hash.empty?
71
+ Jekyll.logger.error("Jekyll-SemTree: No trunk (index docs) detected.")
72
+ end
73
+ # build tree
74
+ @site.tree = Tree.new(tree_hash, root_doc, option_virtual_trunk)
75
+
76
+ # generate metadata
77
+ @site.tree.nodes.each do |n|
78
+ doc = @md_docs.detect { |d| n.text == File.basename(d.basename, File.extname(d.basename)) }
79
+ if !doc.nil?
80
+ n.doc = doc
81
+ ancestorNames, childrenNames = @site.tree.find_doc_ancestors_and_children_metadata(doc)
82
+ doc.data['ancestors'] = fnames_to_urls(ancestorNames)
83
+ doc.data['children'] = fnames_to_urls(childrenNames)
84
+ end
85
+ end
86
+ map_doc = @md_docs.detect { |d| option_page == File.basename(d.basename, File.extname(d.basename)) }
87
+ # root_node = @site.tree.nodes.detect { |n| n.text == @site.tree.root }
88
+ map_doc.data['nodes'] = @site.tree.nodes.map { |n| {
89
+ 'text' => n.text,
90
+ 'url' => n.url,
91
+ 'ancestors' => n.ancestors,
92
+ 'children' => n.children,
93
+ }
94
+ }
95
+ dangling_entries = []
96
+ @site.collections['entries'].each do |d|
97
+ fname = File.basename(d.basename, File.extname(d.basename))
98
+ if !@site.tree.in_tree?(fname)
99
+ dangling_entries << fname
100
+ end
101
+ end
102
+ Jekyll.logger.warn("Jekyll-SemTree: entries not listed in the tree: #{dangling_entries}")
103
+ end
104
+
105
+ def fnames_to_urls(fnames)
106
+ docs = []
107
+ fnames.each do |fname|
108
+ doc = @md_docs.detect { |d| fname == File.basename(d.basename, File.extname(d.basename)) }
109
+ docs << (doc.nil? ? fname : doc.url)
110
+ end
111
+ return docs
112
+ end
113
+
114
+ def build_tree_hash(collection_doc)
115
+ tree_data = {}
116
+ collection_doc.each do |d|
117
+ if d.type.to_s == self.option_doctype_name
118
+ fname = File.basename(d.basename, File.extname(d.basename))
119
+ tree_data[fname] = d.content
120
+ end
121
+ end
122
+ return tree_data
123
+ end
124
+
125
+ # config helpers
126
+
127
+ def disabled?
128
+ option(ENABLED_KEY) == false
129
+ end
130
+
131
+ def excluded?(type)
132
+ return false unless option(EXCLUDE_KEY)
133
+ return option(EXCLUDE_KEY).include?(type.to_s)
134
+ end
135
+
136
+ def markdown_extension?(extension)
137
+ markdown_converter.matches(extension)
138
+ end
139
+
140
+ def markdown_converter
141
+ @markdown_converter ||= @site.find_converter_instance(CONVERTER_CLASS)
142
+ end
143
+
144
+ def option(key)
145
+ @config[CONFIG_KEY] && @config[CONFIG_KEY][key]
146
+ end
147
+
148
+ def option_root_name
149
+ return option(ROOT_KEY) ? @config[CONFIG_KEY][ROOT_KEY] : 'i.bonsai'
150
+ end
151
+
152
+ def option_doctype_name
153
+ return option(DOCTYPE_KEY) ? @config[CONFIG_KEY][DOCTYPE_KEY] : 'index'
154
+ end
155
+
156
+ def option_virtual_trunk
157
+ return option(VIRTUAL_TRUNK_KEY) ? @config[CONFIG_KEY][VIRTUAL_TRUNK_KEY] : false
158
+ end
159
+
160
+ def option_page
161
+ return option(PAGE_KEY) ? @config[CONFIG_KEY][PAGE_KEY] : 'map'
162
+ end
163
+
164
+ end
9
165
  end
10
166
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-semtree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - manunamz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-02 00:00:00.000000000 Z
11
+ date: 2023-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -32,14 +32,18 @@ extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
34
  - lib/jekyll-semtree.rb
35
+ - lib/jekyll-semtree/patch/context.rb
36
+ - lib/jekyll-semtree/patch/doc_manager.rb
37
+ - lib/jekyll-semtree/patch/site.rb
38
+ - lib/jekyll-semtree/tree.rb
35
39
  - lib/jekyll-semtree/version.rb
36
- homepage: https://github.com/manunamz/jekyll-semtree
40
+ homepage: https://github.com/wikibonsai/jekyll-semtree
37
41
  licenses:
38
42
  - MIT
39
43
  metadata:
40
- homepage_uri: https://github.com/manunamz/jekyll-semtree
41
- source_code_uri: https://github.com/manunamz/jekyll-semtree
42
- changelog_uri: https://github.com/manunamz/jekyll-semtree/blob/main/CHANGELOG.md
44
+ homepage_uri: https://github.com/wikibonsai/jekyll-semtree
45
+ source_code_uri: https://github.com/wikibonsai/jekyll-semtree
46
+ changelog_uri: https://github.com/wikibonsai/jekyll-semtree/blob/main/CHANGELOG.md
43
47
  post_install_message:
44
48
  rdoc_options: []
45
49
  require_paths:
@@ -55,8 +59,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
59
  - !ruby/object:Gem::Version
56
60
  version: '0'
57
61
  requirements: []
58
- rubygems_version: 3.4.6
62
+ rubygems_version: 3.4.10
59
63
  signing_key:
60
64
  specification_version: 4
61
- summary: "You thought there was something in here, didn't you? \U0001F609"
65
+ summary: Add jekyll support for a semantic tree (in markdown).
62
66
  test_files: []