jekyll-semtree 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []