jekyll-wikilinks 0.0.3 → 0.0.4

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: 5221a2c4f1d84db636f09d707f0d55891b17be4e35946d1bad55ed743083fde1
4
- data.tar.gz: c5b64c09bab070d24a6a1e4af2461e3f09b996211cb4fdff89bd2d624ca4d0b5
3
+ metadata.gz: 9f7349631b7b0ce9484512d06a368ac2808d3d3c64bdf7ccd66e845dc5f41380
4
+ data.tar.gz: 87c6d80c73e79c260d3b58acb8cc2e3c62dc0c29c2574a9c54bd5f476e70e8cf
5
5
  SHA512:
6
- metadata.gz: 6732b84944cbe73c21c87bb43dbdf9006929c2110c9980507e1739df270098a8aa1f6c0f80998160189440b5bb7f5df52193cdd0af4b41c7e1e6e6d825379517
7
- data.tar.gz: 1718789738c86cdb00094070a470758c60f248af37c6e0c70b34701bc5c331fdc480f64e01a04350ddd4ac6859c59d808e6afa800bb1400357987229c4671330
6
+ metadata.gz: 05a261827c04023b8072f85a566c88abad259885e6af6fd85909d61f93200849994de360076023b383f2abab7f3ad4d92e4e755f0ea9cd487e1c59692786d3bf
7
+ data.tar.gz: 3b3862a5e00356d5227e39a346405e238255fe4cc4275c1f095a2ca828e62d7e1f3468035c99278c95dcbe2a340521588dd2ebc64571a9bec963affc19e9328e
@@ -1,145 +1,133 @@
1
1
  # frozen_string_literal: true
2
2
  require "jekyll"
3
+
3
4
  require_relative "jekyll-wikilinks/context"
5
+ require_relative "jekyll-wikilinks/doc_manager"
6
+ require_relative "jekyll-wikilinks/link_index"
7
+ require_relative "jekyll-wikilinks/parser"
8
+
4
9
  require_relative "jekyll-wikilinks/filter"
5
10
  require_relative "jekyll-wikilinks/version"
6
11
 
12
+ Liquid::Template.register_filter(JekyllWikiLinks::TypeFilters)
7
13
 
8
14
  module JekyllWikiLinks
9
- class Generator < Jekyll::Generator
10
- attr_accessor :site, :config, :md_docs, :graph_nodes, :graph_links
15
+ class Generator < Jekyll::Generator
16
+ attr_accessor :site, :config, :md_docs, :doc_manager, :link_index, :parser, :graph_nodes, :graph_links
11
17
 
12
18
  # Use Jekyll's native relative_url filter
13
19
  include Jekyll::Filters::URLFilters
14
20
 
15
- CONFIG_KEY = "wikilinks"
16
21
  CONVERTER_CLASS = Jekyll::Converters::Markdown
22
+ # config
23
+ CONFIG_KEY = "wikilinks"
17
24
  ENABLED_KEY = "enabled"
18
- ENABLED_GRAPH_DATA_KEY = "enabled"
19
25
  EXCLUDE_KEY = "exclude"
20
- EXCLUDE_GRAPH_KEY = "exclude"
26
+ # graph config
21
27
  GRAPH_DATA_KEY = "d3_graph_data"
28
+ ENABLED_GRAPH_DATA_KEY = "enabled"
29
+ EXCLUDE_GRAPH_KEY = "exclude"
30
+ GRAPH_ASSETS_LOCATION_KEY = "assets_rel_path"
31
+
32
+ # identify missing links in doc via .invalid-wiki-link class and nested doc-text.
33
+ REGEX_INVALID_WIKI_LINK = /invalid-wiki-link#{REGEX_NOT_GREEDY}\[\[(#{REGEX_NOT_GREEDY})\]\]/i
22
34
 
23
35
  def initialize(config)
24
- @config = config
36
+ @config ||= config
37
+ @testing ||= config['testing'] if config.keys.include?('testing')
25
38
  end
26
39
 
27
40
  def generate(site)
28
41
  return if disabled?
42
+ self.old_config_warn()
29
43
  Jekyll.logger.debug "Excluded jekyll types: ", option(EXCLUDE_KEY)
30
44
  Jekyll.logger.debug "Excluded jekyll types in graph: ", option_graph(EXCLUDE_GRAPH_KEY)
31
-
45
+
46
+ # setup site
32
47
  @site = site
33
- @context = context
34
-
35
- documents = []
36
- documents += site.pages if !exclude?(:pages)
37
- included_docs = site.docs_to_write.filter { |d| !exclude?(d.type) }
38
- documents += included_docs
39
- @md_docs = documents.select {|doc| markdown_extension?(doc.extname) }
40
-
41
- old_config_warn()
42
-
43
- # build links
44
- md_docs.each do |document|
45
- parse_wiki_links(document)
46
- end
47
-
48
- # backlinks data handling
48
+ @context ||= JekyllWikiLinks::Context.new(site)
49
+
50
+ # setup markdown docs
51
+ docs = []
52
+ docs += site.pages if !exclude?(:pages)
53
+ docs += site.docs_to_write.filter { |d| !exclude?(d.type) }
54
+ @md_docs = docs.filter {|doc| markdown_extension?(doc.extname) }
55
+
56
+ # setup helper classes
57
+ @doc_manager = DocManager.new(@md_docs, @site.static_files)
58
+ @parser = Parser.new(@context, @markdown_converter, @doc_manager)
59
+ @link_index = LinkIndex.new(@site, @doc_manager)
60
+
61
+ # parse + populate index
62
+ @md_docs.each do |doc|
63
+ @parser.parse(doc.content)
64
+ @link_index.populate_attributes(doc, @parser.typed_link_blocks)
65
+ end
66
+ @link_index.process
67
+
68
+ # handle graph data
49
69
  @graph_nodes, @graph_links = [], []
50
- md_docs.each do |document|
51
- document.data['backlinks'] = get_backlinks(document)
52
- if !disabled_graph_data? && !exclude_graph?(document.type)
53
- generate_graph_data(document)
70
+ @md_docs.each do |doc|
71
+ if !disabled_graph_data? && !self.excluded_in_graph?(doc.type)
72
+ self.generate_graph_data(doc)
54
73
  end
55
74
  end
56
-
57
75
  if !disabled_graph_data?
58
- write_graph_data()
76
+ self.write_graph_data()
59
77
  end
60
78
  end
61
79
 
62
- def old_config_warn()
63
- if config.include?("wikilinks_collection")
64
- Jekyll.logger.warn "Deprecated: As of 0.0.3, 'wikilinks_collection' is no longer used for configs. jekyll-wikilinks will scan all markdown files by default. Check README for details: https://shorty25h0r7.github.io/jekyll-wikilinks/"
65
- end
80
+ # config helpers
81
+
82
+ def disabled?
83
+ option(ENABLED_KEY) == false
84
+ end
85
+
86
+ def exclude?(type)
87
+ return false unless option(EXCLUDE_KEY)
88
+ return option(EXCLUDE_KEY).include?(type.to_s)
66
89
  end
67
90
 
68
- def parse_wiki_links(note)
69
- # Convert all Wiki/Roam-style double-bracket link syntax to plain HTML
70
- # anchor tag elements (<a>) with "wiki-link" CSS class
71
- md_docs.each do |note_potentially_linked_to|
72
- title_from_filename = File.basename(
73
- note_potentially_linked_to.basename,
74
- File.extname(note_potentially_linked_to.basename)
75
- )
76
-
77
- note_url = relative_url(note_potentially_linked_to.url) if note_potentially_linked_to&.url
91
+ def has_custom_assets_path?
92
+ return !!option_graph(GRAPH_ASSETS_LOCATION_KEY)
93
+ end
78
94
 
79
- # Replace double-bracketed links using note title
80
- # [[feline.cats]]
81
- regex_wl, cap_gr = regex_wiki_link(title_from_filename)
82
- render_txt = note_potentially_linked_to.data['title'].downcase
83
- note.content = note.content.gsub(
84
- regex_wl,
85
- "<a class='wiki-link' href='#{note_url}'>#{render_txt}</a>"
86
- )
95
+ def markdown_extension?(extension)
96
+ markdown_converter.matches(extension)
97
+ end
87
98
 
88
- # Replace double-bracketed links with alias (right)
89
- # [[feline.cats|this is a link to the note about cats]]
90
- regex_wl, cap_gr = regex_wiki_link_w_alias_right(title_from_filename)
91
- note.content = note.content.gsub(
92
- regex_wl,
93
- "<a class='wiki-link' href='#{note_url}'>#{cap_gr}</a>"
94
- )
99
+ def markdown_converter
100
+ @markdown_converter ||= site.find_converter_instance(CONVERTER_CLASS)
101
+ end
95
102
 
96
- # Replace double-bracketed links with alias (left)
97
- # [[this is a link to the note about cats|feline.cats]]
98
- regex_wl, cap_gr = regex_wiki_link_w_alias_left(title_from_filename)
99
- note.content = note.content.gsub(
100
- regex_wl,
101
- "<a class='wiki-link' href='#{note_url}'>#{cap_gr}</a>"
102
- )
103
- end
103
+ def option(key)
104
+ config[CONFIG_KEY] && config[CONFIG_KEY][key]
105
+ end
106
+
107
+ # graph config helpers
104
108
 
105
- # At this point, all remaining double-bracket-wrapped words are
106
- # pointing to non-existing pages, so let's turn them into disabled
107
- # links by greying them out and changing the cursor
108
- # vanilla wiki-links
109
- regex_wl, cap_gr = regex_wiki_link()
110
- note.content = note.content.gsub(
111
- regex_wl,
112
- "<span title='There is no note that matches this link.' class='invalid-wiki-link'>[[#{cap_gr}]]</span>"
113
- )
114
- # aliases -- both kinds
115
- regex_wl, cap_gr = regex_wiki_link_w_alias()
116
- note.content = note.content.gsub(
117
- regex_wl,
118
- "<span title='There is no note that matches this link.' class='invalid-wiki-link'>[[#{cap_gr}]]</span>"
119
- )
109
+ def disabled_graph_data?
110
+ option_graph(ENABLED_GRAPH_DATA_KEY) == false
120
111
  end
121
112
 
122
- def get_backlinks(doc)
123
- backlinks = []
124
- md_docs.each do |backlinked_doc|
125
- if backlinked_doc.content.include?(doc.url)
126
- backlinks << backlinked_doc
127
- end
128
- end
129
- return backlinks
113
+ def excluded_in_graph?(type)
114
+ return false unless option_graph(EXCLUDE_KEY)
115
+ return option_graph(EXCLUDE_KEY).include?(type.to_s)
130
116
  end
131
117
 
132
- def invalid_wiki_links(doc)
133
- regex, _ = regex_invalid_wiki_link()
134
- return doc.content.scan(regex)[0]
118
+ def option_graph(key)
119
+ config[GRAPH_DATA_KEY] && config[GRAPH_DATA_KEY][key]
135
120
  end
136
121
 
122
+ # graph helpers
123
+
137
124
  def generate_graph_data(doc)
138
125
  Jekyll.logger.debug "Processing graph nodes for doc: ", doc.data['title']
139
126
  # missing nodes
140
- missing_node_names = invalid_wiki_links(doc)
127
+ missing_node_names = doc.content.scan(REGEX_INVALID_WIKI_LINK)
141
128
  if !missing_node_names.nil?
142
- missing_node_names.each do |missing_node_name|
129
+ missing_node_names.each do |missing_node_name_captures|
130
+ missing_node_name = missing_node_name_captures[0]
143
131
  if graph_nodes.none? { |node| node[:id] == missing_node_name }
144
132
  Jekyll.logger.warn "Net-Web node missing: ", missing_node_name
145
133
  Jekyll.logger.warn " in: ", doc.data['slug']
@@ -161,10 +149,13 @@ module JekyllWikiLinks
161
149
  url: relative_url(doc.url),
162
150
  label: doc.data['title'],
163
151
  }
164
- get_backlinks(doc).each do |b|
165
- if !exclude_graph?(b.type)
152
+ # TODO: this link calculation ends up with duplicates -- re-visit this later.
153
+ all_links = doc.data['attributes'] + doc.data['backlinks']
154
+ all_links.each do |link|
155
+ linked_doc = link['doc']
156
+ if !excluded_in_graph?(linked_doc.type)
166
157
  graph_links << {
167
- source: relative_url(b.url),
158
+ source: relative_url(linked_doc.url),
168
159
  target: relative_url(doc.url),
169
160
  }
170
161
  end
@@ -172,100 +163,30 @@ module JekyllWikiLinks
172
163
  end
173
164
 
174
165
  def write_graph_data()
166
+ assets_path = has_custom_assets_path? ? option_graph(GRAPH_ASSETS_LOCATION_KEY) : "/assets"
167
+ if !File.directory?(File.join(site.source, assets_path))
168
+ Jekyll.logger.error "Assets location does not exist, please create required directories for path: ", assets_path
169
+ end
175
170
  # from: https://github.com/jekyll/jekyll/issues/7195#issuecomment-415696200
176
- static_file = Jekyll::StaticFile.new(site, site.source, "/assets", "graph-net-web.json")
171
+ static_file = Jekyll::StaticFile.new(site, site.source, assets_path, "graph-net-web.json")
172
+ # TODO: make write file location more flexible -- requiring a write location configuration feels messy...
177
173
  File.write(@site.source + static_file.relative_path, JSON.dump({
178
174
  links: graph_links,
179
175
  nodes: graph_nodes,
180
176
  }))
181
- end
182
-
183
- def context
184
- @context ||= JekyllWikiLinks::Context.new(site)
185
- end
186
-
187
-
188
- def exclude?(type)
189
- return false unless option(EXCLUDE_KEY)
190
- return option(EXCLUDE_KEY).include?(type.to_s)
191
- end
192
-
193
- def exclude_graph?(type)
194
- return false unless option_graph(EXCLUDE_KEY)
195
- return option_graph(EXCLUDE_KEY).include?(type.to_s)
196
- end
197
-
198
- def markdown_extension?(extension)
199
- markdown_converter.matches(extension)
200
- end
201
-
202
- def markdown_converter
203
- @markdown_converter ||= site.find_converter_instance(CONVERTER_CLASS)
204
- end
205
-
206
- def option(key)
207
- config[CONFIG_KEY] && config[CONFIG_KEY][key]
208
- end
209
-
210
- def option_graph(key)
211
- config[GRAPH_DATA_KEY] && config[GRAPH_DATA_KEY][key]
212
- end
213
-
214
- def disabled?
215
- option(ENABLED_KEY) == false
216
- end
217
-
218
- def disabled_graph_data?
219
- option_graph(ENABLED_GRAPH_DATA_KEY) == false
220
- end
221
-
222
- # regex
223
- # returns two items: regex and a target capture group (text to be rendered)
224
- # using functions instead of constants because of the need to access 'wiki_link_text'
225
- # -- esp. when aliasing.
226
-
227
- def regex_invalid_wiki_link()
228
- # identify missing links in note via .invalid-wiki-link class and nested note-name.
229
- regex = /invalid-wiki-link[^\]]+\[\[([^\]]+)\]\]/i
230
- cap_gr = "\\1" # this is mostly just to remain consistent with other regex functions
231
- return regex, cap_gr
232
- end
233
-
234
- def regex_wiki_link(wiki_link_text='')
235
- if wiki_link_text.empty?
236
- regex = /(\[\[)([^\|\]]+)(\]\])/i
237
- cap_gr = "\\2"
238
- return regex, cap_gr
239
- else
240
- regex = /\[\[#{wiki_link_text}\]\]/i
241
- cap_gr = wiki_link_text
242
- return regex, cap_gr
177
+ # tests fail without manually adding the static file, but actual site builds seem to do ok
178
+ # ...although there does seem to be a race condition which causes a rebuild to be necessary in order to detect the graph data file
179
+ if @testing
180
+ @site.static_files << static_file if !@site.static_files.include?(static_file)
243
181
  end
244
182
  end
245
183
 
246
- def regex_wiki_link_w_alias()
247
- regex = /(\[\[)([^\]\|]+)(\|)([^\]]+)(\]\])/i
248
- cap_gr = "\\2|\\4"
249
- return regex, cap_gr
250
- end
251
-
252
- def regex_wiki_link_w_alias_left(wiki_link_text)
253
- raise ArgumentError.new(
254
- "Expected a value for 'wiki_link_text'"
255
- ) if wiki_link_text.nil?
256
- regex = /(\[\[)([^\]\|]+)(\|)(#{wiki_link_text})(\]\])/i
257
- cap_gr = "\\2"
258
- return regex, cap_gr
259
- end
260
-
261
- def regex_wiki_link_w_alias_right(wiki_link_text)
262
- raise ArgumentError.new(
263
- "Expected a value for 'wiki_link_text'"
264
- ) if wiki_link_text.nil?
265
- regex = /(\[\[)(#{wiki_link_text})(\|)([^\]]+)(\]\])/i
266
- cap_gr = "\\4"
267
- return regex, cap_gr
268
- end
184
+ # !! deprecated !!
269
185
 
186
+ def old_config_warn()
187
+ if config.include?("wikilinks_collection")
188
+ Jekyll.logger.warn "As of 0.0.3, 'wikilinks_collection' is no longer used for configs. jekyll-wikilinks will scan all markdown files by default. Check README for details."
189
+ end
190
+ end
270
191
  end
271
- end
192
+ end
@@ -0,0 +1,68 @@
1
+ require_relative "naming_const"
2
+
3
+ module JekyllWikiLinks
4
+ class DocManager
5
+ attr_accessor :md_docs, :static_files
6
+
7
+ # kramdown header regexes
8
+ # atx header: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/header.rb#L29
9
+ REGEX_ATX_HEADER = /^\#{1,6}[\t ]*([^ \t].*)\n/i
10
+ # setext header: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/header.rb#L17
11
+ REGEX_SETEXT_HEADER = /^ {0,3}([^ \t].*)\n[-=][-=]*[ \t\r\f\v]*\n/i
12
+ # obsidian-style
13
+ REGEX_BLOCK = /.*\s\^#{REGEX_BLOCK_ID_TXT}^\n/i
14
+
15
+ def initialize(md_docs, static_files)
16
+ @md_docs ||= md_docs
17
+ @static_files ||= static_files
18
+ end
19
+
20
+ def all
21
+ return @md_docs
22
+ end
23
+
24
+ def get_doc_by_fname(filename)
25
+ return nil if filename.nil? || @md_docs.size == 0
26
+ docs = @md_docs.select{ |d| File.basename(d.basename, File.extname(d.basename)) == filename }
27
+ return nil if docs.nil? || docs.size > 1
28
+ return docs[0]
29
+ end
30
+
31
+ def get_doc_by_url(url)
32
+ return nil if url.nil? || @md_docs.size == 0
33
+ docs = @md_docs.select{ |d| d.url == url }
34
+ return nil if docs.nil? || docs.size > 1
35
+ return docs[0]
36
+ end
37
+
38
+ def get_doc_content(filename)
39
+ return nil if filename.nil? || @md_docs.size == 0
40
+ docs = @md_docs.select{ |d| File.basename(d.basename, File.extname(d.basename)) == filename }
41
+ return docs[0].content if docs.size == 1
42
+ return nil
43
+ end
44
+
45
+ # 'bname' -> 'basename' (filename with extension)
46
+ def get_image_by_bname(filename)
47
+ return nil if filename.nil? || @static_files.size == 0 || !SUPPORTED_IMG_FORMATS.any?{ |ext| ext == File.extname(filename).downcase }
48
+ docs = @static_files.select{ |d| d.basename == filename[...-4] }
49
+ return nil if docs.nil? || docs.size > 1
50
+ return docs[0]
51
+ end
52
+
53
+ def self.doc_has_header?(doc, header)
54
+ return nil if header.nil?
55
+ # leading + trailing whitespace is ignored when matching headers
56
+ header_results = doc.content.scan(REGEX_ATX_HEADER).flatten.map { |htxt| htxt.strip }
57
+ setext_header_results = doc.content.scan(REGEX_SETEXT_HEADER).flatten.map { |htxt| htxt.strip }
58
+ return header_results.include?(header.strip) || setext_header_results.include?(header.strip)
59
+ end
60
+
61
+ def self.doc_has_block_id?(doc, block_id)
62
+ return nil if block_id.nil?
63
+ # leading + trailing whitespace is ignored when matching blocks
64
+ block_id_results = doc.content.scan(REGEX_BLOCK).flatten.map { |bid| bid.strip }
65
+ return block_id_results.include?(block_id)
66
+ end
67
+ end
68
+ end
@@ -1,16 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JekyllWikiLinks
4
- module BackLinkTypeFilters
5
- # usage:
6
- # {% assign note_backlinks = page.backlinks | backlink_type = "notes" %}
7
- def backlink_type(backlinks, type)
8
- return if backlinks.nil?
9
- target_backlinks = []
10
- backlinks.each do |bl|
11
- target_backlinks << bl if self.to_string(bl.type) == type
4
+ module TypeFilters
5
+ # 'links' accepts both untyped links, typed links, and attributes; fore and back.
6
+
7
+ # usage: {% assign note_links = page.links | doc_type = "notes" %}
8
+ # "doc_type" is the jekyll type ("pages", "posts", "<collection-name>")
9
+ def doc_type(links, doc_type)
10
+ return if links.nil?
11
+ target_links = []
12
+ links.each do |l|
13
+ target_links << l if self.to_string(l['doc'].type) == doc_type.to_str
12
14
  end
13
- return target_backlinks
15
+ return target_links.uniq
16
+ end
17
+
18
+ # usage: {% assign author_links = page.links | link_type = "author" %}
19
+ # "link_type" is the wikilink's type, the string that appears before the link in `link-type::[[wikilink]]`.
20
+ def link_type(links, link_type)
21
+ return if links.nil?
22
+ target_links = []
23
+ link.each do |l|
24
+ target_links << l if self.to_string(l['type'].to_str) == link_type.to_str
25
+ end
26
+ return target_links.uniq
14
27
  end
15
28
 
16
29
  def to_string(type)
@@ -24,5 +37,3 @@ module JekyllWikiLinks
24
37
  end
25
38
  end
26
39
  end
27
-
28
- Liquid::Template.register_filter(JekyllWikiLinks::BackLinkTypeFilters)
@@ -0,0 +1,24 @@
1
+ # TODO: Would be nice to put LinkIndex-related data into real objects as opposed to sticking tons and tons of data into frontmatter...I think...
2
+
3
+ # modelling off of 'related_posts': https://github.com/jekyll/jekyll/blob/6855200ebda6c0e33f487da69e4e02ec3d8286b7/lib/jekyll/document.rb#L402
4
+ # module LinkLogic
5
+ # attr_accessor :attributed, :attributes, :backlinks, :forelinks
6
+
7
+ # # 'links' 'type' is 'nil' for untyped links.
8
+ # # 'attributes' are block-level typed forelinks; their 'type' may not be 'nil'.
9
+ # # 'attributed' are block-level typed backlinks; their 'type' may not be 'nil'.
10
+ # # [{ 'type': str, 'doc': doc }, ...]
11
+ # end
12
+
13
+ # module Jekyll
14
+ # class Page
15
+ # # it would be nice if these would only exist if the page is guaranteed to be a markdown file.
16
+ # include LinkLogic
17
+ # end
18
+ # end
19
+
20
+ # module Jekyll
21
+ # class Document
22
+ # include LinkLogic
23
+ # end
24
+ # end
@@ -0,0 +1,81 @@
1
+ module JekyllWikiLinks
2
+ class LinkIndex
3
+ attr_accessor :index
4
+
5
+ # Use Jekyll's native relative_url filter
6
+ include Jekyll::Filters::URLFilters
7
+
8
+ REGEX_LINK_TYPE = /<a\sclass="wiki-link(\slink-type\s(?<link-type>([^"]+)))?"\shref="(?<link-url>([^"]+))">/i
9
+
10
+ def initialize(site, doc_manager)
11
+ @context ||= JekyllWikiLinks::Context.new(site)
12
+ @doc_manager ||= doc_manager
13
+ @index = {}
14
+ @doc_manager.all.each do |doc|
15
+ @index[doc.url] = LinksInfo.new()
16
+ end
17
+ end
18
+
19
+ def process
20
+ self.populate_links()
21
+ # apply index info to each document
22
+ @doc_manager.all.each do |doc|
23
+ doc.data['attributed'] = @index[doc.url].attributed
24
+ doc.data['backlinks'] = @index[doc.url].backlinks
25
+ doc.data['attributes'] = @index[doc.url].attributes
26
+ doc.data['forelinks'] = @index[doc.url].forelinks
27
+ end
28
+ end
29
+
30
+ def populate_attributes(doc, typed_link_blocks)
31
+ typed_link_blocks.each do |tl|
32
+ attr_doc = @doc_manager.get_doc_by_fname(tl.filename)
33
+ @index[doc.url].attributes << {
34
+ 'type' => tl.link_type,
35
+ 'doc' => attr_doc,
36
+ }
37
+ @index[attr_doc.url].attributed << {
38
+ 'type' => tl.link_type,
39
+ 'doc' => doc,
40
+ }
41
+ end
42
+ end
43
+
44
+ def populate_links()
45
+ # for each document...
46
+ @doc_manager.all.each do |doc|
47
+ # ...process its forelinks
48
+ doc.content.scan(REGEX_LINK_TYPE).each do |m|
49
+ ltype, lurl = m[0], m[1]
50
+ @index[doc.url].forelinks << {
51
+ 'type' => ltype,
52
+ 'doc' => @doc_manager.get_doc_by_url(lurl),
53
+ }
54
+ end
55
+ # ...process its backlinks
56
+ @doc_manager.all.each do |doc_to_backlink|
57
+ doc_to_backlink.content.scan(REGEX_LINK_TYPE).each do |m|
58
+ ltype, lurl = m[0], m[1]
59
+ if lurl == relative_url(doc.url)
60
+ @index[doc.url].backlinks << {
61
+ 'type' => ltype,
62
+ 'doc' => doc_to_backlink,
63
+ }
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ class LinksInfo
71
+ attr_accessor :attributed, :attributes, :backlinks, :forelinks
72
+
73
+ def initialize
74
+ @attributed = [] # (block-level typed forelinks)
75
+ @attributes = [] # (block-level typed backlinks)
76
+ @backlinks = []
77
+ @forelinks = []
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,21 @@
1
+ # naming_const.rb
2
+ # regex constants defining supported file types and valid names for files, variables, or text
3
+ #
4
+
5
+ module JekyllWikiLinks
6
+ # TODO: Fix REGEX_NOT_GREEDY
7
+ # REGEX_NOT_GREEDY = /[^(?!\]\])]+/i
8
+ # REGEX_NOT_GREEDY = /(?!\]\]).*/i
9
+ REGEX_NOT_GREEDY = /[^\]]+/i
10
+ # <variables> only work with 'match' function, not with 'scan' function. :/
11
+ # oh well...they are there for easier debugging...
12
+ # valid naming conventions # capture indeces for WikiLinks class (0 is 'embed')
13
+ REGEX_LINK_TYPE_TXT = /(?<link-type-txt>([^\n\s\!\#\^\|\]]+))/i # 1
14
+ REGEX_FILENAME = /(?<filename>([^\\\/:\#\^\|\[\]]+))/i # 2
15
+ REGEX_HEADER_TXT = /(?<header-txt>([^\!\#\^\|\[\]]+))/i # 3
16
+ REGEX_BLOCK_ID_TXT = /(?<block-id>([^\\\/:\!\#\^\|\[\]]+))/i # 4
17
+ REGEX_LABEL_TXT = /(?<label-txt>(#{REGEX_NOT_GREEDY}))/i # 5
18
+
19
+ # from: https://docs.github.com/en/github/managing-files-in-a-repository/working-with-non-code-files/rendering-and-diffing-images
20
+ SUPPORTED_IMG_FORMATS = Set.new(['.png', '.jpg', '.gif', '.psd', '.svg'])
21
+ end
@@ -0,0 +1,236 @@
1
+ require_relative "naming_const"
2
+
3
+ module JekyllWikiLinks
4
+ REGEX_LINK_EMBED = /(?<embed>(\!))/i # 0 (capture index for WikiLinks class)
5
+ REGEX_LINK_TYPE = /::/
6
+ REGEX_LINK_HEADER = /\#/
7
+ REGEX_LINK_BLOCK = /\#\^/
8
+ REGEX_LINK_LABEL = /\|/
9
+ REGEX_WIKI_LINKS = %r{
10
+ (#{REGEX_LINK_EMBED})?
11
+ (#{REGEX_LINK_TYPE_TXT}#{REGEX_LINK_TYPE})?
12
+ \[\[
13
+ #{REGEX_FILENAME}
14
+ (#{REGEX_LINK_HEADER}#{REGEX_HEADER_TXT})?
15
+ (#{REGEX_LINK_BLOCK}#{REGEX_BLOCK_ID_TXT})?
16
+ (#{REGEX_LINK_LABEL}#{REGEX_LABEL_TXT})?
17
+ \]\]
18
+ }x
19
+ REGEX_TYPED_LINK_BLOCK = /#{REGEX_LINK_TYPE_TXT}#{REGEX_LINK_TYPE}\[\[#{REGEX_FILENAME}\]\]\n/i
20
+
21
+ # more of a "parser" than a parser
22
+ class Parser
23
+ attr_accessor :doc_manager, :markdown_converter, :wikilinks, :typed_link_blocks
24
+
25
+ # Use Jekyll's native relative_url filter
26
+ include Jekyll::Filters::URLFilters
27
+
28
+ def initialize(context, markdown_converter, doc_manager)
29
+ @context ||= context
30
+ @doc_manager ||= doc_manager
31
+ @markdown_converter ||= markdown_converter
32
+ @wikilinks, @typed_link_blocks = [], []
33
+ end
34
+
35
+ def parse(doc_content)
36
+ @typed_link_blocks, @wikilinks = [], []
37
+ # process blocks
38
+ typed_link_block_matches = doc_content.scan(REGEX_TYPED_LINK_BLOCK)
39
+ if !typed_link_block_matches.nil? && typed_link_block_matches.size != 0
40
+ typed_link_block_matches.each do |wl_match|
41
+ typed_link_block_wikilink = WikiLink.new(
42
+ nil,
43
+ wl_match[0],
44
+ wl_match[1],
45
+ nil,
46
+ nil,
47
+ nil,
48
+ )
49
+ doc_content.gsub!(typed_link_block_wikilink.md_link_str, "")
50
+ @typed_link_blocks << typed_link_block_wikilink
51
+ end
52
+ end
53
+ # process inlines
54
+ wikilink_matches = doc_content.scan(REGEX_WIKI_LINKS)
55
+ if !wikilink_matches.nil? && wikilink_matches.size != 0
56
+ wikilink_matches.each do |wl_match|
57
+ @wikilinks << WikiLink.new(
58
+ wl_match[0],
59
+ wl_match[1],
60
+ wl_match[2],
61
+ wl_match[3],
62
+ wl_match[4],
63
+ wl_match[5],
64
+ )
65
+ end
66
+ end
67
+ # replace text
68
+ return if @wikilinks.nil?
69
+ @wikilinks.each do |wikilink|
70
+ doc_content.sub!(
71
+ wikilink.md_link_regex,
72
+ self.build_html(wikilink)
73
+ )
74
+ end
75
+ end
76
+
77
+ def build_html_embed(title, content, url)
78
+ # multi-line for readability
79
+ return [
80
+ "<div class=\"wiki-link-embed\">",
81
+ "<div class=\"wiki-link-embed-title\">",
82
+ "#{title}",
83
+ "</div>",
84
+ "<div class=\"wiki-link-embed-content\">",
85
+ "#{@markdown_converter.convert(content)}",
86
+ "</div>",
87
+ "<a class=\"wiki-link-embed-link\" href=\"#{url}\"></a>",
88
+ "</div>",
89
+ ].join("\n").gsub!("\n", "")
90
+ end
91
+
92
+ def build_html_img_embed(img_file)
93
+ "<p><span class=\"wiki-link-embed-image\"><img class=\"wiki-link-img\" src=\"#{relative_url(img_file.relative_path)}\"/></span></p>"
94
+ end
95
+
96
+ def build_html(wikilink)
97
+ if wikilink.is_img?
98
+ linked_doc = @doc_manager.get_image_by_bname(wikilink.filename)
99
+ if wikilink.embedded? && wikilink.is_img?
100
+ return build_html_img_embed(linked_doc)
101
+ end
102
+ end
103
+ linked_doc = @doc_manager.get_doc_by_fname(wikilink.filename)
104
+ if !linked_doc.nil?
105
+ link_type = wikilink.typed? ? " link-type #{wikilink.link_type}" : ""
106
+
107
+ # label
108
+ wikilink_inner_txt = wikilink.clean_label_txt if wikilink.labelled?
109
+
110
+ lnk_doc_rel_url = relative_url(linked_doc.url) if linked_doc&.url
111
+ # TODO not sure about downcase
112
+ fname_inner_txt = linked_doc['title'].downcase if wikilink_inner_txt.nil?
113
+
114
+ link_lvl = wikilink.describe['level']
115
+ if (link_lvl == "file")
116
+ wikilink_inner_txt = "#{fname_inner_txt}" if wikilink_inner_txt.nil?
117
+ return build_html_embed(
118
+ linked_doc['title'],
119
+ @doc_manager.get_doc_content(wikilink.filename),
120
+ lnk_doc_rel_url
121
+ ) if wikilink.embedded?
122
+ elsif (link_lvl == "header" && DocManager.doc_has_header?(linked_doc, wikilink.header_txt))
123
+ lnk_doc_rel_url += "\#" + wikilink.header_txt.downcase
124
+ wikilink_inner_txt = "#{fname_inner_txt} > #{wikilink.header_txt}" if wikilink_inner_txt.nil?
125
+ elsif (link_lvl == "block" && DocManager.doc_has_block_id?(linked_doc, wikilink.block_id))
126
+ lnk_doc_rel_url += "\#" + wikilink.block_id.downcase
127
+ wikilink_inner_txt = "#{fname_inner_txt} > ^#{wikilink.block_id}" if wikilink_inner_txt.nil?
128
+ else
129
+ return '<span title="Content not found." class="invalid-wiki-link">' + wikilink.md_link_str + '</span>'
130
+ end
131
+ return '<a class="wiki-link' + link_type + '" href="' + lnk_doc_rel_url + '">' + wikilink_inner_txt + '</a>'
132
+ else
133
+ return '<span title="Content not found." class="invalid-wiki-link">' + wikilink.md_link_str + '</span>'
134
+ end
135
+ end
136
+ end
137
+
138
+ # the wikilink class knows everything about the original markdown syntax and its semantic meaning
139
+ class WikiLink
140
+ attr_accessor :embed, :link_type, :filename, :header_txt, :block_id, :label_txt
141
+
142
+ FILENAME = "filename"
143
+ HEADER_TXT = "header_txt"
144
+ BLOCK_ID = "block_id"
145
+
146
+ # parameters ordered by appearance in regex
147
+ def initialize(embed, link_type, filename, header_txt, block_id, label_txt)
148
+ # super(embed, link_type, filename, header_txt, block_id, label_txt)
149
+ @embed ||= embed
150
+ @link_type ||= link_type
151
+ @filename ||= filename
152
+ @header_txt ||= header_txt
153
+ @block_id ||= block_id
154
+ @label_txt ||= label_txt
155
+ end
156
+
157
+ # labeles are really flexible, so we need to handle them with a bit more care
158
+ def clean_label_txt
159
+ return @label_txt.sub("[", "\\[").sub("]", "\\]")
160
+ end
161
+
162
+ def md_link_str
163
+ embed = embedded? ? "!" : ""
164
+ link_type = typed? ? "#{@link_type}::" : ""
165
+ filename = described?(FILENAME) ? @filename : ""
166
+ if described?(HEADER_TXT)
167
+ header = "\##{@header_txt}"
168
+ block = ""
169
+ elsif described?(BLOCK_ID)
170
+ header = ""
171
+ block = "\#\^#{@block_id}"
172
+ elsif !described?(FILENAME)
173
+ Jekyll.logger.error "Invalid link level in 'md_link_str'. See WikiLink's 'md_link_str' for details"
174
+ end
175
+ label_ = labelled? ? "\|#{@label_txt}" : ""
176
+ return "#{embed}#{link_type}\[\[#{filename}#{header}#{block}#{label_}\]\]"
177
+ end
178
+
179
+ def md_link_regex
180
+ regex_embed = embedded? ? REGEX_LINK_EMBED : %r{}
181
+ regex_link_type = typed? ? %r{#{@link_type}#{REGEX_LINK_TYPE}} : %r{}
182
+ filename = described?(FILENAME) ? @filename : ""
183
+ if described?(HEADER_TXT)
184
+ header = %r{#{REGEX_LINK_HEADER}#{@header_txt}}
185
+ block = %r{}
186
+ elsif described?(BLOCK_ID)
187
+ header = %r{}
188
+ block = %r{#{REGEX_LINK_BLOCK}#{@block_id}}
189
+ elsif !described?(FILENAME)
190
+ Jekyll.logger.error "Invalid link level in regex. See WikiLink's 'md_link_regex' for details"
191
+ end
192
+ label_ = labelled? ? %r{#{REGEX_LINK_LABEL}#{clean_label_txt}} : %r{}
193
+ return %r{#{regex_embed}#{regex_link_type}\[\[#{filename}#{header}#{block}#{label_}\]\]}
194
+ end
195
+
196
+ def describe
197
+ return {
198
+ 'level' => level,
199
+ 'labelled' => labelled?,
200
+ 'embedded' => embedded?,
201
+ 'typed_link' => typed?,
202
+ }
203
+ end
204
+
205
+ def labelled?
206
+ return !@label_txt.nil? && !@label_txt.empty?
207
+ end
208
+
209
+ def typed?
210
+ return !@link_type.nil? && !@link_type.empty?
211
+ end
212
+
213
+ def embedded?
214
+ return !@embed.nil? && @embed == "!"
215
+ end
216
+
217
+ def is_img?
218
+ # github supported image formats: https://docs.github.com/en/github/managing-files-in-a-repository/working-with-non-code-files/rendering-and-diffing-images
219
+ return SUPPORTED_IMG_FORMATS.any?{ |ext| ext == File.extname(@filename).downcase }
220
+ end
221
+
222
+ def described?(chunk)
223
+ return (!@filename.nil? && !@filename.empty?) if chunk == FILENAME
224
+ return (!@header_txt.nil? && !@header_txt.empty?) if chunk == HEADER_TXT
225
+ return (!@block_id.nil? && !@block_id.empty?) if chunk == BLOCK_ID
226
+ Jekyll.logger.error "There is no link level '#{chunk}' in WikiLink Struct"
227
+ end
228
+
229
+ def level
230
+ return "file" if described?(FILENAME) && !described?(HEADER_TXT) && !described?(BLOCK_ID)
231
+ return "header" if described?(FILENAME) && described?(HEADER_TXT) && !described?(BLOCK_ID)
232
+ return "block" if described?(FILENAME) && !described?(HEADER_TXT) && described?(BLOCK_ID)
233
+ return "invalid"
234
+ end
235
+ end
236
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JekyllWikiLinks
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.4"
5
5
  end
metadata CHANGED
@@ -1,32 +1,37 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-wikilinks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
- - shorty25h0r7
7
+ - manunamz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-21 00:00:00.000000000 Z
11
+ date: 2021-06-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
15
- - short2thingz@gmail.com
15
+ - manunamz@pm.me
16
16
  executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - lib/jekyll-wikilinks.rb
21
21
  - lib/jekyll-wikilinks/context.rb
22
+ - lib/jekyll-wikilinks/doc_manager.rb
22
23
  - lib/jekyll-wikilinks/filter.rb
24
+ - lib/jekyll-wikilinks/jekyll_patch.rb
25
+ - lib/jekyll-wikilinks/link_index.rb
26
+ - lib/jekyll-wikilinks/naming_const.rb
27
+ - lib/jekyll-wikilinks/parser.rb
23
28
  - lib/jekyll-wikilinks/version.rb
24
- homepage: https://github.com/shorty25h0r7/jekyll-wikilinks
29
+ homepage: https://github.com/manunamz/jekyll-wikilinks
25
30
  licenses:
26
31
  - MIT
27
32
  metadata:
28
- homepage_uri: https://github.com/shorty25h0r7/jekyll-wikilinks
29
- source_code_uri: https://github.com/shorty25h0r7/jekyll-wikilinks
33
+ homepage_uri: https://github.com/manunamz/jekyll-wikilinks
34
+ source_code_uri: https://github.com/manunamz/jekyll-wikilinks
30
35
  post_install_message:
31
36
  rdoc_options: []
32
37
  require_paths:
@@ -45,5 +50,5 @@ requirements: []
45
50
  rubygems_version: 3.2.17
46
51
  signing_key:
47
52
  specification_version: 4
48
- summary: Add support for [[wikilinks]] (in markdown).
53
+ summary: Add jekyll support for [[wikilinks]] (in markdown).
49
54
  test_files: []