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.
- markdown_javadoc_references-0.1/LICENSE +21 -0
- markdown_javadoc_references-0.1/PKG-INFO +113 -0
- markdown_javadoc_references-0.1/README.md +94 -0
- markdown_javadoc_references-0.1/pyproject.toml +25 -0
- markdown_javadoc_references-0.1/src/markdown_javadoc_references/__init__.py +6 -0
- markdown_javadoc_references-0.1/src/markdown_javadoc_references/docsite/docsite.py +12 -0
- markdown_javadoc_references-0.1/src/markdown_javadoc_references/docsite/jdk8.py +93 -0
- markdown_javadoc_references-0.1/src/markdown_javadoc_references/docsite/jdk9.py +117 -0
- markdown_javadoc_references-0.1/src/markdown_javadoc_references/docsite/util.py +12 -0
- markdown_javadoc_references-0.1/src/markdown_javadoc_references/entities.py +20 -0
- markdown_javadoc_references-0.1/src/markdown_javadoc_references/extension.py +17 -0
- markdown_javadoc_references-0.1/src/markdown_javadoc_references/processor.py +120 -0
- markdown_javadoc_references-0.1/src/markdown_javadoc_references/reference.py +53 -0
|
@@ -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,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,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
|