diff options
Diffstat (limited to 'dir_attach')
-rw-r--r-- | dir_attach/__init__.py | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/dir_attach/__init__.py b/dir_attach/__init__.py new file mode 100644 index 0000000..cf0510f --- /dev/null +++ b/dir_attach/__init__.py @@ -0,0 +1,110 @@ + +"""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"^(\s+:\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) |