markdown_javadoc_references 0.1__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Nick Hensel
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,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: markdown_javadoc_references
3
+ Version: 0.1
4
+ Summary:
5
+ License-File: LICENSE
6
+ Author: Nick Hensel
7
+ Requires-Python: >=3.9
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Dist: beautifulsoup4
16
+ Requires-Dist: markdown
17
+ Requires-Dist: requests
18
+ Description-Content-Type: text/markdown
19
+
20
+ # 🐍 Python Markdown Javadoc References
21
+ Have you ever been tired of copying entire Javadoc URLs into your Markdown just to link to the documentation for some Java classes?
22
+ That is where is extension comes in handy!
23
+
24
+ This [python markdown extension](https://github.com/Python-Markdown/markdown) adds the ability to directly reference [Java](https://www.java.com/de/) classes, methods and fields in
25
+ your markdown to link to their documentation. It supports both Javadoc prior to Java 9 and from Java 9 onwards.
26
+
27
+ ## Installation
28
+ You can find the [extension on PyPi](https://pypi.org/project/markdown_javadoc_references/) under `markdown_javadoc_references`.
29
+
30
+ ### With plain [python markdown](https://github.com/jackdewinter/pymarkdown)
31
+ To use this extension with the API of [python markdown](https://github.com/jackdewinter/pymarkdown), just add
32
+ `JavaDocRefExtension` to the list of your extensions and provide the URLs to use.
33
+
34
+ ```python
35
+ import markdown
36
+ from markdown_javadoc_references import JavaDocRefExtension
37
+
38
+ urls = [
39
+ 'https://docs.oracle.com/en/java/javase/24/docs/api/',
40
+ {
41
+ 'alias': 'jdk8',
42
+ 'url': 'https://docs.oracle.com/javase/8/docs/api/'
43
+ }
44
+ ]
45
+
46
+ text = 'your markdown text'
47
+ result = markdown.markdown(text, extensions=[JavaDocRefExtension(urls=urls)])
48
+ ```
49
+
50
+
51
+ ### With [MkDocs](https://www.mkdocs.org/)
52
+ To use this extension with [MkDocs](https://www.mkdocs.org/) just add it to your `mkdocs.yml`:
53
+
54
+ ```yaml
55
+ markdown_extensions:
56
+ - markdown_javadoc_references:
57
+ - urls:
58
+ - 'https://docs.oracle.com/en/java/javase/24/docs/api/'
59
+ - alias: 'jdk8'
60
+ url: 'https://docs.oracle.com/javase/8/docs/api/'
61
+ ```
62
+
63
+ ## Usage
64
+ Referencing java methods, classes or fields is similar to how it is done in normal javadoc comments, for example
65
+ `[String#concat(String)](String#concat(String))` will result in [String#concat(String)](https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/String.html#concat(java.lang.String))
66
+
67
+ ### Autolinks
68
+ Often times the text presented to the user is the same as the javadoc reference.
69
+ For this common case you can use the autolink syntax to avoid writing it twice.
70
+
71
+ `<String#concat(String)>` is the same as `[String#concat(String)](String#concat(String))`
72
+
73
+ ### Packages
74
+ To clarify which class to use, you can add a package in front of it:
75
+ `<java.lang.String>`.
76
+
77
+ Furthermore, you can also a package to method parameters:
78
+ `<String#concact(java.lang.String)`
79
+
80
+ > [!WARNING]
81
+ > If multiple matches are found for a reference, the reference will be marked as "Invalid"!
82
+
83
+ ### Fields
84
+ Like methods, fields can be referred to in a similar style with the small detail of double `#`: `<String##CASE_INSENSITIVE_ORDER>` will link to [String#CASE_INSENSITIVE_ORDER](https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/String.html#CASE_INSENSITIVE_ORDER)
85
+
86
+ The double `#` is necessary to avoid conflicts with markdowns native headline linking.
87
+
88
+ ### Constructors
89
+ To refer to constructors, just add `<init>` in the place where the method name would be:
90
+ `String#<init>(byte[],int,int)` will link to [String#<init>(byte[],int,int)](https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/String.html#%3Cinit%3E(byte%5B%5D,int,int))
91
+
92
+ ### URL aliases
93
+ Instead of stating the package explicitly, you can also add a URL alias to your reference.
94
+ For that to work, you have to state the alias for your javadoc site in your [configuration. (take a look at installation)](#installation)
95
+
96
+ Assuming you have a javadoc site configured with the alias "jdk8":
97
+ ```yaml
98
+ markdown_extensions:
99
+ - markdown_javadoc_references:
100
+ - urls:
101
+ - 'https://docs.oracle.com/en/java/javase/24/docs/api/'
102
+ - alias: 'jdk8'
103
+ url: 'https://docs.oracle.com/javase/8/docs/api/'
104
+ ```
105
+
106
+ you can now use this alias to force the extension to only search under the site `https://docs.oracle.com/javase/8/docs/api/`
107
+ for the referred to javadoc: `<jdk8 -> String#<init>(byte[],int,int)>`
108
+
109
+ Additionally, if you don't have an alias configured explicitly, you can still use the whole URL:
110
+ `<https://docs.oracle.com/en/java/javase/24/docs/api/ -> String#<init>(byte[],int,int)>`
111
+
112
+ > [!IMPORTANT]
113
+ > The URL has to be still mentioned in the configuration!
@@ -0,0 +1,94 @@
1
+ # 🐍 Python Markdown Javadoc References
2
+ Have you ever been tired of copying entire Javadoc URLs into your Markdown just to link to the documentation for some Java classes?
3
+ That is where is extension comes in handy!
4
+
5
+ This [python markdown extension](https://github.com/Python-Markdown/markdown) adds the ability to directly reference [Java](https://www.java.com/de/) classes, methods and fields in
6
+ your markdown to link to their documentation. It supports both Javadoc prior to Java 9 and from Java 9 onwards.
7
+
8
+ ## Installation
9
+ You can find the [extension on PyPi](https://pypi.org/project/markdown_javadoc_references/) under `markdown_javadoc_references`.
10
+
11
+ ### With plain [python markdown](https://github.com/jackdewinter/pymarkdown)
12
+ To use this extension with the API of [python markdown](https://github.com/jackdewinter/pymarkdown), just add
13
+ `JavaDocRefExtension` to the list of your extensions and provide the URLs to use.
14
+
15
+ ```python
16
+ import markdown
17
+ from markdown_javadoc_references import JavaDocRefExtension
18
+
19
+ urls = [
20
+ 'https://docs.oracle.com/en/java/javase/24/docs/api/',
21
+ {
22
+ 'alias': 'jdk8',
23
+ 'url': 'https://docs.oracle.com/javase/8/docs/api/'
24
+ }
25
+ ]
26
+
27
+ text = 'your markdown text'
28
+ result = markdown.markdown(text, extensions=[JavaDocRefExtension(urls=urls)])
29
+ ```
30
+
31
+
32
+ ### With [MkDocs](https://www.mkdocs.org/)
33
+ To use this extension with [MkDocs](https://www.mkdocs.org/) just add it to your `mkdocs.yml`:
34
+
35
+ ```yaml
36
+ markdown_extensions:
37
+ - markdown_javadoc_references:
38
+ - urls:
39
+ - 'https://docs.oracle.com/en/java/javase/24/docs/api/'
40
+ - alias: 'jdk8'
41
+ url: 'https://docs.oracle.com/javase/8/docs/api/'
42
+ ```
43
+
44
+ ## Usage
45
+ Referencing java methods, classes or fields is similar to how it is done in normal javadoc comments, for example
46
+ `[String#concat(String)](String#concat(String))` will result in [String#concat(String)](https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/String.html#concat(java.lang.String))
47
+
48
+ ### Autolinks
49
+ Often times the text presented to the user is the same as the javadoc reference.
50
+ For this common case you can use the autolink syntax to avoid writing it twice.
51
+
52
+ `<String#concat(String)>` is the same as `[String#concat(String)](String#concat(String))`
53
+
54
+ ### Packages
55
+ To clarify which class to use, you can add a package in front of it:
56
+ `<java.lang.String>`.
57
+
58
+ Furthermore, you can also a package to method parameters:
59
+ `<String#concact(java.lang.String)`
60
+
61
+ > [!WARNING]
62
+ > If multiple matches are found for a reference, the reference will be marked as "Invalid"!
63
+
64
+ ### Fields
65
+ Like methods, fields can be referred to in a similar style with the small detail of double `#`: `<String##CASE_INSENSITIVE_ORDER>` will link to [String#CASE_INSENSITIVE_ORDER](https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/String.html#CASE_INSENSITIVE_ORDER)
66
+
67
+ The double `#` is necessary to avoid conflicts with markdowns native headline linking.
68
+
69
+ ### Constructors
70
+ To refer to constructors, just add `<init>` in the place where the method name would be:
71
+ `String#<init>(byte[],int,int)` will link to [String#<init>(byte[],int,int)](https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/String.html#%3Cinit%3E(byte%5B%5D,int,int))
72
+
73
+ ### URL aliases
74
+ Instead of stating the package explicitly, you can also add a URL alias to your reference.
75
+ For that to work, you have to state the alias for your javadoc site in your [configuration. (take a look at installation)](#installation)
76
+
77
+ Assuming you have a javadoc site configured with the alias "jdk8":
78
+ ```yaml
79
+ markdown_extensions:
80
+ - markdown_javadoc_references:
81
+ - urls:
82
+ - 'https://docs.oracle.com/en/java/javase/24/docs/api/'
83
+ - alias: 'jdk8'
84
+ url: 'https://docs.oracle.com/javase/8/docs/api/'
85
+ ```
86
+
87
+ you can now use this alias to force the extension to only search under the site `https://docs.oracle.com/javase/8/docs/api/`
88
+ for the referred to javadoc: `<jdk8 -> String#<init>(byte[],int,int)>`
89
+
90
+ Additionally, if you don't have an alias configured explicitly, you can still use the whole URL:
91
+ `<https://docs.oracle.com/en/java/javase/24/docs/api/ -> String#<init>(byte[],int,int)>`
92
+
93
+ > [!IMPORTANT]
94
+ > The URL has to be still mentioned in the configuration!
@@ -0,0 +1,25 @@
1
+ [project]
2
+ name = "markdown_javadoc_references"
3
+ version = "0.1"
4
+
5
+ dependencies = [
6
+ "requests",
7
+ "markdown",
8
+ "beautifulsoup4"
9
+ ]
10
+
11
+ authors = [
12
+ {name = "Nick Hensel"}
13
+ ]
14
+ readme = "README.md"
15
+ requires-python = ">=3.9"
16
+
17
+ [build-system]
18
+ requires = ["poetry-core"]
19
+ build-backend = "poetry.core.masonry.api"
20
+
21
+ [dependency-groups]
22
+ dev = [
23
+ "pytest-xdist (>=3.8.0,<4.0.0)",
24
+ "pytest (>=8.4.2,<9.0.0)"
25
+ ]
@@ -0,0 +1,6 @@
1
+ from .extension import JavaDocRefExtension
2
+
3
+ __all__ = ["JavaDocRefExtension"]
4
+
5
+ def makeExtension(**kwargs):
6
+ return JavaDocRefExtension(**kwargs)
@@ -0,0 +1,12 @@
1
+ from functools import lru_cache
2
+
3
+ from .jdk8 import load as jdk8_load
4
+ from .jdk9 import load as jdk9_load
5
+ from .util import check_url
6
+
7
+
8
+ @lru_cache(maxsize=None)
9
+ def load(url):
10
+ # /allclasses-noframe.html only exists pre java 9
11
+ existing = check_url(f'{url}/allclasses-noframe.html')
12
+ return jdk8_load(url) if existing else jdk9_load(url)
@@ -0,0 +1,93 @@
1
+ import logging
2
+ import urllib.parse
3
+ from concurrent.futures import ThreadPoolExecutor
4
+
5
+ from bs4 import BeautifulSoup
6
+
7
+ from .util import read_url
8
+ from ..entities import *
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def load(url):
14
+ # info is intended
15
+ logger.info(f'Loading java 8 docs.. may take a while: {url}')
16
+
17
+ soup = BeautifulSoup(read_url(f'{url}/allclasses-noframe.html'), 'html.parser')
18
+
19
+ klasses = dict()
20
+
21
+ for anchor in soup.find('body').find('div').find('ul').select('a[href]'):
22
+ klass = load_class(url, anchor)
23
+ klasses.setdefault(klass.name, []).append(klass)
24
+
25
+ return Jdk8(klasses)
26
+
27
+
28
+ def load_class(url, c):
29
+ name = c.get_text(strip=True)
30
+ package = c.get('title').split()[-1]
31
+
32
+ klass = Klass(None, package, name, None, None, f'{url}/{c.get('href')}')
33
+ logger.info(f'Loading {package}.{name} from {klass.url}')
34
+
35
+ return klass
36
+
37
+
38
+ def load_members(url, klass):
39
+ text = read_url(url)
40
+ klass.methods = list()
41
+ klass.fields = list()
42
+
43
+ soup = BeautifulSoup(text, "html.parser")
44
+ anchors = {a.get("name") for a in soup.find_all("a", attrs={"name": True})}
45
+ for a in anchors:
46
+ parts = a.split('-')
47
+ unquoted_url = urllib.parse.unquote(f'{url}#{a}')
48
+ # first part is always name
49
+ member_name = parts[0]
50
+
51
+ # is field
52
+ if len(parts) <= 1:
53
+ klass.fields.append(Field(member_name, unquoted_url))
54
+
55
+ if member_name == klass.name: member_name = '<init>' # normalize constructor method names to <init>
56
+ new_params = []
57
+
58
+ # following parts are parameters
59
+ for param in parts[1:]:
60
+
61
+ # skip empty strings - they're not real parameters
62
+ if len(param) == 0: continue
63
+
64
+ # split at : - after it there are metadata like A for arrays
65
+ name_split = param.split(':')
66
+ new_name = name_split[0]
67
+
68
+ # add [] for arrays
69
+ if len(name_split) == 2:
70
+ new_name = new_name + ('[]' * (name_split[1].count('A')))
71
+
72
+ new_params.append(new_name.strip())
73
+
74
+
75
+ klass.methods.append(Method(klass, member_name, new_params, unquoted_url))
76
+
77
+ class Jdk8:
78
+ def __init__(self, klasses):
79
+ self.klasses = klasses
80
+
81
+ # lazy load
82
+ def klasses_for_ref(self, reference):
83
+ if reference.class_name not in self.klasses: return None
84
+ found = self.klasses[reference.class_name]
85
+
86
+ loaded = list()
87
+ for klass in found:
88
+ # none if unloaded
89
+ if klass.methods is None:
90
+ load_members(klass.url, klass)
91
+ loaded.append(klass)
92
+
93
+ return loaded
@@ -0,0 +1,117 @@
1
+ import json
2
+ import logging
3
+ import urllib.parse
4
+
5
+ from .util import read_url
6
+ from ..entities import *
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def load(url):
12
+ logger.debug(f'Load java 9 doc: {url}')
13
+
14
+ packages = load_packages(url)
15
+ members = load_members(url)
16
+
17
+ klasses = load_classes(url, packages, members)
18
+ return Jdk9(klasses)
19
+
20
+
21
+ def read_url_json(url, prefix):
22
+ text = read_url(url)
23
+ plain = text.removeprefix(prefix).removesuffix(';updateSearchResults();').strip()
24
+ return json.loads(plain)
25
+
26
+
27
+ def find_module(name, pkgs):
28
+ e = pkgs[name]
29
+ if 'm' in e:
30
+ return e['m']
31
+ return None
32
+
33
+
34
+ def load_members(url):
35
+ data = read_url_json(url + '/member-search-index.js', 'memberSearchIndex = ')
36
+
37
+ index = dict()
38
+ for e in data:
39
+ if 'l' not in e or 'p' not in e: continue
40
+ index.setdefault(f'{e['p']}.{e['c']}', list()).append(e)
41
+ return index
42
+
43
+
44
+ def load_classes(url, pkgs, members):
45
+ data = read_url_json(url + '/type-search-index.js', 'typeSearchIndex = ')
46
+ klasses = dict()
47
+
48
+ for e in data:
49
+ # skip non member entries
50
+ if 'l' not in e or 'p' not in e: continue
51
+ name = e['l']
52
+ package = e['p']
53
+ module = find_module(package, pkgs)
54
+ methods = list()
55
+ fields = list()
56
+
57
+ klass_url = build_klass_url(url, module, package, name)
58
+ klass = Klass(module, package, name, methods, fields, klass_url)
59
+
60
+ i = f'{package}.{name}'
61
+
62
+ # check if class has members
63
+ if i in members:
64
+
65
+ # get through all members of class
66
+ for m in members[i]:
67
+ # u in only included if reference types are parameters. No parameters + only primitives -> l
68
+ index = 'u' if 'u' in m else 'l'
69
+ m_name = urllib.parse.unquote(m[index].split('(', 1)[0]) ## get name -> split at ( and get first half
70
+ parameters = list()
71
+
72
+ raw = m[index]
73
+ if '(' in raw: # just exclude fields for now
74
+ u_split = raw.split('(', 1)[1].removesuffix(')')
75
+ if len(u_split) != 0:
76
+ for p in u_split.split(','):
77
+ parameters.append(p.strip())
78
+ methods.append(Method(klass, m_name, parameters, build_method_url(klass_url, m)))
79
+ else: # is field
80
+ fields.append(Field(m_name, build_field_url(klass_url, m_name)))
81
+
82
+ klasses.setdefault(name, list()).append(klass)
83
+
84
+ return klasses
85
+
86
+
87
+ def build_klass_url(base, module, package, klass_name):
88
+ # append module name if given
89
+ if module is not None: base = f'{base}/{module}'
90
+ # append package name
91
+ base = f'{base}/{package.replace('.', '/')}'
92
+ # append class name
93
+ base = f'{base}/{klass_name}'
94
+
95
+ # append .html
96
+ return base + '.html'
97
+
98
+ def build_method_url(klass_url, m):
99
+ return f'{klass_url}#{(m['u'] if 'u' in m else m['l'])}'
100
+
101
+ def build_field_url(klass_url, field_name):
102
+ return f'{klass_url}#{field_name}'
103
+
104
+ def load_packages(url):
105
+ data = read_url_json(url + '/package-search-index.js', 'packageSearchIndex = ')
106
+
107
+ index = dict()
108
+ for e in data:
109
+ index[e['l']] = e
110
+ return index
111
+
112
+ class Jdk9:
113
+ def __init__(self, klasses):
114
+ self.klasses = klasses
115
+
116
+ def klasses_for_ref(self, reference):
117
+ return self.klasses[reference.class_name] if reference.class_name in self.klasses else None
@@ -0,0 +1,12 @@
1
+ import requests
2
+
3
+
4
+ def read_url(url):
5
+ resp = requests.get(url)
6
+ resp.raise_for_status()
7
+ return resp.text
8
+
9
+
10
+ def check_url(url):
11
+ resp = requests.head(url)
12
+ return resp.ok
@@ -0,0 +1,20 @@
1
+ class Klass:
2
+ def __init__(self, module, package, name, methods, fields, url):
3
+ self.module = module
4
+ self.package = package
5
+ self.name = name
6
+ self.methods = methods
7
+ self.url = url
8
+ self.fields = fields
9
+
10
+ class Field:
11
+ def __init__(self, name, url):
12
+ self.name = name
13
+ self.url = url
14
+
15
+ class Method:
16
+ def __init__(self, klass, name, parameters, url):
17
+ self.klass = klass
18
+ self.name = name
19
+ self.parameters = parameters
20
+ self.url = url
@@ -0,0 +1,17 @@
1
+ from markdown.extensions import Extension
2
+
3
+ from .processor import JavaDocProcessor, AutoLinkJavaDocProcessor
4
+
5
+
6
+ class JavaDocRefExtension(Extension):
7
+ def __init__(self, **kwargs):
8
+ self.config = {
9
+ 'urls': [[], 'A list of javadoc sites to search in.']
10
+ }
11
+
12
+ super().__init__(**kwargs)
13
+
14
+ def extendMarkdown(self, md):
15
+ md.treeprocessors.register(JavaDocProcessor(md, self.getConfig("urls")), 'javadoc_reference_processor', 15)
16
+
17
+ md.inlinePatterns.register(AutoLinkJavaDocProcessor(md), 'javadoc_reference_autolink_processor', 140)
@@ -0,0 +1,120 @@
1
+ import logging
2
+ from concurrent.futures import ThreadPoolExecutor
3
+ import xml.etree.ElementTree as etree
4
+
5
+ from markdown.treeprocessors import Treeprocessor
6
+ from markdown.inlinepatterns import InlineProcessor
7
+
8
+ from .docsite import docsite
9
+ from .reference import create_or_none
10
+ from .reference import Type
11
+
12
+ from .reference import raw_pattern as ref_pattern
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def match(klasses, reference):
18
+ # search in each class
19
+ for klass in klasses:
20
+ # compare package if given
21
+ if reference.package is not None and (klass.package != reference.package): continue
22
+ # compare method if given
23
+ if reference.member_name is not None:
24
+ if reference.type == Type.METHOD:
25
+ # get all methods for class
26
+ methods = klass.methods
27
+ # search in each member
28
+ for method in methods:
29
+ # compare method name
30
+ if reference.member_name != method.name: continue
31
+ # compare parameter size
32
+ if len(reference.parameters) != len(method.parameters): continue
33
+
34
+ # compare parameters
35
+ parameter_match = True
36
+ for r_p, m_p in zip(reference.parameters, method.parameters):
37
+ if not m_p.endswith(r_p): parameter_match = False
38
+ if not parameter_match: continue
39
+
40
+ return method.url
41
+ else:
42
+ # get all fields
43
+ fields = klass.fields
44
+
45
+ # compare each field
46
+ for field in fields:
47
+ if reference.member_name != field.name: continue
48
+ return field.url
49
+
50
+ else: # if not given, just reference found class
51
+ return klass.url
52
+
53
+ return None
54
+
55
+ def process_url(url):
56
+ stripped_url = url.removesuffix('/')
57
+ return docsite.load(stripped_url)
58
+
59
+ class JavaDocProcessor(Treeprocessor):
60
+ def __init__(self, md, urls):
61
+ super().__init__(md)
62
+ self.sites = dict()
63
+
64
+ for entry in urls:
65
+ if isinstance(entry, str):
66
+ site = process_url(entry)
67
+ self.sites[entry.strip()] = site
68
+ elif isinstance(entry, dict) and 'alias' in entry and 'url' in entry:
69
+ site = process_url(entry['url'])
70
+ self.sites[entry['alias'].strip()] = site
71
+ else:
72
+ raise TypeError(
73
+ f"Invalid entry in urls config: {entry!r}. "
74
+ f"Expected string or dict with 'alias' and 'url'."
75
+ )
76
+
77
+
78
+ def run(self, root):
79
+ for el in root.iter('a'):
80
+ href = el.get('href', '')
81
+ reference = create_or_none(href)
82
+ if reference is not None:
83
+ links = self.find_matching_javadoc(reference)
84
+
85
+ if len(links) == 0:
86
+ logger.warning(f'No javadoc matching {href} was found!')
87
+ el.text = f'Invalid reference to {href}'
88
+ elif len(links) > 1:
89
+ logger.warning(
90
+ f'Multiple javadoc matching {href} found! Please be more specific, maybe add a pacakge? Found javadocs: {'; '.join(links)}')
91
+ el.text = f'Invalid reference to {href}'
92
+ else:
93
+ url = links[0]
94
+ el.set('href', url)
95
+
96
+ def find_matching_javadoc(self, reference):
97
+ matches = list()
98
+ for alias, site in self.sites.items():
99
+ if reference.javadoc_alias is not None:
100
+ if alias != reference.javadoc_alias: continue
101
+
102
+ klasses = site.klasses_for_ref(reference)
103
+ if klasses is None: continue
104
+ link = match(klasses, reference)
105
+ if link is not None: matches.append(link)
106
+ return matches
107
+
108
+
109
+ auto_link_pattern = rf'<({ref_pattern[:-1]})>$'
110
+ class AutoLinkJavaDocProcessor(InlineProcessor):
111
+ def __init__(self, md):
112
+ super().__init__(auto_link_pattern, md)
113
+
114
+ def handleMatch(self, m, data):
115
+ text = m.group(1)
116
+ el = etree.Element('a')
117
+ el.set('href', text)
118
+ # replace ## with # - ## is used for field referring
119
+ el.text = m.group('whole_ref').replace('##', '#')
120
+ return el, m.start(0), m.end(0)
@@ -0,0 +1,53 @@
1
+ from enum import Enum
2
+
3
+ import re
4
+
5
+ raw_pattern = r'(?:(?P<doc>.+) *-> *)?(?P<whole_ref>(?P<pkg>[\w.]*\.)?(?P<klass>\w+)(?:#(?:(?P<method><?\w+>?)\((?P<params>.*)\))|(?:##(?P<field>\w+)))?)$'
6
+ pattern = re.compile(raw_pattern)
7
+
8
+ def create_or_none(raw):
9
+ match = pattern.match(raw)
10
+ return Reference(match) if match else None
11
+
12
+
13
+ class Reference:
14
+ def __init__(self, match: re.Match):
15
+ """
16
+ regex will match: alias -> java.util.com.MyClass#foo(String,int,boolean) -->
17
+
18
+ group doc: javadoc alias name
19
+ group pkg: package (optional)
20
+ group klass: class name
21
+ group method: method name (optional, together with parameters)
22
+ group params: parameters (optional, together with method name)
23
+
24
+ if field reference: java.util.com.MyClass#MY_FIELD
25
+ group field: field name
26
+ """
27
+
28
+ self.javadoc_alias = match.group('doc')
29
+ if self.javadoc_alias is not None:
30
+ self.javadoc_alias = self.javadoc_alias.strip()
31
+
32
+ self.package = match.group('pkg')
33
+ if self.package is not None: self.package = self.package.removesuffix('.')
34
+
35
+ self.class_name = match.group('klass')
36
+
37
+ if match.group('field') is None:
38
+ self.member_name = match.group('method')
39
+
40
+ self.parameters = list()
41
+
42
+ parameter = match.group('params')
43
+ if parameter is not None and parameter != '':
44
+ for par in parameter.split(','):
45
+ self.parameters.append(par.strip())
46
+ self.type = Type.METHOD
47
+ else:
48
+ self.type = Type.FIELD
49
+ self.member_name = match.group('field')
50
+
51
+ class Type(Enum):
52
+ METHOD = 1
53
+ FIELD = 2