pelican-linkclass 2.1.3__tar.gz → 2.1.5__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.

Potentially problematic release.


This version of pelican-linkclass might be problematic. Click here for more details.

@@ -0,0 +1,72 @@
1
+ CHANGELOG
2
+ =========
3
+
4
+ 2.1.5 - 2025-01-20
5
+ ------------------
6
+
7
+ This is a maintenance release. No user-visible changes have been made. This release tests the new system to keep alignment with plugin template via Cruft.
8
+
9
+ 2.1.4 - 2024-04-14
10
+ ------------------
11
+
12
+ Ensure plugin is included in sdist build
13
+
14
+ 2.1.3 - 2024-04-07
15
+ ------------------
16
+
17
+ Maintainance release (no user visible changes)
18
+
19
+ * Add Python 3.12 to CI test matrix
20
+ * Switch build system from Hatchling to PDM
21
+ * Fix linters errors
22
+
23
+ 2.1.2 - 2023-10-31
24
+ ------------------
25
+
26
+ Fix project name
27
+
28
+ 2.1.1 - 2023-10-31
29
+ ------------------
30
+
31
+ Maintenance release:
32
+
33
+ - Migrate to the new tooling standards
34
+ - Improve code quality
35
+
36
+ 2.1.0 - 2023-04-13
37
+ ------------------
38
+
39
+ The tuples in the LINKCLASS configuration variable have only two elements now. This makes the code consistent with the plugin documentation (README.md file) and resolves issue #34.
40
+
41
+ 2.0.4 - 2022-10-01
42
+ ------------------
43
+
44
+ Update list of years in Copyright notices
45
+
46
+ 2.0.3 - 2022-07-15
47
+ ------------------
48
+
49
+ In `test_linkclass.py`, use `io` instead of `six` to import `StringIO`.
50
+
51
+ 2.0.2 - 2021-12-17
52
+ ------------------
53
+
54
+ Upgrade code to Python 3.6+ (thanks to Justin Mayer)
55
+
56
+ 2.0.1 - 2021-11-04
57
+ ------------------
58
+
59
+ Maintenance release: Use .format() instead of % operator to format strings
60
+
61
+ 2.0.0 - 2021-02-17
62
+ ------------------
63
+
64
+ Convert to namespace plugin for use with Pelican 4.5 and above
65
+
66
+ [Justin Mayer](https://github.com/justinmayer) [PR #11](https://github.com/pelican-plugins/linkclass/pull/11/)
67
+
68
+
69
+ 1.0.0 - 2019-09-29
70
+ ------------------
71
+
72
+ Initial release to PyPI
@@ -0,0 +1,9 @@
1
+ Contributing
2
+ ============
3
+
4
+ Contributions are welcome and much appreciated. Every little bit helps. You can contribute by improving the documentation, adding missing features, and fixing bugs. You can also help out by reviewing and commenting on [existing issues][].
5
+
6
+ To start contributing to this plugin, review the [Contributing to Pelican][] documentation, beginning with the **Contributing Code** section.
7
+
8
+ [existing issues]: https://github.com/pelican-plugins/linkclass/issues
9
+ [Contributing to Pelican]: https://docs.getpelican.com/en/latest/contribute.html
@@ -1,33 +1,34 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pelican-linkclass
3
- Version: 2.1.3
3
+ Version: 2.1.5
4
4
  Summary: Pelican plugin to set anchor tag's class attribute to differentiate between internal and external links
5
- Keywords: pelican plugin link class
6
- Author-Email: Rafael Laboissière <rafael@laboissiere.net>
5
+ Keywords: pelican,plugin,link class
6
+ Author-Email: =?utf-8?q?Rafael_Laboissi=C3=A8re?= <rafael@laboissiere.net>
7
7
  License: AGPL-3.0
8
8
  Classifier: Development Status :: 5 - Production/Stable
9
9
  Classifier: Environment :: Console
10
10
  Classifier: Framework :: Pelican
11
11
  Classifier: Framework :: Pelican :: Plugins
12
12
  Classifier: Intended Audience :: End Users/Desktop
13
- Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
13
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.8
17
16
  Classifier: Programming Language :: Python :: 3.9
18
17
  Classifier: Programming Language :: Python :: 3.10
19
18
  Classifier: Programming Language :: Python :: 3.11
20
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Topic :: Internet :: WWW/HTTP
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Project-URL: Homepage, https://github.com/pelican-plugins/linkclass
24
- Project-URL: Issue tracker, https://github.com/pelican-plugins/linkclass/issues
24
+ Project-URL: Issue Tracker, https://github.com/pelican-plugins/linkclass/issues
25
+ Project-URL: Changelog, https://github.com/pelican-plugins/linkclass/blob/main/CHANGELOG.md
25
26
  Project-URL: Funding, https://donate.getpelican.com/
26
- Requires-Python: <4.0,>=3.8.1
27
+ Requires-Python: <4.0,>=3.9
27
28
  Requires-Dist: pelican>=4.5
28
29
  Requires-Dist: py3dns>=3.2
29
- Requires-Dist: markdown>=3.4; extra == "markdown"
30
30
  Provides-Extra: markdown
31
+ Requires-Dist: markdown>=3.4; extra == "markdown"
31
32
  Description-Content-Type: text/markdown
32
33
 
33
34
  Link Class: A Plugin for Pelican
@@ -35,55 +36,49 @@ Link Class: A Plugin for Pelican
35
36
 
36
37
  [![Build Status](https://img.shields.io/github/actions/workflow/status/pelican-plugins/linkclass/main.yml?branch=main)](https://github.com/pelican-plugins/linkclass/actions)
37
38
  [![PyPI Version](https://img.shields.io/pypi/v/pelican-linkclass)](https://pypi.org/project/pelican-linkclass/)
38
- ![License](https://img.shields.io/pypi/l/pelican-linkclass?color=blue)
39
+ [![License](https://img.shields.io/pypi/l/pelican-linkclass?color=blue)](https://www.gnu.org/licenses/agpl-3.0.en.html)
40
+
41
+ This Pelican plugin allows you to set the class attribute of `<a>` elements (generated in Markdown by `[ext](link)`) according to whether the link is external (i.e., starts with `http://` or `https://`) or internal to the Pelican-generated site.
39
42
 
40
- This Pelican plugin allows you to set the class attribute of `<a>` elements
41
- (generated in Markdown by `[ext](link)`) according to whether the link is
42
- external (i.e., starts with `http://` or `https://`) or internal to the
43
- Pelican-generated site.
43
+ For now, this plugin only works with Markdown. It has been tested with version 3.0+ of the Python-Markdown module and may not work with previous versions.
44
44
 
45
- For now, this plugin only works with Markdown. It has been tested with version
46
- 3.0+ of the Python-Markdown module and may not work with previous versions.
47
45
 
48
46
  Installation
49
47
  ------------
50
48
 
51
- This plugin [is available as a package](https://pypi.org/project/pelican-linkclass/)
52
- at PyPI and can be installed via:
49
+ This plugin [is available as a package](https://pypi.org/project/pelican-linkclass/) at PyPI and can be installed via:
53
50
 
54
51
  ```
55
52
  python -m pip install pelican-linkclass
56
53
  ```
57
54
 
55
+ As long as you have not explicitly added a `PLUGINS` setting to your Pelican settings file, then the newly-installed plugin should be automatically detected and enabled. Otherwise, you must add `avatar` to your existing `PLUGINS` list. For more information, please see the [How to Use Plugins](https://docs.getpelican.com/en/latest/plugins.html#how-to-use-plugins) documentation.
56
+
57
+
58
58
  Configuration
59
59
  -------------
60
60
 
61
- In order to avoid clashes with already-defined classes in the user CSS
62
- style sheets, it is possible to specify the name of the classes that will
63
- be used. They can be specified in the Pelican setting file with the
64
- `LINKCLASS` variable, which must be defined as a list of tuples, like this:
61
+ In order to avoid clashes with already-defined classes in the user CSS style sheets, it is possible to specify the name of the classes that will be used. They can be specified in the Pelican setting file with the `LINKCLASS` variable, which must be defined as a list of tuples, like this:
65
62
 
66
63
  ```python
67
64
  'LINKCLASS' = (('EXTERNAL_CLASS', 'name-of-the-class-for-external-links'),
68
65
  ('INTERNAL_CLASS', 'name-of-the-class-for-internal-links'))
69
66
  ```
70
67
 
71
- The default values for `EXTERNAL_CLASS` and `INTERNAL_CLASS` are,
72
- respectively, `'external'` and `'internal'`.
68
+ The default values for `EXTERNAL_CLASS` and `INTERNAL_CLASS` are, respectively, `'external'` and `'internal'`.
69
+
73
70
 
74
71
  Styling Hyperlinks
75
72
  ------------------
76
73
 
77
- One of the possible uses of this plugins is for styling. Suppose that we
78
- have the following Markdown content in your article:
74
+ One of the possible uses of this plugins is for styling. Suppose that we have the following Markdown content in your article:
79
75
 
80
76
  ```markdown
81
77
  This is an [internal](internal) link and this is an
82
78
  [external](http://external.com) link.
83
79
  ```
84
80
 
85
- If the default configuration variable values are used, then one possible
86
- CSS setting could be:
81
+ If the default configuration variable values are used, then one possible CSS setting could be:
87
82
 
88
83
  ```css
89
84
  a.external:before {
@@ -92,16 +87,13 @@ a.external:before {
92
87
  }
93
88
  ```
94
89
 
95
- (The file `external-link.png` is also distributed with this plugin. To use it,
96
- copy it to the appropriate place in your web site source tree, for instance
97
- in `theme/static/images/`.)
90
+ (The file `external-link.png` is also distributed with this plugin. To use it, copy it to the appropriate place in your web site source tree, for instance in `theme/static/images/`.)
98
91
 
99
92
  Then, the result will look like the following:
100
93
 
101
94
  ![figure](https://github.com/pelican-plugins/linkclass/raw/main/linkclass-example.png)
102
95
 
103
- Note that this plugin also works with reference-style links, as in the
104
- following example:
96
+ Note that this plugin also works with reference-style links, as in the following example:
105
97
 
106
98
  ```markdown
107
99
  This is an [internal][internal] link and this is an
@@ -111,6 +103,7 @@ This is an [internal][internal] link and this is an
111
103
  [external]: http://external.com
112
104
  ```
113
105
 
106
+
114
107
  Contributing
115
108
  ------------
116
109
 
@@ -121,6 +114,7 @@ To start contributing to this plugin, review the [Contributing to Pelican][] doc
121
114
  [existing issues]: https://github.com/pelican-plugins/linkclass/issues
122
115
  [Contributing to Pelican]: https://docs.getpelican.com/en/latest/contribute.html
123
116
 
117
+
124
118
  Acknowledgments
125
119
  ---------------
126
120
 
@@ -132,10 +126,12 @@ Many thanks to [Yuliya Bagriy][] for setting up the package for [PyPI][], to [Lu
132
126
  [pytest]: https://pytest.org/
133
127
  [Justin Mayer]: https://github.com/justinmayer
134
128
 
129
+
135
130
  Author
136
131
  ------
137
132
 
138
- Copyright © 2015, 2017, 2019, 2021-2023 Rafael Laboissière (<rafael@laboissiere.net>)
133
+ Copyright © 2015, 2017, 2019, 2021-2023, 2025 Rafael Laboissière (<rafael@laboissiere.net>)
134
+
139
135
 
140
136
  License
141
137
  -------
@@ -3,55 +3,49 @@ Link Class: A Plugin for Pelican
3
3
 
4
4
  [![Build Status](https://img.shields.io/github/actions/workflow/status/pelican-plugins/linkclass/main.yml?branch=main)](https://github.com/pelican-plugins/linkclass/actions)
5
5
  [![PyPI Version](https://img.shields.io/pypi/v/pelican-linkclass)](https://pypi.org/project/pelican-linkclass/)
6
- ![License](https://img.shields.io/pypi/l/pelican-linkclass?color=blue)
6
+ [![License](https://img.shields.io/pypi/l/pelican-linkclass?color=blue)](https://www.gnu.org/licenses/agpl-3.0.en.html)
7
7
 
8
- This Pelican plugin allows you to set the class attribute of `<a>` elements
9
- (generated in Markdown by `[ext](link)`) according to whether the link is
10
- external (i.e., starts with `http://` or `https://`) or internal to the
11
- Pelican-generated site.
8
+ This Pelican plugin allows you to set the class attribute of `<a>` elements (generated in Markdown by `[ext](link)`) according to whether the link is external (i.e., starts with `http://` or `https://`) or internal to the Pelican-generated site.
9
+
10
+ For now, this plugin only works with Markdown. It has been tested with version 3.0+ of the Python-Markdown module and may not work with previous versions.
12
11
 
13
- For now, this plugin only works with Markdown. It has been tested with version
14
- 3.0+ of the Python-Markdown module and may not work with previous versions.
15
12
 
16
13
  Installation
17
14
  ------------
18
15
 
19
- This plugin [is available as a package](https://pypi.org/project/pelican-linkclass/)
20
- at PyPI and can be installed via:
16
+ This plugin [is available as a package](https://pypi.org/project/pelican-linkclass/) at PyPI and can be installed via:
21
17
 
22
18
  ```
23
19
  python -m pip install pelican-linkclass
24
20
  ```
25
21
 
22
+ As long as you have not explicitly added a `PLUGINS` setting to your Pelican settings file, then the newly-installed plugin should be automatically detected and enabled. Otherwise, you must add `avatar` to your existing `PLUGINS` list. For more information, please see the [How to Use Plugins](https://docs.getpelican.com/en/latest/plugins.html#how-to-use-plugins) documentation.
23
+
24
+
26
25
  Configuration
27
26
  -------------
28
27
 
29
- In order to avoid clashes with already-defined classes in the user CSS
30
- style sheets, it is possible to specify the name of the classes that will
31
- be used. They can be specified in the Pelican setting file with the
32
- `LINKCLASS` variable, which must be defined as a list of tuples, like this:
28
+ In order to avoid clashes with already-defined classes in the user CSS style sheets, it is possible to specify the name of the classes that will be used. They can be specified in the Pelican setting file with the `LINKCLASS` variable, which must be defined as a list of tuples, like this:
33
29
 
34
30
  ```python
35
31
  'LINKCLASS' = (('EXTERNAL_CLASS', 'name-of-the-class-for-external-links'),
36
32
  ('INTERNAL_CLASS', 'name-of-the-class-for-internal-links'))
37
33
  ```
38
34
 
39
- The default values for `EXTERNAL_CLASS` and `INTERNAL_CLASS` are,
40
- respectively, `'external'` and `'internal'`.
35
+ The default values for `EXTERNAL_CLASS` and `INTERNAL_CLASS` are, respectively, `'external'` and `'internal'`.
36
+
41
37
 
42
38
  Styling Hyperlinks
43
39
  ------------------
44
40
 
45
- One of the possible uses of this plugins is for styling. Suppose that we
46
- have the following Markdown content in your article:
41
+ One of the possible uses of this plugins is for styling. Suppose that we have the following Markdown content in your article:
47
42
 
48
43
  ```markdown
49
44
  This is an [internal](internal) link and this is an
50
45
  [external](http://external.com) link.
51
46
  ```
52
47
 
53
- If the default configuration variable values are used, then one possible
54
- CSS setting could be:
48
+ If the default configuration variable values are used, then one possible CSS setting could be:
55
49
 
56
50
  ```css
57
51
  a.external:before {
@@ -60,16 +54,13 @@ a.external:before {
60
54
  }
61
55
  ```
62
56
 
63
- (The file `external-link.png` is also distributed with this plugin. To use it,
64
- copy it to the appropriate place in your web site source tree, for instance
65
- in `theme/static/images/`.)
57
+ (The file `external-link.png` is also distributed with this plugin. To use it, copy it to the appropriate place in your web site source tree, for instance in `theme/static/images/`.)
66
58
 
67
59
  Then, the result will look like the following:
68
60
 
69
61
  ![figure](https://github.com/pelican-plugins/linkclass/raw/main/linkclass-example.png)
70
62
 
71
- Note that this plugin also works with reference-style links, as in the
72
- following example:
63
+ Note that this plugin also works with reference-style links, as in the following example:
73
64
 
74
65
  ```markdown
75
66
  This is an [internal][internal] link and this is an
@@ -79,6 +70,7 @@ This is an [internal][internal] link and this is an
79
70
  [external]: http://external.com
80
71
  ```
81
72
 
73
+
82
74
  Contributing
83
75
  ------------
84
76
 
@@ -89,6 +81,7 @@ To start contributing to this plugin, review the [Contributing to Pelican][] doc
89
81
  [existing issues]: https://github.com/pelican-plugins/linkclass/issues
90
82
  [Contributing to Pelican]: https://docs.getpelican.com/en/latest/contribute.html
91
83
 
84
+
92
85
  Acknowledgments
93
86
  ---------------
94
87
 
@@ -100,10 +93,12 @@ Many thanks to [Yuliya Bagriy][] for setting up the package for [PyPI][], to [Lu
100
93
  [pytest]: https://pytest.org/
101
94
  [Justin Mayer]: https://github.com/justinmayer
102
95
 
96
+
103
97
  Author
104
98
  ------
105
99
 
106
- Copyright © 2015, 2017, 2019, 2021-2023 Rafael Laboissière (<rafael@laboissiere.net>)
100
+ Copyright © 2015, 2017, 2019, 2021-2023, 2025 Rafael Laboissière (<rafael@laboissiere.net>)
101
+
107
102
 
108
103
  License
109
104
  -------
@@ -0,0 +1 @@
1
+ from .linkclass import * # NOQA: F403
@@ -0,0 +1,50 @@
1
+ """Link Class Plugin for Pelican."""
2
+
3
+ # Copyright (C) 2015, 2019, 2023 Rafael Laboissière
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify it
6
+ # under the terms of the GNU General Affero Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or (at
8
+ # your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful, but
11
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with this program. If not, see http://www.gnu.org/licenses/.
17
+
18
+ from pelican import signals
19
+
20
+ from .mdx_linkclass import LC_CONFIG, LinkClassExtension
21
+
22
+
23
+ def addLinkClass(gen):
24
+ """Add LinkClass connector."""
25
+ if not gen.settings.get("MARKDOWN"):
26
+ from pelican.settings import DEFAULT_CONFIG
27
+
28
+ gen.settings["MARKDOWN"] = DEFAULT_CONFIG["MARKDOWN"]
29
+
30
+ if gen.settings.get("LINKCLASS"):
31
+ for param, default in gen.settings.get("LINKCLASS"):
32
+ LC_CONFIG[param] = default
33
+
34
+ if LinkClassExtension not in gen.settings["MARKDOWN"]:
35
+ config = {}
36
+ for key, value in LC_CONFIG.items():
37
+ config[key] = value
38
+ for key, value in gen.settings.items():
39
+ if key in LC_CONFIG:
40
+ config[key] = value
41
+ lcobj = LinkClassExtension(config)
42
+ try:
43
+ gen.settings["MARKDOWN"]["extensions"].append(lcobj)
44
+ except KeyError:
45
+ gen.settings["MARKDOWN"]["extensions"] = [lcobj]
46
+
47
+
48
+ def register():
49
+ """Register the Link Class plugin with Pelican."""
50
+ signals.initialized.connect(addLinkClass)
@@ -0,0 +1,104 @@
1
+ """Markdown extension for the Link Class plugin for Pelican."""
2
+
3
+ # Copyright (C) 2015, 2017, 2019, 2023, 2025 Rafael Laboissière
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify it
6
+ # under the terms of the GNU General Affero Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or (at
8
+ # your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful, but
11
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with this program. If not, see http://www.gnu.org/licenses/.
17
+
18
+ import re
19
+
20
+ from markdown.extensions import Extension
21
+ from markdown.inlinepatterns import (
22
+ LINK_RE,
23
+ REFERENCE_RE,
24
+ LinkInlineProcessor,
25
+ ReferenceInlineProcessor,
26
+ )
27
+
28
+ LC_CONFIG = {"INTERNAL_CLASS": "internal", "EXTERNAL_CLASS": "external"}
29
+ LC_HELP = {
30
+ "INTERNAL_CLASS": "Name of the CSS class for internal links",
31
+ "EXTERNAL_CLASS": "Name of the CSS class for external links",
32
+ }
33
+
34
+
35
+ def add_class(elm, config):
36
+ """Utlity function for adding the appropriate class attribute."""
37
+ try:
38
+ m = re.match("^https?://", elm.get("href"))
39
+ elm.set("class", (m and config["EXTERNAL_CLASS"]) or config["INTERNAL_CLASS"])
40
+ except AttributeError:
41
+ pass
42
+ return elm
43
+
44
+
45
+ class LinkClassExtension(Extension):
46
+ """Markdown extension for the Link Class plugin."""
47
+
48
+ def __init__(self, config):
49
+ """Initialize the class object."""
50
+ for key, value in LC_CONFIG.items():
51
+ self.config[key] = [value, LC_HELP[key]]
52
+ super().__init__(**config)
53
+
54
+ def extendMarkdown(self, md):
55
+ """Register the Markdown extension."""
56
+ # LinkClass instances is added to the list of inline pattern
57
+ # processors, with higher priority than the processor defined for the
58
+ # "link" and the "reference" objects, such that the normal behavior is
59
+ # overridden.
60
+ LinkClassPattern = LinkClass(LINK_RE, self.getConfigs())
61
+ LinkClassPattern.md = md
62
+ md.inlinePatterns.register(LinkClassPattern, "linkclass", 200)
63
+ ReferenceClassPattern = ReferenceClass(REFERENCE_RE, self.getConfigs())
64
+ ReferenceClassPattern.md = md
65
+ md.inlinePatterns.register(ReferenceClassPattern, "referenceclass", 200)
66
+
67
+
68
+ class LinkClass(LinkInlineProcessor):
69
+ """Markdown inline pattern processor for adding class attribute to inline-style hyperlinks.""" # NOQA: E501
70
+
71
+ def __init__(self, pattern, config):
72
+ """Initialize the Markdwon inline pattern processor."""
73
+ super().__init__(pattern)
74
+ # Store the configuration dict
75
+ self.config = config
76
+
77
+ def handleMatch(self, m, data):
78
+ """Add the class attribute to the generated <a> element."""
79
+ # Build the <a> element using the parent class
80
+ elm, start, end = super().handleMatch(m, data)
81
+ # Return the <a> element with added class
82
+ return add_class(elm, self.config), start, end
83
+
84
+
85
+ class ReferenceClass(ReferenceInlineProcessor):
86
+ """Markdown inline pattern processor for adding class attribute to inline-style references.""" # NOQA: E501
87
+
88
+ def __init__(self, pattern, config):
89
+ """Initialize the Markdwon inline pattern processor."""
90
+ super().__init__(pattern)
91
+ # Store the configuration dict
92
+ self.config = config
93
+
94
+ def handleMatch(self, m, data):
95
+ """Add the class attribute to the generated <a> element."""
96
+ # Build the <a> element using the parent class
97
+ elm, start, end = super().handleMatch(m, data)
98
+ # Return the <a> element with added class
99
+ return add_class(elm, self.config), start, end
100
+
101
+
102
+ def makeExtension(config=None):
103
+ """Register the MarkDown extension."""
104
+ return LinkClassExtension(config=config)
@@ -0,0 +1,191 @@
1
+ """Unit testing suite for the Link Class Plugin."""
2
+
3
+ # Copyright (C) 2015, 2017, 2019, 2021-2023, 2025 Rafael Laboissière <rafael@laboissiere.net> # noqa: E501
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify it
6
+ # under the terms of the GNU General Affero Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or (at
8
+ # your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful, but
11
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with this program. If not, see http://www.gnu.org/licenses/.
17
+
18
+ # For running this test in a standalone-way, run:
19
+ # (cd .. ; python3 -Wd -m unittest discover)
20
+
21
+ from io import StringIO
22
+ import os
23
+ import re
24
+ from shutil import rmtree
25
+ import sys
26
+ from tempfile import mkdtemp
27
+ import unittest
28
+
29
+ from pelican import Pelican
30
+ from pelican.settings import read_settings
31
+
32
+ from . import linkclass
33
+
34
+ INTERNAL_CLASS = "internal"
35
+ EXTERNAL_CLASS = "external"
36
+
37
+ INTERNAL_INLINE_TEXT = "internal inline text"
38
+ INTERNAL_INLINE_LINK = "internal inline link"
39
+
40
+ INTERNAL_REFERENCE_TEXT = "internal reference text"
41
+ INTERNAL_REFERENCE_LABEL = "internal reference label"
42
+ INTERNAL_REFERENCE_LINK = "internal-reference-link"
43
+
44
+ EXTERNAL_INLINE_TEXT_HTTP = "external inline text http"
45
+ EXTERNAL_INLINE_LINK_HTTP = "https://inline.org"
46
+
47
+ EXTERNAL_REFERENCE_TEXT_HTTP = "external reference text http"
48
+ EXTERNAL_REFERENCE_LABEL_HTTP = "external reference label http"
49
+ EXTERNAL_REFERENCE_LINK_HTTP = "https://reference.org"
50
+
51
+ EXTERNAL_INLINE_TEXT_HTTPS = "external inline text https"
52
+ EXTERNAL_INLINE_LINK_HTTPS = "https://inline.org"
53
+
54
+ EXTERNAL_REFERENCE_TEXT_HTTPS = "external reference text https"
55
+ EXTERNAL_REFERENCE_LABEL_HTTPS = "external reference label https"
56
+ EXTERNAL_REFERENCE_LINK_HTTPS = "https://reference.org"
57
+
58
+ LINK_PATTERN = '<a class="{}" href="{}">{}</a>'
59
+
60
+ TEST_FILE_STEM = "test"
61
+ TEST_DIR_PREFIX = "pelicantests."
62
+
63
+
64
+ class TestLinkClass(unittest.TestCase):
65
+ """Class for testing the <a> output elements generated by the Link Class plugin."""
66
+
67
+ def setUp(self, override=None):
68
+ """Initialize the configuration."""
69
+ self.output_path = mkdtemp(prefix=TEST_DIR_PREFIX)
70
+ self.content_path = mkdtemp(prefix=TEST_DIR_PREFIX)
71
+ settings = {
72
+ "PATH": self.content_path,
73
+ "OUTPUT_PATH": self.output_path,
74
+ "PLUGINS": [linkclass],
75
+ "CACHE_CONTENT": False,
76
+ "SITEURL": "http://example.org",
77
+ "TIMEZONE": "UTC",
78
+ "LINKCLASS": (
79
+ ("INTERNAL_CLASS", INTERNAL_CLASS),
80
+ ("EXTERNAL_CLASS", EXTERNAL_CLASS),
81
+ ),
82
+ }
83
+ if override:
84
+ settings.update(override)
85
+
86
+ # Generate the test Markdown source file
87
+ with open(
88
+ os.path.join(self.content_path, f"{TEST_FILE_STEM}.md"),
89
+ "w",
90
+ ) as fid:
91
+ fid.write(f"""Title: Test
92
+ Date: 1970-01-01
93
+
94
+ This is an [{INTERNAL_INLINE_TEXT}]({INTERNAL_INLINE_LINK}), inline-style link.
95
+ This is an [{EXTERNAL_INLINE_TEXT_HTTP}]({EXTERNAL_INLINE_LINK_HTTP}), inline-style link (with http URL).
96
+ This is an [{EXTERNAL_INLINE_TEXT_HTTPS}]({EXTERNAL_INLINE_LINK_HTTP}), inline-style link (with https URL).
97
+
98
+ This is an [{INTERNAL_REFERENCE_TEXT}][{INTERNAL_REFERENCE_LABEL}], reference-style link.
99
+ This is an [{EXTERNAL_REFERENCE_TEXT_HTTP}][{EXTERNAL_REFERENCE_LABEL_HTTP}], reference-style link (with http URL).
100
+ This is an [{EXTERNAL_REFERENCE_TEXT_HTTPS}][{EXTERNAL_REFERENCE_LABEL_HTTPS}], reference-style link (with https URL).
101
+
102
+ [{INTERNAL_REFERENCE_LABEL}]: {INTERNAL_REFERENCE_LINK}
103
+ [{EXTERNAL_REFERENCE_LABEL_HTTP}]: {EXTERNAL_REFERENCE_LINK_HTTP}
104
+ [{EXTERNAL_REFERENCE_LABEL_HTTPS}]: {EXTERNAL_REFERENCE_LINK_HTTPS}
105
+
106
+ """)
107
+
108
+ # Run the Pelican instance
109
+ self.settings = read_settings(override=settings)
110
+ pelican = Pelican(settings=self.settings)
111
+ saved_stdout = sys.stdout
112
+ sys.stdout = StringIO()
113
+ pelican.run()
114
+ sys.stdout = saved_stdout
115
+
116
+ def tearDown(self):
117
+ """Remove the temporary directories."""
118
+ rmtree(self.output_path)
119
+ rmtree(self.content_path)
120
+
121
+ def search(self, string):
122
+ """Search for a string in the article output."""
123
+ with open(
124
+ os.path.join(self.output_path, f"{TEST_FILE_STEM}.html"),
125
+ ) as fid:
126
+ found = False
127
+ for line in fid.readlines():
128
+ if re.search(string, line):
129
+ found = True
130
+ break
131
+ return found
132
+
133
+ def test_internal_inline(self):
134
+ """Test for the internal inline link."""
135
+ assert self.search(
136
+ LINK_PATTERN.format(
137
+ INTERNAL_CLASS,
138
+ INTERNAL_INLINE_LINK,
139
+ INTERNAL_INLINE_TEXT,
140
+ )
141
+ )
142
+
143
+ def test_external_inline_http(self):
144
+ """Test for the external http inline link."""
145
+ assert self.search(
146
+ LINK_PATTERN.format(
147
+ EXTERNAL_CLASS,
148
+ EXTERNAL_INLINE_LINK_HTTP,
149
+ EXTERNAL_INLINE_TEXT_HTTP,
150
+ )
151
+ )
152
+
153
+ def test_external_inline_https(self):
154
+ """Test for the external https inline link."""
155
+ assert self.search(
156
+ LINK_PATTERN.format(
157
+ EXTERNAL_CLASS,
158
+ EXTERNAL_INLINE_LINK_HTTPS,
159
+ EXTERNAL_INLINE_TEXT_HTTPS,
160
+ )
161
+ )
162
+
163
+ def test_internal_reference(self):
164
+ """Test for the internal reference link."""
165
+ assert self.search(
166
+ LINK_PATTERN.format(
167
+ INTERNAL_CLASS,
168
+ INTERNAL_REFERENCE_LINK,
169
+ INTERNAL_REFERENCE_TEXT,
170
+ )
171
+ )
172
+
173
+ def test_external_reference_http(self):
174
+ """Test for the external http reference link."""
175
+ assert self.search(
176
+ LINK_PATTERN.format(
177
+ EXTERNAL_CLASS,
178
+ EXTERNAL_REFERENCE_LINK_HTTP,
179
+ EXTERNAL_REFERENCE_TEXT_HTTPS,
180
+ )
181
+ )
182
+
183
+ def test_external_reference_https(self):
184
+ """Test for the external https reference link."""
185
+ assert self.search(
186
+ LINK_PATTERN.format(
187
+ EXTERNAL_CLASS,
188
+ EXTERNAL_REFERENCE_LINK_HTTPS,
189
+ EXTERNAL_REFERENCE_TEXT_HTTPS,
190
+ )
191
+ )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pelican-linkclass"
3
- version = "2.1.3"
3
+ version = "2.1.5"
4
4
  description = "Pelican plugin to set anchor tag's class attribute to differentiate between internal and external links"
5
5
  authors = [
6
6
  { name = "Rafael Laboissière", email = "rafael@laboissiere.net" },
@@ -17,18 +17,18 @@ classifiers = [
17
17
  "Framework :: Pelican",
18
18
  "Framework :: Pelican :: Plugins",
19
19
  "Intended Audience :: End Users/Desktop",
20
- "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
20
+ "License :: OSI Approved :: GNU Affero General Public License v3",
21
21
  "Operating System :: OS Independent",
22
22
  "Programming Language :: Python :: 3",
23
- "Programming Language :: Python :: 3.8",
24
23
  "Programming Language :: Python :: 3.9",
25
24
  "Programming Language :: Python :: 3.10",
26
25
  "Programming Language :: Python :: 3.11",
27
26
  "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
28
  "Topic :: Internet :: WWW/HTTP",
29
29
  "Topic :: Software Development :: Libraries :: Python Modules",
30
30
  ]
31
- requires-python = ">=3.8.1,<4.0"
31
+ requires-python = ">=3.9,<4.0"
32
32
  dependencies = [
33
33
  "pelican>=4.5",
34
34
  "py3dns>=3.2",
@@ -40,6 +40,7 @@ text = "AGPL-3.0"
40
40
  [project.urls]
41
41
  Homepage = "https://github.com/pelican-plugins/linkclass"
42
42
  "Issue Tracker" = "https://github.com/pelican-plugins/linkclass/issues"
43
+ Changelog = "https://github.com/pelican-plugins/linkclass/blob/main/CHANGELOG.md"
43
44
  Funding = "https://donate.getpelican.com/"
44
45
 
45
46
  [project.optional-dependencies]
@@ -47,17 +48,31 @@ markdown = [
47
48
  "markdown>=3.4",
48
49
  ]
49
50
 
50
- [tool.pdm.dev-dependencies]
51
+ [dependency-groups]
51
52
  lint = [
52
- "black>=23.10.1",
53
- "invoke>=2.2.0",
54
- "ruff>=0.1.3",
53
+ "invoke>=2.2",
54
+ "ruff>=0.9.1,<1.0.0",
55
55
  ]
56
56
  test = [
57
+ "invoke>=2.2",
57
58
  "markdown>=3.4",
58
59
  "pytest>=7.0",
59
60
  "pytest-cov>=4.0",
60
- "pytest-sugar>=0.9.7",
61
+ "pytest-sugar>=1.0",
62
+ ]
63
+
64
+ [tool.pdm.build]
65
+ source-includes = [
66
+ "CHANGELOG.md",
67
+ "CONTRIBUTING.md",
68
+ "external-link.png",
69
+ "linkclass-example.png",
70
+ ]
71
+ includes = [
72
+ "pelican/",
73
+ ]
74
+ excludes = [
75
+ "tasks.py",
61
76
  ]
62
77
 
63
78
  [tool.autopub]
@@ -66,7 +81,7 @@ git-username = "botpub"
66
81
  git-email = "52496925+botpub@users.noreply.github.com"
67
82
  append-github-contributor = true
68
83
 
69
- [tool.ruff]
84
+ [tool.ruff.lint]
70
85
  select = [
71
86
  "B",
72
87
  "BLE",
@@ -92,13 +107,13 @@ select = [
92
107
  ]
93
108
  ignore = [
94
109
  "D100",
95
- "D102",
96
110
  "D104",
97
111
  "D203",
98
112
  "D213",
113
+ "ISC001",
99
114
  ]
100
115
 
101
- [tool.ruff.isort]
116
+ [tool.ruff.lint.isort]
102
117
  combine-as-imports = true
103
118
  force-sort-within-sections = true
104
119
  known-first-party = [
@@ -1,99 +0,0 @@
1
- from inspect import cleandoc
2
- import logging
3
- import os
4
- from pathlib import Path
5
- from shutil import which
6
-
7
- from invoke import task
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
- PKG_NAME = "linkclass"
12
- PKG_PATH = Path(f"pelican/plugins/{PKG_NAME}")
13
-
14
- ACTIVE_VENV = os.environ.get("VIRTUAL_ENV", None)
15
- VENV_HOME = Path(os.environ.get("WORKON_HOME", "~/.local/share/virtualenvs"))
16
- VENV_PATH = Path(ACTIVE_VENV) if ACTIVE_VENV else (VENV_HOME.expanduser() / PKG_NAME)
17
- VENV = str(VENV_PATH.expanduser())
18
- BIN_DIR = "bin" if os.name != "nt" else "Scripts"
19
- VENV_BIN = Path(VENV) / Path(BIN_DIR)
20
-
21
- TOOLS = ("pdm", "pre-commit")
22
- PDM = which("pdm") if which("pdm") else (VENV_BIN / "pdm")
23
- CMD_PREFIX = f"{VENV_BIN}/" if ACTIVE_VENV else f"{PDM} run "
24
- PRECOMMIT = which("pre-commit") if which("pre-commit") else f"{CMD_PREFIX}pre-commit"
25
- PTY = os.name != "nt"
26
-
27
-
28
- @task
29
- def tests(c, deprecations=False):
30
- """Run the test suite, optionally with `--deprecations`."""
31
- deprecations_flag = "" if deprecations else "-W ignore::DeprecationWarning"
32
- c.run(f"{CMD_PREFIX}pytest {deprecations_flag}", pty=PTY)
33
-
34
-
35
- @task
36
- def black(c, check=False, diff=False):
37
- """Run Black auto-formatter, optionally with `--check` or `--diff`."""
38
- check_flag, diff_flag = "", ""
39
- if check:
40
- check_flag = "--check"
41
- if diff:
42
- diff_flag = "--diff"
43
- c.run(f"{CMD_PREFIX}black {check_flag} {diff_flag} {PKG_PATH} tasks.py", pty=PTY)
44
-
45
-
46
- @task
47
- def ruff(c, fix=False, diff=False):
48
- """Run Ruff to ensure code meets project standards."""
49
- diff_flag, fix_flag = "", ""
50
- if fix:
51
- fix_flag = "--fix"
52
- if diff:
53
- diff_flag = "--diff"
54
- c.run(f"{CMD_PREFIX}ruff check {diff_flag} {fix_flag} .", pty=PTY)
55
-
56
-
57
- @task
58
- def lint(c, fix=False, diff=False):
59
- """Check code style via linting tools."""
60
- ruff(c, fix=fix, diff=diff)
61
- black(c, check=(not fix), diff=diff)
62
-
63
-
64
- @task
65
- def tools(c):
66
- """Install development tools in the virtual environment if not already on PATH."""
67
- for tool in TOOLS:
68
- if not which(tool):
69
- logger.info(f"** Installing {tool} **")
70
- c.run(f"{CMD_PREFIX}pip install {tool}")
71
-
72
-
73
- @task
74
- def precommit(c):
75
- """Install pre-commit hooks to .git/hooks/pre-commit."""
76
- logger.info("** Installing pre-commit hooks **")
77
- c.run(f"{PRECOMMIT} install")
78
-
79
-
80
- @task
81
- def setup(c):
82
- """Set up the development environment."""
83
- if which("pdm") or ACTIVE_VENV:
84
- tools(c)
85
- c.run(f"{CMD_PREFIX}python -m pip install --upgrade pip", pty=PTY)
86
- c.run(f"{PDM} update --dev", pty=PTY)
87
- precommit(c)
88
- logger.info("\nDevelopment environment should now be set up and ready!\n")
89
- else:
90
- error_message = """
91
- PDM is not installed, and there is no active virtual environment available.
92
- You can either manually create and activate a virtual environment, or you can
93
- install PDM via:
94
-
95
- curl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py | python3 -
96
-
97
- Once you have taken one of the above two steps, run `invoke setup` again.
98
- """ # noqa: E501
99
- raise SystemExit(cleandoc(error_message))