jekyll-wikirefs 0.0.1 → 0.0.14
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/lib/jekyll-wikirefs/config.rb +113 -0
- data/lib/jekyll-wikirefs/patch/context.rb +19 -0
- data/lib/jekyll-wikirefs/patch/doc_manager.rb +117 -0
- data/lib/jekyll-wikirefs/patch/site.rb +12 -0
- data/lib/jekyll-wikirefs/plugins/converter.rb +53 -0
- data/lib/jekyll-wikirefs/plugins/filter.rb +81 -0
- data/lib/jekyll-wikirefs/plugins/generator.rb +41 -0
- data/lib/jekyll-wikirefs/util/link_index.rb +99 -0
- data/lib/jekyll-wikirefs/util/parser.rb +182 -0
- data/lib/jekyll-wikirefs/util/regex.rb +72 -0
- data/lib/jekyll-wikirefs/util/wikiref.rb +312 -0
- data/lib/jekyll-wikirefs/version.rb +1 -1
- data/lib/jekyll-wikirefs.rb +24 -4
- metadata +33 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ffaf29b4a348e3036a6508d41df6cc915ae3459a2afb250a4c778242f4dff69
|
4
|
+
data.tar.gz: 939fa037711ee9f265ee73edc709f34264188f71dca4f7d7e8f972f97be9eb3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97d3144acc57f975dfb1fb6799bc90df9c93de66b9b1bca5f2c6cfc34a5fe4b24316830d87410a90e5ba867516f68326dc9e4de688098e879e5cfd50f2ab0e33
|
7
|
+
data.tar.gz: 01c27427d75cb71a22965f91f890deca40fbfa8955560aea6fa01518d02343da53a26c927389624f12d771d574127383af61a7f65255a7111d1d1ef7b3d5b90f
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "jekyll"
|
3
|
+
|
4
|
+
module Jekyll
|
5
|
+
module WikiRefs
|
6
|
+
|
7
|
+
class PluginConfig
|
8
|
+
|
9
|
+
ATTR_KEY = "attributes"
|
10
|
+
CONFIG_KEY = "wikirefs"
|
11
|
+
ENABLED_KEY = "enabled"
|
12
|
+
EXCLUDE_KEY = "exclude"
|
13
|
+
# css-related
|
14
|
+
CSS_KEY = "css"
|
15
|
+
NAME_KEY = "name"
|
16
|
+
# names
|
17
|
+
## valid
|
18
|
+
TYPED_KEY = "typed"
|
19
|
+
WEB_KEY = "web"
|
20
|
+
WIKI_KEY = "wiki"
|
21
|
+
## invalid
|
22
|
+
INV_WIKI_KEY = "invalid_wiki"
|
23
|
+
# INV_WEB_KEY = "invalid_web"
|
24
|
+
## embed
|
25
|
+
EMBED_WRAPPER_KEY = "embed_wrapper"
|
26
|
+
EMBED_TITLE_KEY = "embed_title"
|
27
|
+
EMBED_CONTENT_KEY = "embed_content"
|
28
|
+
EMBED_LINK_KEY = "embed_wiki_link"
|
29
|
+
EMBED_IMG_WRAPPER_KEY = "embed_image_wrapper"
|
30
|
+
EMBED_IMG_KEY = "embed_image"
|
31
|
+
|
32
|
+
def initialize(config)
|
33
|
+
@config ||= config
|
34
|
+
self.old_config_warn()
|
35
|
+
Jekyll.logger.debug("Jekyll-WikiRefs: Excluded jekyll types: #{option(EXCLUDE_KEY)}") unless disabled?
|
36
|
+
end
|
37
|
+
|
38
|
+
# util
|
39
|
+
|
40
|
+
def css_name(name_key)
|
41
|
+
return option_css_name(name_key) if option_css_name(name_key)
|
42
|
+
return "typed" if name_key == TYPED_KEY
|
43
|
+
# valid
|
44
|
+
return "wiki-link" if name_key == WIKI_KEY
|
45
|
+
# invalid
|
46
|
+
return "invalid-wiki-link" if name_key == INV_WIKI_KEY
|
47
|
+
# return "invalid-web-link" if name_key == INV_WEB_KEY
|
48
|
+
# embeds
|
49
|
+
return "embed-wrapper" if name_key == EMBED_WRAPPER_KEY
|
50
|
+
return "embed-title" if name_key == EMBED_TITLE_KEY
|
51
|
+
return "embed-content" if name_key == EMBED_CONTENT_KEY
|
52
|
+
return "embed-wiki-link" if name_key == EMBED_LINK_KEY
|
53
|
+
# img
|
54
|
+
return "embed-image-wrapper" if name_key == EMBED_IMG_WRAPPER_KEY
|
55
|
+
return "embed-image" if name_key == EMBED_IMG_KEY
|
56
|
+
end
|
57
|
+
|
58
|
+
def disabled?
|
59
|
+
option(ENABLED_KEY) == false
|
60
|
+
end
|
61
|
+
|
62
|
+
def disabled_attributes?
|
63
|
+
option_attributes(ENABLED_KEY) == false
|
64
|
+
end
|
65
|
+
|
66
|
+
def exclude?(type)
|
67
|
+
return false unless option(EXCLUDE_KEY)
|
68
|
+
return option(EXCLUDE_KEY).include?(type.to_s)
|
69
|
+
end
|
70
|
+
|
71
|
+
def excluded_css_names
|
72
|
+
return self.option_css(EXCLUDE_KEY)
|
73
|
+
end
|
74
|
+
|
75
|
+
# options
|
76
|
+
|
77
|
+
def option(key)
|
78
|
+
@config[CONFIG_KEY] && @config[CONFIG_KEY][key]
|
79
|
+
end
|
80
|
+
|
81
|
+
def option_attributes(key)
|
82
|
+
@config[CONFIG_KEY] && @config[CONFIG_KEY][ATTR_KEY] && @config[CONFIG_KEY][ATTR_KEY][key]
|
83
|
+
end
|
84
|
+
|
85
|
+
def option_css(key)
|
86
|
+
@config[CONFIG_KEY] && @config[CONFIG_KEY][CSS_KEY] && @config[CONFIG_KEY][CSS_KEY][key]
|
87
|
+
end
|
88
|
+
|
89
|
+
def option_css_name(key)
|
90
|
+
option_css(NAME_KEY) && @config[CONFIG_KEY][CSS_KEY][NAME_KEY][key]
|
91
|
+
end
|
92
|
+
|
93
|
+
# !! deprecated !!
|
94
|
+
|
95
|
+
def option_exist?(key)
|
96
|
+
@config[CONFIG_KEY] && @config[CONFIG_KEY].include?(key)
|
97
|
+
end
|
98
|
+
|
99
|
+
def old_config_warn()
|
100
|
+
if @config.include?("wikilinks_collection")
|
101
|
+
Jekyll.logger.warn("Jekyll-WikiRefs: As of 0.0.3, 'wikilinks_collection' is no longer used for configs. Jekyll-WikiRefs will scan all markdown files by default. Check README for details.")
|
102
|
+
end
|
103
|
+
if option_exist?("assets_rel_path")
|
104
|
+
Jekyll.logger.warn("Jekyll-WikiRefs: As of 0.0.5, 'assets_rel_path' is now 'path'.")
|
105
|
+
end
|
106
|
+
if @config.include?("d3_graph_data")
|
107
|
+
Jekyll.logger.warn("Jekyll-WikiRefs: As of 0.0.6, 'd3_graph_data' and graph functionality have been moved to the 'jekyll-graph' plugin.")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "../util/regex"
|
3
|
+
|
4
|
+
module Jekyll
|
5
|
+
module WikiRefs
|
6
|
+
|
7
|
+
# todo: move these methods to the 'WikiRef' classes...?
|
8
|
+
#
|
9
|
+
# this class is responsible for answering any questions
|
10
|
+
# related to jekyll markdown documents
|
11
|
+
# that are meant to be processed by the wikirefs plugin.
|
12
|
+
#
|
13
|
+
# the following methods are specifically to address two things:
|
14
|
+
# 1. ruby's 'find' / 'detect' function does not throw errors if
|
15
|
+
# there are multiple matches. fail fast, i want to know if there
|
16
|
+
# are duplicates.
|
17
|
+
# (not using sets because i don't want to clobber existing documents)
|
18
|
+
# 2. handle all jekyll documents in one place. i don't want to
|
19
|
+
# have to filter all documents for target markdown documents
|
20
|
+
# every time i need to check if a file exists.
|
21
|
+
#
|
22
|
+
# there is probably a better way to do this...i would prefer to have
|
23
|
+
# a plugin-wide function that just wraps all of this and can be called
|
24
|
+
# from anywhere in the plugin...but ruby is not a functional language...
|
25
|
+
# gotta have classes...
|
26
|
+
#
|
27
|
+
class DocManager
|
28
|
+
CONVERTER_CLASS = Jekyll::Converters::Markdown
|
29
|
+
|
30
|
+
def initialize(site)
|
31
|
+
return if $wiki_conf.disabled?
|
32
|
+
|
33
|
+
markdown_converter = site.find_converter_instance(CONVERTER_CLASS)
|
34
|
+
# filter docs based on configs
|
35
|
+
docs = []
|
36
|
+
docs += site.pages if !$wiki_conf.exclude?(:pages)
|
37
|
+
docs += site.docs_to_write.filter { |d| !$wiki_conf.exclude?(d.type) }
|
38
|
+
@md_docs = docs.filter { |doc| markdown_converter.matches(doc.extname) }
|
39
|
+
if @md_docs.nil? || @md_docs.empty?
|
40
|
+
Jekyll.logger.warn("Jekyll-WikiRefs: No documents to process.")
|
41
|
+
end
|
42
|
+
|
43
|
+
@static_files ||= site.static_files
|
44
|
+
end
|
45
|
+
|
46
|
+
# accessors
|
47
|
+
|
48
|
+
def all
|
49
|
+
return @md_docs
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_doc_by_fname(filename)
|
53
|
+
Jekyll.logger.error("Jekyll-WikiRefs: Must provide a 'filename'") if filename.nil? || filename.empty?
|
54
|
+
docs = @md_docs.select{ |d| File.basename(d.basename, File.extname(d.basename)) == filename }
|
55
|
+
return nil if docs.nil? || docs.empty? || docs.size > 1
|
56
|
+
return docs[0]
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_doc_by_fpath(file_path)
|
60
|
+
Jekyll.logger.error("Jekyll-WikiRefs: Must provide a 'file_path'") if file_path.nil? || file_path.empty?
|
61
|
+
docs = @md_docs.select{ |d| d.relative_path == (file_path + ".md") }
|
62
|
+
return nil if docs.nil? || docs.empty? || docs.size > 1
|
63
|
+
return docs[0]
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_doc_by_url(url)
|
67
|
+
Jekyll.logger.error("Jekyll-WikiRefs: Must provide a 'url'") if url.nil? || url.empty?
|
68
|
+
docs = @md_docs.select{ |d| d.url == url }
|
69
|
+
return nil if docs.nil? || docs.empty? || docs.size > 1
|
70
|
+
return docs[0]
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_doc_content(filename)
|
74
|
+
doc = self.get_doc_by_fname(filename)
|
75
|
+
return nil if docs.nil?
|
76
|
+
return doc.content
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_image_by_fname(filename)
|
80
|
+
Jekyll.logger.error("Jekyll-WikiRefs: Must provide a 'filename'") if filename.nil? || filename.empty?
|
81
|
+
return nil if @static_files.size == 0 || !SUPPORTED_IMG_FORMATS.any?{ |ext| ext == File.extname(filename).downcase }
|
82
|
+
docs = @static_files.select{ |d| File.basename(d.relative_path) == filename }
|
83
|
+
return nil if docs.nil? || docs.empty? || docs.size > 1
|
84
|
+
return docs[0]
|
85
|
+
end
|
86
|
+
|
87
|
+
# validators
|
88
|
+
|
89
|
+
def file_exists?(filename, file_path=nil)
|
90
|
+
Jekyll.logger.error("Jekyll-WikiRefs: Must provide a 'filename'") if filename.nil? || filename.empty?
|
91
|
+
if file_path.nil?
|
92
|
+
return false if get_doc_by_fname(filename).nil? && get_image_by_fname(filename).nil?
|
93
|
+
return true
|
94
|
+
else
|
95
|
+
return false if get_doc_by_fpath(file_path).nil?
|
96
|
+
return true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def doc_has_header?(doc, header)
|
101
|
+
Jekyll.logger.error("Jekyll-WikiRefs: Must provide a 'header'") if header.nil? || header.empty?
|
102
|
+
# leading + trailing whitespace is ignored when matching headers
|
103
|
+
header_results = doc.content.scan(REGEX_ATX_HEADER).flatten.map { |htxt| htxt.downcase.strip }
|
104
|
+
setext_header_results = doc.content.scan(REGEX_SETEXT_HEADER).flatten.map { |htxt| htxt.downcase.strip }
|
105
|
+
return header_results.include?(header.downcase.strip) || setext_header_results.include?(header.downcase.strip)
|
106
|
+
end
|
107
|
+
|
108
|
+
def doc_has_block_id?(doc, block_id)
|
109
|
+
Jekyll.logger.error("Jekyll-WikiRefs: Must provide a 'block_id'") if block_id.nil? || block_id.empty?
|
110
|
+
# leading + trailing whitespace is ignored when matching blocks
|
111
|
+
block_id_results = doc.content.scan(REGEX_BLOCK).flatten.map { |bid| bid.strip }
|
112
|
+
return block_id_results.include?(block_id)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "jekyll"
|
2
|
+
require "nokogiri"
|
3
|
+
|
4
|
+
module Jekyll
|
5
|
+
module WikiRefs
|
6
|
+
|
7
|
+
class WebLinkConverter < Jekyll::Converter
|
8
|
+
priority :low
|
9
|
+
|
10
|
+
# config
|
11
|
+
CSS_KEY = "css"
|
12
|
+
CONFIG_KEY = "wikirefs"
|
13
|
+
EXCLUDE_KEY = "exclude"
|
14
|
+
# link types
|
15
|
+
# WEB_KEY = "web"
|
16
|
+
# WIKIL_KEY = "wiki"
|
17
|
+
# INVALID_KEY = "invalid"
|
18
|
+
# WIKI_EMBED_KEY = "wiki_embed"
|
19
|
+
|
20
|
+
def matches(ext)
|
21
|
+
ext =~ /^\.md$/i
|
22
|
+
end
|
23
|
+
|
24
|
+
def output_ext(ext)
|
25
|
+
".html"
|
26
|
+
end
|
27
|
+
|
28
|
+
# add 'web-link' css class to links that aren't
|
29
|
+
# - wikilinks
|
30
|
+
# - contain an excluded css class
|
31
|
+
def convert(content)
|
32
|
+
excluded_classes = option_css(EXCLUDE_KEY)
|
33
|
+
if excluded_classes.nil? || excluded_classes.empty?
|
34
|
+
css_def = "a:not(.#{$wiki_conf.css_name("wiki")}):not(.#{$wiki_conf.css_name("embed_wiki_link")})"
|
35
|
+
else
|
36
|
+
css_def = "a:not(.#{$wiki_conf.css_name("wiki")}):not(.#{$wiki_conf.css_name("embed_wiki_link")}):not(.#{excluded_classes.join("):not(.")})"
|
37
|
+
end
|
38
|
+
parsed_content = Nokogiri::HTML::fragment(content)
|
39
|
+
parsed_content.css(css_def).each do |link|
|
40
|
+
link.add_class('web-link')
|
41
|
+
end
|
42
|
+
content = parsed_content.to_html
|
43
|
+
end
|
44
|
+
|
45
|
+
# config helpers
|
46
|
+
|
47
|
+
def option_css(key)
|
48
|
+
@config[CONFIG_KEY] && @config[CONFIG_KEY][CSS_KEY] && @config[CONFIG_KEY][CSS_KEY][key]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module WikiRefs
|
5
|
+
|
6
|
+
module TypeFilters
|
7
|
+
# 'links' accepts untyped links, typed links, and attributes; fore and back.
|
8
|
+
# why: these filters are useful when you want to list backlinks of certain type(s) and don't want type mismatches to display as "missing"
|
9
|
+
|
10
|
+
# usage: {% assign note_links = page.links | doc_type: "notes" %}
|
11
|
+
def doc_type(links, doc_type)
|
12
|
+
Jekyll.logger.error("Jekyll-WikiRefs: 'links' invalid") if links.nil?
|
13
|
+
Jekyll.logger.error("Jekyll-WikiRefs: 'doc_type' invalid") if doc_type.nil? || doc_type.empty?
|
14
|
+
return [] if links.empty?
|
15
|
+
|
16
|
+
site = @context.registers[:site]
|
17
|
+
|
18
|
+
links_of_type = []
|
19
|
+
links.each do |l|
|
20
|
+
# links
|
21
|
+
if l.keys.include?('url')
|
22
|
+
docs = site.documents.select{ |d| d.url == l['url'] && d.type.to_s == doc_type.to_s }
|
23
|
+
if !docs.nil? && docs.size == 1
|
24
|
+
links_of_type << l
|
25
|
+
end
|
26
|
+
# attributes
|
27
|
+
elsif l.keys.include?('urls')
|
28
|
+
l['urls'].each do |lurl|
|
29
|
+
docs = site.documents.select{ |d| d.url == lurl && d.type.to_s == doc_type.to_s }
|
30
|
+
if !docs.nil? && docs.size == 1
|
31
|
+
links_of_type << l
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
Jekyll.logger.error("Jekyll-WikiRefs: In 'doc_type' filter, 'links' do not have 'url' or 'urls'")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
return links_of_type.uniq
|
39
|
+
end
|
40
|
+
|
41
|
+
# usage: {% assign author_links = page.links | link_type: "author" %}
|
42
|
+
def link_type(links, link_type)
|
43
|
+
Jekyll.logger.error("Jekyll-WikiRefs: 'links' invalid") if links.nil?
|
44
|
+
Jekyll.logger.error("Jekyll-WikiRefs: 'link_type' invalid") if link_type.nil?
|
45
|
+
return [] if links.empty?
|
46
|
+
|
47
|
+
site = @context.registers[:site]
|
48
|
+
|
49
|
+
links_of_type = []
|
50
|
+
links.each do |l|
|
51
|
+
if l['type'].to_s == link_type.to_s
|
52
|
+
# links
|
53
|
+
if l.keys.include?('url')
|
54
|
+
docs = site.documents.select{ |d| d.url == l['url'] }
|
55
|
+
if !doc.nil? && docs.size != 1
|
56
|
+
links_of_type << l
|
57
|
+
end
|
58
|
+
# attributes
|
59
|
+
elsif l.keys.include?('urls')
|
60
|
+
all_docs_exist = true
|
61
|
+
l['urls'].each do |lurl|
|
62
|
+
docs = site.documents.select{ |d| d.url == lurl }
|
63
|
+
if !docs.nil? && docs.size != 1
|
64
|
+
all_docs_exist = false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
if all_docs_exist
|
68
|
+
links_of_type << l
|
69
|
+
end
|
70
|
+
else
|
71
|
+
Jekyll.logge.error("Jekyll-WikiRefs: In 'link_type' filter, 'links' do not have 'url' or 'urls'")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
return links_of_type.uniq
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "jekyll"
|
3
|
+
|
4
|
+
require_relative "../patch/context"
|
5
|
+
require_relative "../patch/doc_manager"
|
6
|
+
require_relative "../patch/site"
|
7
|
+
require_relative "../util/link_index"
|
8
|
+
require_relative "../util/parser"
|
9
|
+
|
10
|
+
module Jekyll
|
11
|
+
module WikiRefs
|
12
|
+
|
13
|
+
class Generator < Jekyll::Generator
|
14
|
+
|
15
|
+
def generate(site)
|
16
|
+
return if $wiki_conf.disabled?
|
17
|
+
|
18
|
+
@site ||= site
|
19
|
+
@context ||= Jekyll::WikiRefs::Context.new(site)
|
20
|
+
|
21
|
+
# setup helper classes
|
22
|
+
@parser = Parser.new(@site)
|
23
|
+
@site.link_index = LinkIndex.new(@site)
|
24
|
+
|
25
|
+
@site.doc_mngr.all.each do |doc|
|
26
|
+
filename = File.basename(doc.basename, File.extname(doc.basename))
|
27
|
+
@parser.parse(filename, doc.content)
|
28
|
+
@site.link_index.populate(doc, @parser.wikilink_blocks, @parser.wikilink_inlines)
|
29
|
+
end
|
30
|
+
# wait until all docs are processed before assigning backward facing metadata,
|
31
|
+
# this ensures all attributed/backlinks are collected for assignment
|
32
|
+
@site.doc_mngr.all.each do |doc|
|
33
|
+
# populate frontmatter metadata from (wiki)link index
|
34
|
+
@site.link_index.assign_metadata(doc)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require_relative 'regex'
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module WikiRefs
|
5
|
+
|
6
|
+
class LinkIndex
|
7
|
+
attr_reader :index
|
8
|
+
|
9
|
+
def initialize(site)
|
10
|
+
@baseurl = site.baseurl
|
11
|
+
@index = {}
|
12
|
+
site.doc_mngr.all.each do |doc|
|
13
|
+
@index[doc.url] = DocLinks.new()
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def assign_metadata(doc)
|
18
|
+
doc.data['attributed'] = @index[doc.url].attributed.uniq
|
19
|
+
doc.data['attributes'] = @index[doc.url].attributes.uniq
|
20
|
+
doc.data['backlinks'] = @index[doc.url].backlinks.uniq
|
21
|
+
doc.data['forelinks'] = @index[doc.url].forelinks.uniq
|
22
|
+
doc.data['missing'] = @index[doc.url].missing.uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
def populate(doc, wikilink_blocks, wikilink_inlines)
|
26
|
+
# #
|
27
|
+
# blocks #
|
28
|
+
# #
|
29
|
+
wikilink_blocks.each do |wlbl|
|
30
|
+
if wlbl.is_valid?
|
31
|
+
#
|
32
|
+
# attributes
|
33
|
+
#
|
34
|
+
target_attr = @index[doc.url].attributes.detect { |atr| atr['type'] == wlbl.link_type }
|
35
|
+
# create
|
36
|
+
if target_attr.nil?
|
37
|
+
@index[doc.url].attributes << wlbl.linked_fm_data
|
38
|
+
# append
|
39
|
+
else
|
40
|
+
target_attr['urls'] += wlbl.urls
|
41
|
+
end
|
42
|
+
## append missing docs
|
43
|
+
@index[doc.url].missing += wlbl.missing_doc_filenames
|
44
|
+
#
|
45
|
+
# attributed
|
46
|
+
#
|
47
|
+
wlbl.linked_docs.each do |linked_doc|
|
48
|
+
target_attr = @index[linked_doc.url].attributed.detect { |atr| atr['type'] == wlbl.link_type }
|
49
|
+
# create
|
50
|
+
if target_attr.nil?
|
51
|
+
@index[linked_doc.url].attributed << wlbl.context_fm_data
|
52
|
+
# append
|
53
|
+
else
|
54
|
+
target_attr['urls'] << doc.url
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
#
|
59
|
+
# invalid || empty
|
60
|
+
#
|
61
|
+
@index[doc.url].missing += wlbl.missing_doc_filenames
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# #
|
65
|
+
# inlines #
|
66
|
+
# #
|
67
|
+
wikilink_inlines.each do |wlil|
|
68
|
+
return if wlil.is_img?
|
69
|
+
if wlil.is_valid?
|
70
|
+
# forelink
|
71
|
+
@index[doc.url].forelinks << wlil.linked_fm_data
|
72
|
+
# backlink
|
73
|
+
@index[wlil.linked_doc.url].backlinks << wlil.context_fm_data
|
74
|
+
else
|
75
|
+
@index[doc.url].missing << wlil.filename
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# def remove_baseurl(url)
|
81
|
+
# return url.gsub(@baseurl, '') if !@baseurl.nil?
|
82
|
+
# return url
|
83
|
+
# end
|
84
|
+
|
85
|
+
class DocLinks
|
86
|
+
attr_accessor :attributes, :attributed, :backlinks, :forelinks, :missing
|
87
|
+
|
88
|
+
def initialize
|
89
|
+
@attributed = [] # block typed backlinks; { 'type' => str, 'urls' => [ str ] }
|
90
|
+
@attributes = [] # block typed forelinks; { 'type' => str, 'urls' => [ str ] }
|
91
|
+
@backlinks = [] # inline typed and basic backlinks; { 'type' => str, 'url' => str }
|
92
|
+
@forelinks = [] # inline typed and basic forelinks; { 'type' => str, 'url' => str }
|
93
|
+
@missing = [] # missing forelinks + attributes; ( built from (missing) filenames )
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require "nokogiri"
|
2
|
+
require_relative "regex"
|
3
|
+
require_relative "wikiref"
|
4
|
+
|
5
|
+
module Jekyll
|
6
|
+
module WikiRefs
|
7
|
+
|
8
|
+
# more of a "parser" than a parser
|
9
|
+
class Parser
|
10
|
+
attr_accessor :doc_manager, :markdown_converter, :wikilink_inlines, :wikilink_blocks
|
11
|
+
|
12
|
+
# Use Jekyll's native relative_url filter
|
13
|
+
include Jekyll::Filters::URLFilters
|
14
|
+
|
15
|
+
CONVERTER_CLASS = Jekyll::Converters::Markdown
|
16
|
+
|
17
|
+
def initialize(site)
|
18
|
+
@context ||= Jekyll::WikiRefs::Context.new(site)
|
19
|
+
# do not use @dm in parser -- it is only meant to be passed down into wikilink classes.
|
20
|
+
@doc_manager ||= site.doc_mngr
|
21
|
+
@markdown_converter ||= site.find_converter_instance(CONVERTER_CLASS)
|
22
|
+
@wikilink_blocks, @wikilink_inlines = [], []
|
23
|
+
end
|
24
|
+
|
25
|
+
# parsing
|
26
|
+
|
27
|
+
def parse(doc_filename, doc_content)
|
28
|
+
@wikilink_blocks, @wikilink_inlines = [], []
|
29
|
+
if !$wiki_conf.disabled_attributes?
|
30
|
+
self.parse_blocks(doc_filename, doc_content)
|
31
|
+
end
|
32
|
+
self.parse_inlines(doc_filename, doc_content)
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_blocks(doc_filename, doc_content)
|
36
|
+
block_matches = doc_content.scan(REGEX_WIKI_LINK_BLOCKS)
|
37
|
+
if !block_matches.nil? && block_matches.size != 0
|
38
|
+
block_matches.each do |w_match|
|
39
|
+
# init block wikilink
|
40
|
+
wikilink_block = WikiLinkBlock.new(
|
41
|
+
@doc_manager,
|
42
|
+
doc_filename,
|
43
|
+
w_match[0], # link_type
|
44
|
+
w_match[2], # bullet_type
|
45
|
+
)
|
46
|
+
# extract + add filenames
|
47
|
+
items = w_match[1]
|
48
|
+
filename_matches = items.scan(/#{REGEX_LINK_LEFT}#{REGEX_FILENAME}#{REGEX_LINK_RIGHT}/i)
|
49
|
+
filename_matches.each do |match|
|
50
|
+
match.each do |fname|
|
51
|
+
wikilink_block.add_item(fname)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
# replace text
|
55
|
+
doc_content.gsub!(wikilink_block.md_regex, "\n")
|
56
|
+
@wikilink_blocks << wikilink_block
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_inlines(doc_filename, doc_content)
|
62
|
+
inline_matches = doc_content.scan(REGEX_WIKI_LINK_INLINES)
|
63
|
+
if !inline_matches.nil? && inline_matches.size != 0
|
64
|
+
inline_matches.each do |w_match|
|
65
|
+
@wikilink_inlines << WikiLinkInline.new(
|
66
|
+
@doc_manager,
|
67
|
+
doc_filename,
|
68
|
+
w_match[0],
|
69
|
+
w_match[1],
|
70
|
+
w_match[2],
|
71
|
+
w_match[3],
|
72
|
+
w_match[4],
|
73
|
+
w_match[5],
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
# replace text
|
78
|
+
return if @wikilink_inlines.nil?
|
79
|
+
# process typed wikilinks first so we don't accidentally
|
80
|
+
# overwrite them when handling untyped wikilinks
|
81
|
+
self.sort_for_replacement
|
82
|
+
@wikilink_inlines.each do |wikilink|
|
83
|
+
doc_content.gsub!(
|
84
|
+
wikilink.md_regex,
|
85
|
+
self.build_html(wikilink)
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# building/converting
|
91
|
+
|
92
|
+
def build_html_embed(title, content, url)
|
93
|
+
# multi-line for readability
|
94
|
+
return [
|
95
|
+
"<div class=\"#{$wiki_conf.css_name("embed_wrapper")}\">",
|
96
|
+
"<div class=\"#{$wiki_conf.css_name("embed_title")}\">",
|
97
|
+
"#{title}",
|
98
|
+
"</div>",
|
99
|
+
"<div class=\"#{$wiki_conf.css_name("embed_content")}\">",
|
100
|
+
"#{@markdown_converter.convert(content)}",
|
101
|
+
"</div>",
|
102
|
+
"<a class=\"#{$wiki_conf.css_name("embed_wiki_link")}\" href=\"#{url}\"></a>",
|
103
|
+
"</div>",
|
104
|
+
].join("\n").gsub!("\n", "")
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_html_img_embed(static_doc, is_svg=false)
|
108
|
+
svg_content = ""
|
109
|
+
if is_svg
|
110
|
+
File.open(static_doc.path, "r") do |svg_img|
|
111
|
+
svg_content = svg_img.read
|
112
|
+
end
|
113
|
+
return "<p><span class=\"#{$wiki_conf.css_name("embed_image_wrapper")}\">#{svg_content}</span></p>"
|
114
|
+
else
|
115
|
+
return "<p><span class=\"#{$wiki_conf.css_name("embed_image_wrapper")}\"><img class=\"#{$wiki_conf.css_name("embed_image")}\" src=\"#{relative_url(static_doc.relative_path)}\"></span></p>"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def build_html(wikilink)
|
120
|
+
if !wikilink.is_valid?
|
121
|
+
return '<span class="' + $wiki_conf.css_name("invalid_wiki") + '">' + wikilink.md_str + '</span>'
|
122
|
+
end
|
123
|
+
# image processing
|
124
|
+
if wikilink.embedded? && wikilink.is_img?
|
125
|
+
return build_html_img_embed(wikilink.linked_img, is_svg=wikilink.is_img_svg?)
|
126
|
+
end
|
127
|
+
# markdown file processing
|
128
|
+
linked_doc = wikilink.linked_doc
|
129
|
+
link_type_txt = wikilink.is_typed? ? " #{$wiki_conf.css_name("typed")} #{wikilink.link_type}" : ""
|
130
|
+
lnk_doc_rel_url = relative_url(linked_doc.url)
|
131
|
+
if wikilink.labelled?
|
132
|
+
inner_txt = wikilink.label_txt
|
133
|
+
elsif linked_doc.data.keys.include?('title')
|
134
|
+
inner_txt = linked_doc.data['title'].downcase
|
135
|
+
# in case there is no 'title' frontmatter attribute
|
136
|
+
# (i'm seeing deprecation warnings, but there might
|
137
|
+
# be bugs caused by not using this...)
|
138
|
+
elsif linked_doc.respond_to?(:title)
|
139
|
+
inner_txt = linked_doc.title.downcase
|
140
|
+
# pages don't have automatically generated titles
|
141
|
+
else
|
142
|
+
inner_txt = Jekyll::Utils.slugify(linked_doc.basename)
|
143
|
+
end
|
144
|
+
# level-specific
|
145
|
+
if (wikilink.level == "file_path" || wikilink.level == "filename")
|
146
|
+
return build_html_embed(
|
147
|
+
linked_doc.title,
|
148
|
+
linked_doc.content,
|
149
|
+
lnk_doc_rel_url
|
150
|
+
) if wikilink.embedded?
|
151
|
+
elsif (wikilink.level == "header")
|
152
|
+
# from: https://github.com/jekyll/jekyll/blob/6855200ebda6c0e33f487da69e4e02ec3d8286b7/Rakefile#L74
|
153
|
+
lnk_doc_rel_url += "\#" + Jekyll::Utils.slugify(wikilink.header_txt)
|
154
|
+
inner_txt += " > #{wikilink.header_txt.downcase}" if !wikilink.labelled?
|
155
|
+
elsif (wikilink.level == "block")
|
156
|
+
lnk_doc_rel_url += "\#" + wikilink.block_id
|
157
|
+
inner_txt += " > ^#{wikilink.block_id}" if !wikilink.labelled?
|
158
|
+
else
|
159
|
+
Jekyll.logger.error("Jekyll-WikiRefs: Invalid wikilink level")
|
160
|
+
end
|
161
|
+
return '<a class="' + $wiki_conf.css_name("wiki") + link_type_txt + '" href="' + lnk_doc_rel_url + '">' + inner_txt + '</a>'
|
162
|
+
end
|
163
|
+
|
164
|
+
# helpers
|
165
|
+
|
166
|
+
def sort_for_replacement
|
167
|
+
# sorting inline wikilinks is necessary so when wikilinks are replaced,
|
168
|
+
# !embeds and longer strings are replaced first so as not to accidentally overwrite
|
169
|
+
# substrings
|
170
|
+
# (this is especially likely if there is a matching wikilink that
|
171
|
+
# appears as both untyped and typed in a document or in a regular link and embed)
|
172
|
+
temp = @wikilink_inlines.dup
|
173
|
+
@wikilink_inlines.clear()
|
174
|
+
embeds = temp.select { |w| w.embedded? }
|
175
|
+
typed_wikilinks = temp.select { |w| w.is_typed? }
|
176
|
+
untyped_wikilinks = temp.select { |w| !w.is_typed? }
|
177
|
+
@wikilink_inlines = embeds.concat(typed_wikilinks.concat(untyped_wikilinks))
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# regex.rb
|
2
|
+
# regex constants defining supported file types and valid names for files, variables, or text
|
3
|
+
#
|
4
|
+
|
5
|
+
module Jekyll
|
6
|
+
module WikiRefs
|
7
|
+
# <regex_variables> only work with 'match' function, not with 'scan' function. :/
|
8
|
+
# oh well...they are there for easier debugging...
|
9
|
+
|
10
|
+
# supported image formats
|
11
|
+
# from: https://docs.github.com/en/github/managing-files-in-a-repository/working-with-non-code-files/rendering-and-diffing-images
|
12
|
+
SUPPORTED_IMG_FORMATS = Set.new(['.png', '.jpg', '.gif', '.psd', '.svg'])
|
13
|
+
|
14
|
+
# wikilink constants
|
15
|
+
REGEX_LINK_LEFT = /\[\[/
|
16
|
+
REGEX_LINK_RIGHT = /\]\]/
|
17
|
+
REGEX_LINK_EMBED = /(?<embed>\!)/
|
18
|
+
REGEX_LINK_TYPE = /\s*::\s*/
|
19
|
+
REGEX_LINK_HEADER = /\#/
|
20
|
+
REGEX_LINK_BLOCK = /\#\^/
|
21
|
+
REGEX_LINK_LABEL = /\|/
|
22
|
+
|
23
|
+
# wikitext usable char requirements
|
24
|
+
REGEX_LINK_TYPE_CHARS = /[^\n\s\!\#\^\|\]]+/i
|
25
|
+
REGEX_FILENAME_CHARS = /[^\\:\#\^\|\[\]]+/i
|
26
|
+
REGEX_HEADER_CHARS = /[^\!\#\^\|\[\]]+/i
|
27
|
+
REGEX_BLOCK_ID_CHARS = /[^\\\/:\!\#\^\|\[\]^\n]+/i
|
28
|
+
REGEX_LABEL_CHARS = /(.+?)(?=\]{2}[^\]])/i
|
29
|
+
|
30
|
+
# capture groups
|
31
|
+
REGEX_LINK_TYPE_TXT = /(?<link-type-txt>#{REGEX_LINK_TYPE_CHARS})/i
|
32
|
+
REGEX_FILENAME = /(?<filename>#{REGEX_FILENAME_CHARS})/i
|
33
|
+
REGEX_HEADER_TXT = /(?<header-txt>#{REGEX_HEADER_CHARS})/i
|
34
|
+
REGEX_BLOCK_ID_TXT = /(?<block-id>#{REGEX_BLOCK_ID_CHARS})/i
|
35
|
+
REGEX_LABEL_TXT = /(?<label-txt>#{REGEX_LABEL_CHARS})/i
|
36
|
+
|
37
|
+
# target markdown text (headers, lists, and blocks)
|
38
|
+
## kramdown regexes
|
39
|
+
### atx header: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/header.rb#L29
|
40
|
+
REGEX_ATX_HEADER = /^\#{1,6}[\t ]*([^ \t].*)\n/i
|
41
|
+
### setext header: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/header.rb#L17
|
42
|
+
REGEX_SETEXT_HEADER = /^\s{0,3}([^ \t].*)\n[-=][-=]*[ \t\r\f\v]*\n/i
|
43
|
+
## list item: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/list.rb#L49
|
44
|
+
REGEX_BULLET = /(?<bullet>[+*-])/i
|
45
|
+
## markdown-style block-reference
|
46
|
+
REGEX_BLOCK = /.*\s\^#{REGEX_BLOCK_ID_TXT}/i
|
47
|
+
|
48
|
+
# wikilinks
|
49
|
+
|
50
|
+
## inline
|
51
|
+
REGEX_WIKI_LINK_INLINES = %r{ # capture indeces
|
52
|
+
(#{REGEX_LINK_EMBED})? # 0
|
53
|
+
(#{REGEX_LINK_TYPE_TXT}#{REGEX_LINK_TYPE})? # 1
|
54
|
+
#{REGEX_LINK_LEFT}
|
55
|
+
#{REGEX_FILENAME} # 2
|
56
|
+
(#{REGEX_LINK_HEADER}#{REGEX_HEADER_TXT})? # 3
|
57
|
+
(#{REGEX_LINK_BLOCK}#{REGEX_BLOCK_ID_TXT})? # 4
|
58
|
+
(#{REGEX_LINK_LABEL}#{REGEX_LABEL_TXT})? # 5
|
59
|
+
#{REGEX_LINK_RIGHT}
|
60
|
+
}x
|
61
|
+
|
62
|
+
## block
|
63
|
+
### single
|
64
|
+
REGEX_SINGLE = /#{REGEX_LINK_LEFT}#{REGEX_FILENAME_CHARS}#{REGEX_LINK_RIGHT}/i
|
65
|
+
### list (comma is responsible for catching the single case)
|
66
|
+
REGEX_LIST_COMMA = /((?:\s*#{REGEX_SINGLE}\s*)(?:,\s*#{REGEX_SINGLE}\s*)*)/i
|
67
|
+
REGEX_LIST_MKDN = /((?<=\n)\s{0,3}#{REGEX_BULLET}\s#{REGEX_SINGLE}\s*)+/i # (see REGEX_LIST_ITEM)
|
68
|
+
### process
|
69
|
+
REGEX_BLOCK_TYPES = /((?<!\n)(?:#{REGEX_LIST_COMMA})|#{REGEX_LIST_MKDN})/i
|
70
|
+
REGEX_WIKI_LINK_BLOCKS = /^\s{0,3}#{REGEX_LINK_TYPE_TXT}#{REGEX_LINK_TYPE}(?:\s*|\G)(?<items>#{REGEX_BLOCK_TYPES})\n/i
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,312 @@
|
|
1
|
+
# wiki data structures
|
2
|
+
require_relative "regex"
|
3
|
+
|
4
|
+
module Jekyll
|
5
|
+
module WikiRefs
|
6
|
+
|
7
|
+
# wikilink classes know everything about the original markdown syntax and its semantic meaning
|
8
|
+
|
9
|
+
class WikiLinkBlock
|
10
|
+
attr_reader :link_type, :filenames
|
11
|
+
|
12
|
+
# parameters ordered by appearance in regex
|
13
|
+
def initialize(doc_mngr, context_filename, link_type, bullet_type=nil)
|
14
|
+
@doc_mngr ||= doc_mngr
|
15
|
+
@context_filename ||= context_filename
|
16
|
+
@link_type ||= link_type
|
17
|
+
@bullet_type ||= bullet_type
|
18
|
+
@filenames = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_item(filename)
|
22
|
+
Jekyll.logger.error("Jekyll-WikiRefs: 'filename' required") if filename.nil? || filename.empty?
|
23
|
+
@filenames << filename
|
24
|
+
end
|
25
|
+
|
26
|
+
# data
|
27
|
+
|
28
|
+
def md_regex
|
29
|
+
if !is_typed? || !has_filenames?
|
30
|
+
Jekyll.logger.error("Jekyll-WikiRefs: WikiLinkBlock.md_regex error -- type: #{@link_type}, fnames: #{@filenames.inspect}, for: #{@context_filename}")
|
31
|
+
end
|
32
|
+
# comma (including singles)
|
33
|
+
if @bullet_type.nil?
|
34
|
+
link_type = /#{@link_type}#{REGEX_LINK_TYPE}/i
|
35
|
+
tmp_filenames = @filenames.dup
|
36
|
+
first_filename = /\s*#{REGEX_LINK_LEFT}#{tmp_filenames.shift()}#{REGEX_LINK_RIGHT}\s*/i
|
37
|
+
filename_strs = tmp_filenames.map { |f| /,\s*#{REGEX_LINK_LEFT}#{f}#{REGEX_LINK_RIGHT}\s*/i }
|
38
|
+
md_regex = /#{link_type}#{first_filename}#{filename_strs.join('')}\n/i
|
39
|
+
# mkdn
|
40
|
+
elsif !@bullet_type.match(REGEX_BULLET).nil?
|
41
|
+
link_type = /#{@link_type}#{REGEX_LINK_TYPE}\n/i
|
42
|
+
filename_strs = @filenames.map { |f| /\s{0,3}#{Regexp.escape(@bullet_type)}\s#{REGEX_LINK_LEFT}#{f}#{REGEX_LINK_RIGHT}\n/i }
|
43
|
+
md_regex = /#{link_type}#{filename_strs.join("")}/i
|
44
|
+
else
|
45
|
+
Jekyll.logger.error("Jekyll-WikiRefs: WikiLinkBlock.bullet_type error: #{@bullet_type}")
|
46
|
+
end
|
47
|
+
return md_regex
|
48
|
+
end
|
49
|
+
|
50
|
+
def md_str
|
51
|
+
if !is_typed? || !has_filenames?
|
52
|
+
Jekyll.logger.error("Jekyll-WikiRefs: WikiLinkBlockList.md_str error -- type: #{@link_type}, fnames: #{@filenames.inspect}, for: #{@context_filename}")
|
53
|
+
end
|
54
|
+
# comma (including singles)
|
55
|
+
if @bullet_type.nil?
|
56
|
+
link_type = "#{@link_type}::"
|
57
|
+
filename_strs = @filenames.map { |f| "\[\[#{f}\]\]," }
|
58
|
+
md_str = (link_type + filename_strs.join('')).delete_suffix(",")
|
59
|
+
# mkdn
|
60
|
+
elsif !@bullet_type.match(REGEX_BULLET).nil?
|
61
|
+
link_type = "#{@link_type}::\n"
|
62
|
+
filename_strs = @filenames.map { |f| li[0] + " \[\[#{li[1]}\]\]\n" }
|
63
|
+
md_str = link_type + filename_strs.join('')
|
64
|
+
else
|
65
|
+
Jekyll.logger.error("Jekyll-WikiRefs: 'bullet_type' invalid: #{@bullet_type}")
|
66
|
+
end
|
67
|
+
return md_str
|
68
|
+
end
|
69
|
+
|
70
|
+
def urls
|
71
|
+
# return @filenames.map { |f| @doc_mngr.get_doc_by_fname(f).url }.compact()
|
72
|
+
urls = []
|
73
|
+
@filenames.each do |f|
|
74
|
+
doc = @doc_mngr.get_doc_by_fname(f)
|
75
|
+
urls << doc.url if !doc.nil?
|
76
|
+
end
|
77
|
+
return urls
|
78
|
+
end
|
79
|
+
|
80
|
+
# 'fm' -> frontmatter
|
81
|
+
|
82
|
+
def context_fm_data
|
83
|
+
return {
|
84
|
+
'type' => @link_type,
|
85
|
+
'urls' => [self.context_doc.url],
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def linked_fm_data
|
90
|
+
valid_urls = self.urls.select{ |url| @doc_mngr.get_doc_by_url(url) }
|
91
|
+
return {
|
92
|
+
'type' => @link_type,
|
93
|
+
'urls' => valid_urls,
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def context_doc
|
98
|
+
return @doc_mngr.get_doc_by_fname(@context_filename)
|
99
|
+
end
|
100
|
+
|
101
|
+
def linked_docs
|
102
|
+
docs = []
|
103
|
+
@filenames.each do |f|
|
104
|
+
doc = @doc_mngr.get_doc_by_fname(f)
|
105
|
+
docs << doc if !doc.nil?
|
106
|
+
end
|
107
|
+
return docs
|
108
|
+
end
|
109
|
+
|
110
|
+
def missing_doc_filenames
|
111
|
+
missing_doc_fnames = []
|
112
|
+
@filenames.each do |f|
|
113
|
+
doc = @doc_mngr.get_doc_by_fname(f)
|
114
|
+
missing_doc_fnames << f if doc.nil?
|
115
|
+
end
|
116
|
+
return missing_doc_fnames
|
117
|
+
end
|
118
|
+
|
119
|
+
# descriptor methods
|
120
|
+
|
121
|
+
def has_filenames?
|
122
|
+
return !@filenames.nil? && !@filenames.empty?
|
123
|
+
end
|
124
|
+
|
125
|
+
def is_typed?
|
126
|
+
return !@link_type.nil? && !@link_type.empty?
|
127
|
+
end
|
128
|
+
|
129
|
+
# validation methods
|
130
|
+
|
131
|
+
def is_valid?
|
132
|
+
all_filenames_missing = linked_docs.empty?
|
133
|
+
return false if !is_typed? || !has_filenames? || all_filenames_missing
|
134
|
+
return true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class WikiLinkInline
|
139
|
+
attr_reader :link_type, :filename, :header_txt, :block_id
|
140
|
+
|
141
|
+
FILE_PATH = "file_path"
|
142
|
+
FILENAME = "filename"
|
143
|
+
HEADER_TXT = "header_txt"
|
144
|
+
BLOCK_ID = "block_id"
|
145
|
+
|
146
|
+
# parameters ordered by appearance in regex
|
147
|
+
def initialize(doc_mngr, context_filename, embed, link_type, file_string, header_txt, block_id, label_txt)
|
148
|
+
if file_string.include?('/') && file_string[0] == '/'
|
149
|
+
@path_type = "absolute"
|
150
|
+
@file_path ||= file_string[1...] # remove leading '/' to match `jekyll_collection_doc.relative_path`
|
151
|
+
@filename ||= file_string.split('/').last
|
152
|
+
elsif file_string.include?('/') && file_string[0] != '/'
|
153
|
+
Jekyll.logger.error("Jekyll-WikiRefs: Relative file paths are not yet supported, please use absolute file paths that start with '/' for #{file_string}")
|
154
|
+
# todo:
|
155
|
+
# @path_type = "relative"
|
156
|
+
else
|
157
|
+
@filename ||= file_string
|
158
|
+
end
|
159
|
+
@doc_mngr ||= doc_mngr
|
160
|
+
@context_filename ||= context_filename
|
161
|
+
@embed ||= embed
|
162
|
+
@link_type ||= link_type
|
163
|
+
@header_txt ||= header_txt
|
164
|
+
@block_id ||= block_id
|
165
|
+
@label_txt ||= label_txt
|
166
|
+
end
|
167
|
+
|
168
|
+
# escape square brackets if they appear in label text
|
169
|
+
def label_txt
|
170
|
+
return @label_txt.sub("[", "\\[").sub("]", "\\]")
|
171
|
+
end
|
172
|
+
|
173
|
+
# data
|
174
|
+
|
175
|
+
def md_regex
|
176
|
+
regex_embed = embedded? ? REGEX_LINK_EMBED : %r{}
|
177
|
+
regex_link_type = is_typed? ? %r{#{@link_type}#{REGEX_LINK_TYPE}} : %r{}
|
178
|
+
if !@file_path.nil?
|
179
|
+
file_string = described?(FILE_PATH) ? @file_path : ""
|
180
|
+
file_string = '/' + file_string if @path_type == "absolute"
|
181
|
+
else
|
182
|
+
file_string = described?(FILENAME) ? @filename : ""
|
183
|
+
end
|
184
|
+
if described?(HEADER_TXT)
|
185
|
+
header = %r{#{REGEX_LINK_HEADER}#{@header_txt}}
|
186
|
+
block = %r{}
|
187
|
+
elsif described?(BLOCK_ID)
|
188
|
+
header = %r{}
|
189
|
+
block = %r{#{REGEX_LINK_BLOCK}#{@block_id}}
|
190
|
+
elsif !described?(FILENAME) && !described?(FILE_PATH)
|
191
|
+
Jekyll.logger.error("Jekyll-WikiRefs: WikiLinkInline.md_regex error")
|
192
|
+
end
|
193
|
+
label_ = labelled? ? %r{#{REGEX_LINK_LABEL}#{label_txt}} : %r{}
|
194
|
+
return %r{#{regex_embed}#{regex_link_type}#{REGEX_LINK_LEFT}#{file_string}#{header}#{block}#{label_}#{REGEX_LINK_RIGHT}}
|
195
|
+
end
|
196
|
+
|
197
|
+
def md_str
|
198
|
+
embed = embedded? ? "!" : ""
|
199
|
+
link_type = is_typed? ? "#{@link_type}::" : ""
|
200
|
+
if !@file_path.nil?
|
201
|
+
file_string = described?(FILE_PATH) ? @file_path : ""
|
202
|
+
file_string = '/' + file_string if @path_type == "absolute"
|
203
|
+
else
|
204
|
+
file_string = described?(FILENAME) ? @filename : ""
|
205
|
+
end
|
206
|
+
if described?(HEADER_TXT)
|
207
|
+
header = "\##{@header_txt}"
|
208
|
+
block = ""
|
209
|
+
elsif described?(BLOCK_ID)
|
210
|
+
header = ""
|
211
|
+
block = "\#\^#{@block_id}"
|
212
|
+
elsif !described?(FILENAME) && !described?(FILE_PATH)
|
213
|
+
Jekyll.logger.error("Jekyll-WikiRefs: WikiLinkInline.md_str error")
|
214
|
+
end
|
215
|
+
label_ = labelled? ? "\|#{@label_txt}" : ""
|
216
|
+
return "#{embed}#{link_type}\[\[#{file_string}#{header}#{block}#{label_}\]\]"
|
217
|
+
end
|
218
|
+
|
219
|
+
# 'fm' -> frontmatter
|
220
|
+
|
221
|
+
def context_fm_data
|
222
|
+
return {
|
223
|
+
'type' => @link_type,
|
224
|
+
'url' => self.context_doc.url,
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
def linked_fm_data
|
229
|
+
return {
|
230
|
+
'type' => @link_type,
|
231
|
+
'url' => self.linked_doc.url,
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
def context_doc
|
236
|
+
return @doc_mngr.get_doc_by_fname(@context_filename)
|
237
|
+
end
|
238
|
+
|
239
|
+
def linked_doc
|
240
|
+
# by file path
|
241
|
+
return @doc_mngr.get_doc_by_fpath(@file_path) if !@file_path.nil?
|
242
|
+
# by filename
|
243
|
+
return @doc_mngr.get_doc_by_fname(@filename) if @file_path.nil?
|
244
|
+
return nil
|
245
|
+
end
|
246
|
+
|
247
|
+
def linked_img
|
248
|
+
return @doc_mngr.get_image_by_fname(@filename) if self.is_img?
|
249
|
+
return nil
|
250
|
+
end
|
251
|
+
|
252
|
+
# descriptor methods
|
253
|
+
|
254
|
+
# def describe
|
255
|
+
# return {
|
256
|
+
# 'level' => level,
|
257
|
+
# 'labelled' => labelled?,
|
258
|
+
# 'embedded' => embedded?,
|
259
|
+
# 'typed_link' => is_typed?,
|
260
|
+
# }
|
261
|
+
# end
|
262
|
+
|
263
|
+
def labelled?
|
264
|
+
return !@label_txt.nil? && !@label_txt.empty?
|
265
|
+
end
|
266
|
+
|
267
|
+
def is_typed?
|
268
|
+
return !@link_type.nil? && !@link_type.empty?
|
269
|
+
end
|
270
|
+
|
271
|
+
def embedded?
|
272
|
+
return !@embed.nil? && @embed == "!"
|
273
|
+
end
|
274
|
+
|
275
|
+
def is_img?
|
276
|
+
# github supported image formats: https://docs.github.com/en/github/managing-files-in-a-repository/working-with-non-code-files/rendering-and-diffing-images
|
277
|
+
return SUPPORTED_IMG_FORMATS.any?{ |ext| ext == File.extname(@filename).downcase }
|
278
|
+
end
|
279
|
+
|
280
|
+
def is_img_svg?
|
281
|
+
return File.extname(@filename).downcase == ".svg"
|
282
|
+
end
|
283
|
+
|
284
|
+
# this method helps to make the 'WikiLinkInline.level' code read like a clean truth table.
|
285
|
+
def described?(chunk)
|
286
|
+
return (!@file_path.nil? && !@file_path.empty?) if chunk == FILE_PATH
|
287
|
+
return (!@filename.nil? && !@filename.empty?) if chunk == FILENAME
|
288
|
+
return (!@header_txt.nil? && !@header_txt.empty?) if chunk == HEADER_TXT
|
289
|
+
return (!@block_id.nil? && !@block_id.empty?) if chunk == BLOCK_ID
|
290
|
+
Jekyll.logger.error("Jekyll-WikiRefs: There is no link level '#{chunk}' in the WikiLink Class")
|
291
|
+
end
|
292
|
+
|
293
|
+
def level
|
294
|
+
return "file_path" if described?(FILE_PATH) && described?(FILENAME) && !described?(HEADER_TXT) && !described?(BLOCK_ID)
|
295
|
+
return "filename" if !described?(FILE_PATH) && described?(FILENAME) && !described?(HEADER_TXT) && !described?(BLOCK_ID)
|
296
|
+
return "header" if (described?(FILE_PATH) || described?(FILENAME)) && described?(HEADER_TXT) && !described?(BLOCK_ID)
|
297
|
+
return "block" if (described?(FILE_PATH) || described?(FILENAME)) && !described?(HEADER_TXT) && described?(BLOCK_ID)
|
298
|
+
return "invalid"
|
299
|
+
end
|
300
|
+
|
301
|
+
# validation methods
|
302
|
+
|
303
|
+
def is_valid?
|
304
|
+
return false if !@doc_mngr.file_exists?(@filename, @file_path)
|
305
|
+
return false if (self.level == "header") && !@doc_mngr.doc_has_header?(self.linked_doc, @header_txt)
|
306
|
+
return false if (self.level == "block") && !@doc_mngr.doc_has_block_id?(self.linked_doc, @block_id)
|
307
|
+
return true
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
end
|
312
|
+
end
|
data/lib/jekyll-wikirefs.rb
CHANGED
@@ -2,9 +2,29 @@
|
|
2
2
|
|
3
3
|
require_relative "jekyll-wikirefs/version"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
# setup config
|
6
|
+
require_relative "jekyll-wikirefs/config"
|
7
|
+
Jekyll::Hooks.register :site, :after_init do |site|
|
8
|
+
# global '$wiki_conf' to ensure that all local jekyll plugins
|
9
|
+
# are reading from the same configuration
|
10
|
+
# (global var is not ideal, but is DRY)
|
11
|
+
$wiki_conf = Jekyll::WikiRefs::PluginConfig.new(site.config)
|
12
|
+
end
|
13
|
+
|
14
|
+
# setup docs (based on configs)
|
15
|
+
require_relative "jekyll-wikirefs/patch/doc_manager"
|
16
|
+
Jekyll::Hooks.register :site, :post_read do |site|
|
17
|
+
if !$wiki_conf.disabled?
|
18
|
+
site.doc_mngr = Jekyll::WikiRefs::DocManager.new(site)
|
9
19
|
end
|
10
20
|
end
|
21
|
+
|
22
|
+
# parse wikilinks / generate metadata
|
23
|
+
require_relative "jekyll-wikirefs/plugins/generator"
|
24
|
+
|
25
|
+
# convert weblinks
|
26
|
+
require_relative "jekyll-wikirefs/plugins/converter"
|
27
|
+
|
28
|
+
# hook up liquid filters
|
29
|
+
require_relative "jekyll-wikirefs/plugins/filter"
|
30
|
+
Liquid::Template.register_filter(Jekyll::WikiRefs::TypeFilters)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-wikirefs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- manunamz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jekyll
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 4.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.13.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.13.3
|
27
41
|
description:
|
28
42
|
email:
|
29
43
|
- manunamz@pm.me
|
@@ -32,14 +46,25 @@ extensions: []
|
|
32
46
|
extra_rdoc_files: []
|
33
47
|
files:
|
34
48
|
- lib/jekyll-wikirefs.rb
|
49
|
+
- lib/jekyll-wikirefs/config.rb
|
50
|
+
- lib/jekyll-wikirefs/patch/context.rb
|
51
|
+
- lib/jekyll-wikirefs/patch/doc_manager.rb
|
52
|
+
- lib/jekyll-wikirefs/patch/site.rb
|
53
|
+
- lib/jekyll-wikirefs/plugins/converter.rb
|
54
|
+
- lib/jekyll-wikirefs/plugins/filter.rb
|
55
|
+
- lib/jekyll-wikirefs/plugins/generator.rb
|
56
|
+
- lib/jekyll-wikirefs/util/link_index.rb
|
57
|
+
- lib/jekyll-wikirefs/util/parser.rb
|
58
|
+
- lib/jekyll-wikirefs/util/regex.rb
|
59
|
+
- lib/jekyll-wikirefs/util/wikiref.rb
|
35
60
|
- lib/jekyll-wikirefs/version.rb
|
36
|
-
homepage: https://github.com/
|
61
|
+
homepage: https://github.com/wikibonsai/jekyll-wikirefs
|
37
62
|
licenses:
|
38
63
|
- MIT
|
39
64
|
metadata:
|
40
|
-
homepage_uri: https://github.com/
|
41
|
-
source_code_uri: https://github.com/
|
42
|
-
changelog_uri: https://github.com/
|
65
|
+
homepage_uri: https://github.com/wikibonsai/jekyll-wikirefs
|
66
|
+
source_code_uri: https://github.com/wikibonsai/jekyll-wikirefs
|
67
|
+
changelog_uri: https://github.com/wikibonsai/jekyll-wikirefs/blob/main/CHANGELOG.md
|
43
68
|
post_install_message:
|
44
69
|
rdoc_options: []
|
45
70
|
require_paths:
|
@@ -55,8 +80,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
80
|
- !ruby/object:Gem::Version
|
56
81
|
version: '0'
|
57
82
|
requirements: []
|
58
|
-
rubygems_version: 3.4.
|
83
|
+
rubygems_version: 3.4.10
|
59
84
|
signing_key:
|
60
85
|
specification_version: 4
|
61
|
-
summary:
|
86
|
+
summary: Add [[wikirefs]] support (in markdown files) for jekyll.
|
62
87
|
test_files: []
|