mkdocs-wikilinks-plugin 0.1.0__py3-none-any.whl

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.
File without changes
@@ -0,0 +1,108 @@
1
+ import posixpath
2
+ from typing import List
3
+
4
+ import pygtrie
5
+ import mkdocs
6
+
7
+ from .types import EzLinksOptions
8
+
9
+
10
+ class FileMapper:
11
+ def __init__(
12
+ self,
13
+ options: EzLinksOptions,
14
+ root: str,
15
+ files: List[mkdocs.structure.pages.Page],
16
+ logger=None):
17
+ self.options = options
18
+ self.root = root
19
+ self.file_cache = {}
20
+ self.file_trie = pygtrie.StringTrie(separator='/')
21
+ self.logger = logger
22
+
23
+ # Drop any files outside of the root of the docs dir
24
+ self.files = [file for file in files if root in file.abs_src_path]
25
+
26
+ for file in self.files:
27
+ self._store_file(file.src_uri)
28
+
29
+ # Reduce the dictionary to only search terms that are unique
30
+ self.file_cache = {k: v for (k, v) in self.file_cache.items() if len(v) == 1}
31
+
32
+ def _store_file(self, file_path):
33
+ # Treat paths as posix format, regardless of OS
34
+ file_path = file_path.replace('\\', '/')
35
+ # Store the pathwise reversed representation of the file with and
36
+ # without file extension.
37
+ search_exprs = [file_path, posixpath.splitext(file_path)[0]]
38
+ for search_expr in search_exprs:
39
+ # Store in fast file cache
40
+ file_name = posixpath.basename(search_expr)
41
+ if file_name not in self.file_cache:
42
+ self.file_cache[file_name] = [file_path]
43
+ else:
44
+ self.file_cache[file_name].append(file_path)
45
+
46
+ # Store in trie
47
+ components = list(search_expr.split('/'))
48
+ components.reverse()
49
+ self.file_trie['/'.join(components)] = file_path
50
+
51
+ def search(self, from_file: str, file_path: str):
52
+ abs_to = file_path
53
+ # Detect if it's an absolute link, then just return it directly
54
+ if abs_to.startswith('/'):
55
+ return posixpath.join(self.root, abs_to[1:])
56
+ else:
57
+ # Check if it is a direct link first
58
+ from_dir = posixpath.dirname(from_file)
59
+ if posixpath.exists(posixpath.join(self.root, from_dir, file_path)):
60
+ return posixpath.join(self.root, from_dir, file_path)
61
+
62
+ # It's an EzLink that must be searched
63
+ file_name = posixpath.basename(file_path)
64
+
65
+ # Check fast file cache first
66
+ if posixpath.basename(file_name) in self.file_cache:
67
+ abs_to = self.file_cache[file_name][0]
68
+ else:
69
+ search_for = list(file_path.split('/'))
70
+ search_for.reverse()
71
+ search_for = "/".join(search_for)
72
+
73
+ # If we have an _exact_ match in the trie, we don't need to search
74
+ if search_for in self.file_trie:
75
+ abs_to = self.file_trie[search_for]
76
+ elif self.file_trie.has_subtrie(search_for):
77
+ # If we don't have an exact match, but have a partial prefix
78
+ values = self.file_trie.values(search_for)
79
+ abs_to = values[0]
80
+ has_ambiguity = len(values) > 1
81
+ # If we have ambiguities, attempt to auto-disambiguate by performing
82
+ # an iterative ascent of the link file's path. In this way, we should
83
+ # be able to get the result closest to the file doing the linking
84
+ if has_ambiguity:
85
+ file_path = posixpath.dirname(from_file)
86
+ components = file_path.split('/')
87
+ components.reverse()
88
+ for path_component in components:
89
+ search_for += f"/{path_component}"
90
+ if self.file_trie.has_subtrie(search_for) or search_for in self.file_trie:
91
+ new_vals = self.file_trie.values(search_for)
92
+ if len(new_vals) == 1:
93
+ abs_to = new_vals[0]
94
+ # We've resolved the ambiguity, so no need to warn
95
+ has_ambiguity = False
96
+ break
97
+
98
+ if has_ambiguity:
99
+ ambiguities = ""
100
+ for idx, file in enumerate(values):
101
+ active = "<--- (Selected)" if idx == 0 else ""
102
+ ambiguities += f" {idx}: {file} {active}\n"
103
+ log_fn = self.logger.warning if self.options.warn_ambiguities else self.logger.debug
104
+ log_fn(f"[EzLink] Link ambiguity detected.\n"
105
+ f"File: '{from_file}'\n"
106
+ f"Link: '{search_for}'\n"
107
+ "Ambiguities:\n" + ambiguities)
108
+ return posixpath.join(self.root, abs_to)
@@ -0,0 +1,56 @@
1
+ import logging
2
+ from typing import List
3
+
4
+ import mkdocs
5
+
6
+ from .file_mapper import FileMapper
7
+ from .replacer import EzLinksReplacer
8
+ from .scanners.md_link_scanner import MdLinkScanner
9
+ from .scanners.wiki_link_scanner import WikiLinkScanner
10
+ from .scanners.reference_link_scanner import ReferenceLinkScanner
11
+ from .types import EzLinksOptions
12
+
13
+ LOGGER = logging.getLogger(f"mkdocs.plugins.{__name__}")
14
+
15
+
16
+ class EzLinksPlugin(mkdocs.plugins.BasePlugin):
17
+ config_scheme = (
18
+ ('wikilinks', mkdocs.config.config_options.Type(bool, default=True)),
19
+ ('warn_ambiguities', mkdocs.config.config_options.Type(bool, default=False)),
20
+ ('reference_links', mkdocs.config.config_options.Type(bool, default=False))
21
+ )
22
+
23
+ def init(self, config):
24
+ self.replacer = EzLinksReplacer(
25
+ root=config['docs_dir'],
26
+ file_map=self.file_mapper,
27
+ use_directory_urls=config['use_directory_urls'],
28
+ options=EzLinksOptions(**self.config),
29
+ logger=LOGGER
30
+ )
31
+
32
+ self.replacer.add_scanner(MdLinkScanner())
33
+ if self.config['wikilinks']:
34
+ self.replacer.add_scanner(WikiLinkScanner())
35
+
36
+ if self.config['reference_links']:
37
+ self.replacer.add_scanner(ReferenceLinkScanner())
38
+
39
+ # Compile the regex once
40
+ self.replacer.compile()
41
+
42
+ # Build a fast lookup of all files (by file name)
43
+ def on_files(self, files: List[mkdocs.structure.files.File], config):
44
+ self.file_mapper = FileMapper(
45
+ options=EzLinksOptions(**self.config),
46
+ root=config['docs_dir'],
47
+ files=files,
48
+ logger=LOGGER
49
+ )
50
+
51
+ # After the file map has been built, initialize what we can that will
52
+ # remain static
53
+ self.init(config)
54
+
55
+ def on_page_markdown(self, markdown, page, config, **kwargs):
56
+ return self.replacer.replace(page.file.src_uri, markdown)
@@ -0,0 +1,89 @@
1
+ import posixpath
2
+ import re
3
+ from typing import Match
4
+ from urllib.parse import quote
5
+ from .types import EzLinksOptions, BrokenLink
6
+ from .scanners.base_link_scanner import BaseLinkScanner
7
+ from .file_mapper import FileMapper
8
+
9
+
10
+ class EzLinksReplacer:
11
+ def __init__(
12
+ self,
13
+ root: str,
14
+ file_map: FileMapper,
15
+ use_directory_urls: bool,
16
+ options: EzLinksOptions,
17
+ logger):
18
+ self.root = root
19
+ self.file_map = file_map
20
+ self.use_directory_urls = use_directory_urls
21
+ self.options = options
22
+ self.scanners = []
23
+ self.logger = logger
24
+
25
+ def add_scanner(self, scanner: BaseLinkScanner) -> None:
26
+ self.scanners.append(scanner)
27
+
28
+ def replace(self, path: str, markdown: str) -> str:
29
+ self.path = path
30
+
31
+ # Multi-Pattern search pattern, to capture all link types at once
32
+ return re.sub(self.regex, self._do_replace, markdown)
33
+
34
+ # Compiles all scanner patterns as a multi-pattern search, with
35
+ # built in code fence skipping (individual link scanners don't
36
+ # have to worry about them.
37
+ def compile(self):
38
+ patterns = '|'.join([scanner.pattern() for scanner in self.scanners])
39
+ self.regex = re.compile(
40
+ fr'''
41
+ (?: # Attempt to match a code block
42
+ [`]{{3}}
43
+ (?:[\w\W]*?)
44
+ [`]{{3}}$
45
+ | # Match an inline code block
46
+ `[\w\W]*?`
47
+ )
48
+ | # Attempt to match any one of the subpatterns
49
+ (?:
50
+ {patterns}
51
+ )
52
+ ''', re.X | re.MULTILINE)
53
+
54
+ def _do_replace(self, match: Match) -> str:
55
+ abs_from = posixpath.dirname(posixpath.join(self.root, self.path))
56
+ try:
57
+ for scanner in self.scanners:
58
+ if scanner.match(match):
59
+ link = scanner.extract(match)
60
+
61
+ # Do some massaging of the extracted results
62
+ if not link:
63
+ raise BrokenLink(f"Could not extract link from '{match.group(0)}'")
64
+
65
+ # Handle case of local page anchor
66
+ if not link.target:
67
+ if link.anchor:
68
+ link.target = posixpath.join(self.root, self.path)
69
+ else:
70
+ raise BrokenLink(f"No target for link '{match.group(0)}'")
71
+ else:
72
+ # Otherwise, search for the target through the file map
73
+ search_result = self.file_map.search(self.path, link.target)
74
+ if not self.use_directory_urls:
75
+ search_result = search_result + '.md' if '.' not in search_result else search_result
76
+
77
+ if not search_result:
78
+ raise BrokenLink(f"'{link.target}' not found.")
79
+ link.target = search_result
80
+
81
+ link.target = quote(posixpath.relpath(link.target, abs_from))
82
+ return link.render()
83
+ except BrokenLink as ex:
84
+ # Log these out as Debug messages, as the regular mkdocs
85
+ # strict mode will log out broken links.
86
+ self.logger.debug(f"[EzLinks] {ex}")
87
+
88
+ # Fall through, return the original link unaltered, and let mkdocs handle it
89
+ return match.group(0)
File without changes
@@ -0,0 +1,17 @@
1
+ from abc import ABCMeta, abstractmethod
2
+ from typing import Pattern, Match
3
+ from ..types import Link
4
+
5
+
6
+ class BaseLinkScanner(metaclass=ABCMeta):
7
+ @abstractmethod
8
+ def pattern(self) -> str:
9
+ pass
10
+
11
+ @abstractmethod
12
+ def match(self, match: Match) -> bool:
13
+ pass
14
+
15
+ @abstractmethod
16
+ def extract(self, match: Match) -> Link:
17
+ pass
@@ -0,0 +1,53 @@
1
+ import re
2
+ from typing import Match
3
+
4
+ from .base_link_scanner import BaseLinkScanner
5
+ from ..types import Link, BrokenLink
6
+
7
+
8
+ class MdLinkScanner(BaseLinkScanner):
9
+ def pattern(self) -> str:
10
+ # +------------------------------+
11
+ # | MD Link Regex Capture Groups |
12
+ # +-------------------------------------------------------------------------------------+
13
+ # | md_is_image | Contains ! when an image tag, or empty if not (check both) |
14
+ # | md_alt_is_image | Contains ! when an image tag, or empty if not (check both) |
15
+ # | md_text | Contains the Link Text between [md_text] |
16
+ # | md_protocol | Rejects match if link contains a protocol scheme (e.g. http) |
17
+ # | md_target | Contains the full target of the Link (filename.md#anchor) |
18
+ # | md_filename | Contains just the filename portion of the target (filename.md) |
19
+ # | md_anchor | Contains the anchor, if present (e.g. `file.md#anchor`) |
20
+ # | md_title | Contains the title, if present (e.g. `file.md "My Title"`) |
21
+ # +-------------------------------------------------------------------------------------+
22
+ return r"""
23
+ (?:
24
+ (?P<md_is_image>\!?)\[\]
25
+ |
26
+ (?P<md_alt_is_image>\!?)
27
+ \[
28
+ (?P<md_text>[^\]]+)
29
+ \]
30
+ )
31
+ \(
32
+ (?P<md_target>
33
+ (?!(?P<md_protocol>[a-z][a-z0-9+\-.]*:\/\/))
34
+ (?P<md_filename>\/?[^\#\ \)]*)?
35
+ (?:\#(?P<md_anchor>[^\)\"]*)?)?
36
+ (?:\ \"(?P<md_title>[^\"\)]*)\")?
37
+ )
38
+ \)
39
+ """
40
+
41
+ def match(self, match: Match) -> bool:
42
+ return bool(match.groupdict().get("md_target"))
43
+
44
+ def extract(self, match: Match) -> Link:
45
+ groups = match.groupdict()
46
+ image = groups.get("md_is_image") or groups.get("md_alt_is_image") or ""
47
+ return Link(
48
+ image=image,
49
+ text=groups.get("md_text") or "",
50
+ target=groups.get("md_filename") or "",
51
+ title=groups.get("md_title") or "",
52
+ anchor=groups.get("md_anchor") or "",
53
+ )
@@ -0,0 +1,42 @@
1
+ from typing import Pattern, Match
2
+ from .base_link_scanner import BaseLinkScanner
3
+ from ..types import Link
4
+
5
+
6
+ class ReferenceLinkScanner(BaseLinkScanner):
7
+ def pattern(self) -> str:
8
+ # +--------------------------------------+
9
+ # | Reference Link Regex Capture Groups |
10
+ # +--------------------------------------+
11
+ # | Example: [text]: url "title" |
12
+ # | |
13
+ # | text: Required |
14
+ # | url: Required |
15
+ # | title: Optional, up to one newline |
16
+ # +--------------------------------------+
17
+ return r"""
18
+ (?:
19
+ \[
20
+ (?P<ref_text>[^\]]+)
21
+ \]
22
+ )\:\
23
+ (?!(?P<ref_protocol>[a-z][a-z0-9+\-.]*:\/\/))
24
+ (?P<ref_target>\/?[^\#\ \)(\r\n|\r|\n)]*)?
25
+ (?:\#(?P<ref_anchor> [^\(\ ]*)?)?
26
+ (?:(\r\n|\r|\n)?)?(?P<ref_title>\ ?\"[^(\r\n|\r|\n)\"]*\")?
27
+ """
28
+
29
+ def match(self, match: Match) -> bool:
30
+ return bool(
31
+ match.groupdict().get("ref_text") and match.groupdict().get("ref_target")
32
+ )
33
+
34
+ def extract(self, match: Match) -> Link:
35
+ groups = match.groupdict()
36
+ return Link(
37
+ image=False,
38
+ text=groups.get("ref_text") or "",
39
+ target=groups.get("ref_target") or "",
40
+ title=groups.get("ref_title") or "",
41
+ anchor=groups.get("ref_anchor") or "",
42
+ )
@@ -0,0 +1,55 @@
1
+ import re
2
+ from typing import Pattern, Match
3
+
4
+ from .base_link_scanner import BaseLinkScanner
5
+ from ..types import Link, BrokenLink
6
+
7
+
8
+ class WikiLinkScanner(BaseLinkScanner):
9
+ def pattern(self) -> str:
10
+ # +--------------------------------+
11
+ # | Wiki Link Regex Capture Groups |
12
+ # +------------------------------------------------------------------------------------+
13
+ # | wiki_is_image | Contains ! when an image tag, or empty if not |
14
+ # | wiki_link | Contains the Link Text between [[ wiki_link ]] |
15
+ # | wiki_anchor | Contains the anchor, if present (e.g. file.md#anchor -> 'anchor') |
16
+ # | wiki_text | Contains the text of the link. |
17
+ # +------------------------------------------------------------------------------------+
18
+ return r"""
19
+ (?P<wiki_is_image>[\!]?)
20
+ \[\[
21
+ (?P<wiki_link>[^#\|\]]*?)
22
+ (?:\#(?P<wiki_anchor>[^\|\]]+)?)?
23
+ (?:\|(?P<wiki_text>[^\]]+)?)?
24
+ \]\]
25
+ """
26
+
27
+ def match(self, match: Match) -> bool:
28
+ groups = match.groupdict()
29
+ return groups.get("wiki_link") or groups.get("wiki_anchor")
30
+
31
+ def extract(self, match: Match) -> Link:
32
+ groups = match.groupdict()
33
+
34
+ image = groups.get("wiki_is_image") or ""
35
+ link = groups.get("wiki_link") or ""
36
+ anchor = groups.get("wiki_anchor") or ""
37
+ text = groups.get("wiki_text") or link or anchor
38
+
39
+ if not (link or text or anchor):
40
+ raise BrokenLink(
41
+ f"Could not extract required field `wiki_link` from {match.group(0)}"
42
+ )
43
+
44
+ if anchor:
45
+ anchor = self._slugify(anchor)
46
+
47
+ return Link(image=image, text=text, target=link, title=text, anchor=anchor)
48
+
49
+ def _slugify(self, link: str) -> str:
50
+ # Convert to lowercase
51
+ slug = link.lower()
52
+ # Convert all spaces to '-'
53
+ slug = re.sub(r"\ ", r"-", slug)
54
+ # Convert all unsupported characters to ''
55
+ return slug
@@ -0,0 +1,34 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ class BrokenLink(Exception):
5
+ #Ignore these
6
+ pass
7
+
8
+
9
+ @dataclass
10
+ class Link:
11
+ """Dataclass to hold the contents required to form a complete Link."""
12
+
13
+ image: bool
14
+ text: str
15
+ target: str
16
+ anchor: str
17
+ title: str
18
+
19
+ # Render as a complete MD compatible link
20
+ def render(self):
21
+ img = "!" if self.image else ""
22
+ anchor = f"#{self.anchor}" if self.anchor else ""
23
+ title = f' "{self.title}"' if self.title else ""
24
+
25
+ return f"{img}[{self.text}]({self.target}{anchor}{title})"
26
+
27
+
28
+ @dataclass
29
+ class EzLinksOptions:
30
+ """Dataclass to hold typed options from the configuration."""
31
+
32
+ wikilinks: bool
33
+ warn_ambiguities: bool
34
+ reference_links: bool
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Mick Orbik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,212 @@
1
+ Metadata-Version: 2.1
2
+ Name: mkdocs-wikilinks-plugin
3
+ Version: 0.1.0
4
+ Summary: A mkdocs plugin that makes linking to other documents easy.
5
+ Home-page: https://github.com/carloslab-ai/mkdocs-wikilinks-plugin
6
+ Author: Carlos
7
+ Author-email: carlos.truong.dev@gmail.com
8
+ License: MIT
9
+ Keywords: mkdocs,wikilinks,ezlinks,obsidian,roam
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Information Technology
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Requires-Python: >=3.6
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: mkdocs
19
+ Requires-Dist: pygtrie==2.*
20
+ Requires-Dist: dataclasses>=0.7; python_version < "3.7.0"
21
+
22
+ *Fork*
23
+ To install this fork :
24
+ `pip install mkdocs-ezlinked-plugin`
25
+
26
+ # mkdocs-ezlinks-plugin
27
+
28
+ ![](logo.png)
29
+
30
+ Plugin for mkdocs which enables easier linking between pages.
31
+
32
+ This plugin was written in order to provide an up-to-date and
33
+ feature complete plugin for easily referencing documents
34
+ with a variety of features:
35
+
36
+ * Optimized file name lookup
37
+ * Code Block Preservation
38
+ * File name linking (e.g. `[Text](file#anchor "title")`)
39
+ * Absolute paths (e.g. `[Text](/link/to/file.md)`)
40
+ * WikiLinks support (e.g. `[[Link#anchor|Link Title]]`)
41
+ * Reference Link support (e.g. `[foo]: bar/ "Foo Title"`)
42
+
43
+ # Install
44
+ ```
45
+ pip install mkdocs-ezlinks-plugin
46
+ ```
47
+
48
+ Edit your mkdocs configuration file to enable the plugin:
49
+ ```
50
+ plugins:
51
+ - search
52
+ - ezlinks
53
+ ```
54
+ > **NOTE**
55
+ > If you have no plugins entry in your config file yet, you'll likely also want to add the search plugin. MkDocs enables it by default if there is no plugins entry set, but now you have to enable it explicitly.
56
+
57
+ # Release Log
58
+
59
+ ## Release 0.1.14
60
+ This is a bugfix release.
61
+
62
+ Issues addressed:
63
+ * GH issue #35, `Links between deeply nested subfolders fails.`
64
+ Dev @Mara-Li reported an issue with wikilinks between deeply nested subfolders failing due to an incorrectly
65
+ rendered relative link to the file.
66
+
67
+ * An unreported Windows usage issue
68
+ It's possible this bug existed for quite some time. Basically, on Windows, there was disagreement between the
69
+ path separators used at different points in the file mapping and searching process. This unifies it to store
70
+ and search for paths only with the `/` delimiter instead of the OS defined separator.
71
+
72
+
73
+ ## Release 0.1.13
74
+ Adds support for Reference Link parsing. This is to support certain Foam editors, which generate [Reference Links](https://spec.commonmark.org/0.29/#reference-link).
75
+
76
+ Issues Addressed:
77
+ * GH Issue #31, `Add support for reference link definitions`. Allows compatibility with certain Foam editors which generate Reference Links.
78
+
79
+ ## Release 0.1.12
80
+ This is a bugfix release.
81
+
82
+ Issues addressed:
83
+ * GH issue #25, `Absolute links not using http:// or https:// are treated as relative`.
84
+ Dev @robbcrg (thanks!) reported that links with protocol schemes other than those two should also be treated as
85
+ absolute links. The regex will exclude any link from a conformant protocol scheme from being converted using EzLinks.
86
+
87
+ * GH Issue #27, `Dictionary file cache is not being leveraged`.
88
+ An inverted comparison led to the fast file cache lookup never really being exercised. Now, if a filename is unique, it will find it in the fast file cache first, saving a more expensive full trie lookup.
89
+
90
+ ## Release 0.1.11
91
+ This is a bugfix release. The prior release switched from a dictionary lookup to a prefix trie lookup strategy, which allowed for better disambiguation between links, but is more expensive. The bug was that, even if a link was direct, it would trigger a full trie search. Now, direct links
92
+ are checked and returned directly if the file exists.
93
+
94
+ Additionally, a slight performance improvement was made where, in the case that a filename is unique to the entire site, it will rely on a fast dictionary lookup instead of a trie lookup.
95
+
96
+ # Configuration Options
97
+ ```
98
+ plugins:
99
+ - search
100
+ - ezlinks:
101
+ warn_ambiguities: {true|false}
102
+ wikilinks: {true|false}
103
+ reference_links: {true|false}
104
+ ```
105
+ ## warn_ambiguities
106
+ Determines whether to warn when an abmiguous link is encountered. An ambiguous link is one that would have more than one possible targets. For example, if you had the following document setup:
107
+
108
+ ```
109
+ + folder1/
110
+ +-- index.md
111
+ + folder2/
112
+ +-- index.md
113
+ ```
114
+
115
+ If you had any links that targeted `index.md`, EzLinks is not able to determine _which_ of the instances of `index.md` to target, thus it is ambiguous.
116
+
117
+ ### Disambiguating links
118
+ By default, EzLinks will attempt to resolve the ambiguity automatically. It does this by searching for the file closest to the file that is linking (with respect to the folder hierarchy).
119
+
120
+ ```
121
+ + guide/
122
+ + test.md
123
+ + getting_started/
124
+ + index.md
125
+ + tutorials/
126
+ - test.md
127
+ + getting_started/
128
+ + index.md
129
+ + more_advanced/
130
+ + index.md
131
+ ```
132
+ If you placed a link inside `guide/getting_started/index.md` such as `[Test](test)`, the resulting link has ambiguity, but in the default case, the `guide/test.md` file is _closer_ than the `tutorials/test.md`, therefore, it will select that file.
133
+
134
+ In the circumstance above, it would be possible to disambiguate _which_ `test.md` by including the containing folder, e.g. `guide/test.md` or `tutorials/test.md`. Note: This also works in conjunction with extension-less targets, e.g. `guide/test` and `tutorials/test`.
135
+
136
+ This disambiguation can continue with as many parent directories are specified, for instance `folder1/subfolder1/subfolder2/test.md`, specifying as many path components as necessary to fully disambiguate the links.
137
+
138
+ This method of disambiguation is supported by each of the supported link formats (MD links, wiki/roamlinks). For instance, you can use `[[folder1/index|Link Title]]` and `[[folder2/index.md]]`.
139
+
140
+ ## wikilinks
141
+ Determines whether to scan for wikilinks or not (See [WikiLink Support](#wikilink-support)).
142
+ > **NOTE**
143
+ > This plugin feature does not function well when the 'wikilinks' markdown extension is enabled. This plugin's functionality should replace the need for enabling said extension.
144
+
145
+ ## reference_links
146
+ Determins whether to scan for Reference Links or not (See [Reference Links](https://spec.commonmark.org/0.29/#reference-link), e.g. `[foo]: /bar "Foo Bar"`)
147
+
148
+ # Features
149
+ ## Filename Links
150
+ Given a layout such as
151
+ ```
152
+ - index.md
153
+ - folder/
154
+ +-- filename.md
155
+ +-- image.png
156
+ ```
157
+
158
+ The following links will result in the following translations,
159
+
160
+ |Link|Translation|
161
+ |----|-----------|
162
+ | `[Link Text](filename)` | `[Link Text](folder/filename.md)`|
163
+ | `[Link Text](filename#Anchor)` | `[Link Text](folder/filename.md#Anchor)`|
164
+ | `[Link Text](filename.md)` | `[Link Text](folder/filename.md)`|
165
+ | `[Link Text](filename.md#Anchor)` | `[Link Text](folder/filename.md#Anchor)` |
166
+ | `![Image Alt Text](image)` | `![Image Alt Text](folder/image.png)` |
167
+ | `![Image Alt Text](image.png)` | `![Image Alt Text](folder/image.png)` |
168
+ | `![Image Alt Test](image "Image Title")` | `![Image Alt Text](folder/image.png "Image Title")` |
169
+
170
+
171
+ ## Absolute Links
172
+ Given a layout such as
173
+ ```
174
+ - static/
175
+ +-- image.png
176
+ - folder/
177
+ +-- document.md
178
+ - index.md
179
+ ```
180
+ Given that we are entering the links into the `folder/document.md` file,
181
+
182
+ |Link|Translation|
183
+ |----|-----------|
184
+ | `![Link Text](/static/image.png)` | `![Link Text](../static/image.png)` |
185
+
186
+ # WikiLink Support
187
+ Given a layout such as
188
+ ```
189
+ - folder1/
190
+ +-- main.md
191
+ - folder2/
192
+ +-- page-name.md
193
+ - images/
194
+ +-- puppy.png
195
+ ```
196
+ and these links are entered in `folder1/main.md`, this is how wikilinks will be translated
197
+
198
+ |Link|Translation|
199
+ |----|-----------|
200
+ | `[[Page Name]]` | `[Page Name](../folder2/page-name.md)` |
201
+ | `![[Puppy]]` | `![Puppy](../images/puppy.png)` | `[[Page Name#Section Heading]]` | `[Page Name](../relative/path/to/page-name.md#section-heading)` |
202
+ | `[[Page Name\|Link Text]]` | `[Link Text](../folder2/page-name.md)` |
203
+ | `[[Page Name#Section Heading\|Link Text]]` | `[Link Text](../folder2/page-name.md#section-heading)` |
204
+
205
+ # Attribution
206
+ This work is highly inspired from the following plugins:
207
+ - [mkdocs-autolinks-plugin](https://github.com/midnightprioriem/mkdocs-autolinks-plugin/)
208
+ - [mkdocs-roamlinks-plugin](https://github.com/Jackiexiao/mkdocs-roamlinks-plugin)
209
+ - [mkdocs-abs-rel-plugin](https://github.com/sander76/mkdocs-abs-rel-plugin)
210
+
211
+ I have combined some the features of these plugins, fixed several existing bugs, and am adding features in order to
212
+ provide a cohesive, up-to-date, and maintained solution for the mkdocs community.
@@ -0,0 +1,16 @@
1
+ mkdocs_ezlinks_plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ mkdocs_ezlinks_plugin/file_mapper.py,sha256=95s8b7WmH0Yj7dUEEsbw1zmI6IUeLtmhqKNf8U2Ntjc,4865
3
+ mkdocs_ezlinks_plugin/plugin.py,sha256=V5Ch6cmLGqBpKw8ckdVWAAOnKEXR4fejaxDC_5T1zQg,1921
4
+ mkdocs_ezlinks_plugin/replacer.py,sha256=llSpPD_EWo0-MARSYRo_pOaAug9dwwvkvOVRpA6M7JA,3412
5
+ mkdocs_ezlinks_plugin/types.py,sha256=j0devK-Fl-IWOmRonEWNQ1SFgBJXIG0BzoEDrHLac9I,737
6
+ mkdocs_ezlinks_plugin/scanners/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ mkdocs_ezlinks_plugin/scanners/base_link_scanner.py,sha256=zHn496tkvJNfLQ6Q4CCCR-W4qVAY3_pHXikbOgeldRs,362
8
+ mkdocs_ezlinks_plugin/scanners/md_link_scanner.py,sha256=jzlwKC67Nn2461zG88ByGjfQHDvmflc5Bm0hrg3zuhA,2316
9
+ mkdocs_ezlinks_plugin/scanners/reference_link_scanner.py,sha256=8twH0QETx6Zj1iUV8YUgPOetmOPcqepk5-RJPFgvaSc,1466
10
+ mkdocs_ezlinks_plugin/scanners/wiki_link_scanner.py,sha256=hgf_skV0unz-00pbqUf54qo4whwxVnkA91v-vmAn3ZA,2118
11
+ mkdocs_wikilinks_plugin-0.1.0.dist-info/LICENSE,sha256=moLBUL6d6yHy1m8G5xb0obEQq6nB_BVXY1V3NPo6l2Q,1067
12
+ mkdocs_wikilinks_plugin-0.1.0.dist-info/METADATA,sha256=X-GUjxwAnHxZFCeVOZFHD3dVDmMIY9mK8I1qTHtvc5k,8590
13
+ mkdocs_wikilinks_plugin-0.1.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
14
+ mkdocs_wikilinks_plugin-0.1.0.dist-info/entry_points.txt,sha256=ieCaPCi59TMrpwzH6ydAtzrqe9Cp2CcftcxYzhO7XE0,70
15
+ mkdocs_wikilinks_plugin-0.1.0.dist-info/top_level.txt,sha256=3D2Vwzw60-FXxJ9CxUQrlEkOxwQR3zt10EglqyGuAYg,22
16
+ mkdocs_wikilinks_plugin-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.3.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [mkdocs.plugins]
2
+ ezlinks = mkdocs_ezlinks_plugin.plugin:EzLinksPlugin
@@ -0,0 +1 @@
1
+ mkdocs_ezlinks_plugin