summaryrefslogtreecommitdiffstats
path: root/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to '__init__.py')
-rw-r--r--__init__.py111
1 files changed, 111 insertions, 0 deletions
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..930220c
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,111 @@
+
+"""dir_attach, a Pelican plugin to attach files from a directory named after
+restructuredText file names.
+
+If your file is named ``foobar.rst``, then the following
+restructuredText entry:
+
+ .. image:: {dir_attach}example-thumbnail.jpg
+ :alt: An example image for demonstration.
+ :target: {dir_attach}example.jpg
+
+Will automatically be pre-processed by this plugin to become:
+
+ .. image:: {attach}../images/foobar/example-thumbnail.jpg
+ :alt: An example image for demonstration.
+ :target: {attach}../images/foobar/example.jpg
+
+Using ``../images/`` directory instead of ``images/`` is mandatory. This is
+because the current Pelican implementation will simplify the path only if the
+target directory is not in the same root as the current page.
+
+If ``images/`` directory was used, then the resulting path in HTML would be:
+
+ foobar/images/foobar/example.jpg
+
+By using a directory outside of the page's source folder, the generated HTML
+path is simplified by Pelican:
+
+ foobar/example.jpg
+
+"""
+
+import os
+import tempfile
+import re
+
+from pelican import signals
+from pelican.readers import RstReader
+
+
+def dirname_from_source_path(source_path):
+ """Get the dirname to use from the .rst filename.
+
+ >>> dirname_from_source_path("/tmp/foobar.rst")
+ 'foobar'
+
+ """
+ return os.path.splitext(os.path.basename(source_path))[0]
+
+
+def expand_dir_attach(content, dirname):
+ """Expand the {dir_attach} directives to image {attach} directives.
+
+ >>> src = \
+ ".. image:: {dir_attach}example.jpg\\n" \
+ " :alt: Example illustration.\\n" \
+ " :target: {dir_attach}example.jpg\\n"
+ >>> expand_dir_attach(src, "foobar")
+ '.. image:: {attach}../images/foobar/example.jpg\\n\
+ :alt: Example illustration.\\n\
+ :target: {attach}../images/foobar/example.jpg\\n'
+
+ """
+
+ dest = f"{{attach}}../images/{dirname}/"
+
+ # We are trying to match only restructuredText directives instead of doing
+ # a whole content.replace to avoid to replace inside of paragraphs, etc.
+ regexes = (
+ (r"^(.. \w+::) {dir_attach}(.*)$", fr"\1 {dest}\2"),
+ (r"^(.. _.*:) {dir_attach}(.*)$", fr"\1 {dest}\2"),
+ (r"^( :\w+:) {dir_attach}(.*)$", fr"\1 {dest}\2"),
+ )
+
+ for match, replace in regexes:
+ content = re.sub(match, replace, content, flags=re.MULTILINE)
+
+ return content
+
+
+class CustomRstReader(RstReader):
+ """A custom restructuredText reader that will pre-process source first."""
+
+ enabled = True
+ file_extensions = ['rst']
+
+ def read(self, source_path):
+ """Parses restructured text."""
+ dirname = dirname_from_source_path(source_path)
+
+ # Open temporary file in "w" instead of default "w+b" in order to
+ # use utf-8 by default.
+ with tempfile.NamedTemporaryFile(mode="w") as tmp:
+ with open(source_path) as src:
+ tmp.write(expand_dir_attach(src.read(), dirname))
+
+ # Force flush to disk before docutils tries to open the file
+ # in super().read(), elsewise file may be empty.
+ tmp.flush()
+
+ return super().read(tmp.name)
+
+
+def add_reader(readers):
+ """Override the .rst reader with our custom reader."""
+ readers.reader_classes['rst'] = CustomRstReader
+
+
+def register():
+ """Register the plugin to Pelican."""
+ signals.readers_init.connect(add_reader)