summaryrefslogtreecommitdiff
path: root/doc/source/ext/sphinxext-archive.py
diff options
context:
space:
mode:
Diffstat (limited to 'doc/source/ext/sphinxext-archive.py')
-rw-r--r--doc/source/ext/sphinxext-archive.py184
1 files changed, 184 insertions, 0 deletions
diff --git a/doc/source/ext/sphinxext-archive.py b/doc/source/ext/sphinxext-archive.py
new file mode 100644
index 0000000..dc1c2c8
--- /dev/null
+++ b/doc/source/ext/sphinxext-archive.py
@@ -0,0 +1,184 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Sphinx extension creating a link to an archive.
+
+This Sphinx extension adds the possibility to create a zip archive and
+include it in the generated HTML documentation.
+
+This extension provides an *archive* directive.
+
+Usage:
+
+.. archive:: <relative_path/to/directory/to/compress>
+ :filename: <name_of_the_archive_file, default: last_folder_of_directory.zip>
+ :filter: <Space-separated list of included file patterns, default *.*>
+ :basedir: <Name of the base directory in the archive, default: filename>
+
+Examples:
+
+To create a example.zip archive of the ../../example/ folder:
+
+.. archive:: ../../examples/
+
+To get more control on the name of the archive and its content:
+
+.. archive:: ../../examples/
+ :filename: myproject_examples.zip
+ :filter: *.py *.png
+ :basedir: myproject_examples
+
+WARNING: The content of this directory is not checked for outdated documents.
+"""
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "01/09/2017"
+
+
+import fnmatch
+import logging
+import os
+import os.path
+import posixpath
+import shutil
+import tempfile
+
+from docutils.parsers.rst import directives, Directive
+import docutils.nodes
+
+
+_logger = logging.getLogger(__name__)
+
+
+# docutils directive
+
+class ArchiveDirective(Directive):
+ """Add a link to download an archive
+
+ This directive add an :class:`archive` node to the document tree.
+ """
+
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = False
+ option_spec = {
+ 'filename': directives.unchanged,
+ 'filter': directives.unchanged,
+ 'basedir': directives.unchanged,
+ }
+
+ def run(self):
+ node = archive('')
+
+ # Get rst source file containing this directive
+ source_file = self.state_machine.get_source_and_line()[0]
+ if source_file is None:
+ raise RuntimeError('Cannot get rst source file path')
+
+ # Build input path from rst source file and directive argument
+ input_path = self.arguments[0]
+ if not input_path.startswith('/'): # Argument is a relative path
+ input_path = os.path.abspath(
+ os.path.join(os.path.dirname(source_file), input_path))
+ node['input_path'] = input_path
+
+ default_basedir = os.path.basename(input_path)
+ node['basedir'] = self.options.get('basedir', default_basedir)
+ node['filename'] = self.options.get('filename',
+ '.'.join((default_basedir, 'zip')))
+
+ node['filter'] = self.options.get('filter', '*.*')
+
+ return [node]
+
+
+# archive doctuils node
+
+class archive(docutils.nodes.General, docutils.nodes.Element, docutils.nodes.Inline):
+ """archive node created by :class:`ArchiveDirective`"""
+ pass
+
+
+def visit_archive_html(self, node):
+ """Node visitor to translate :class:`archive` nodes to HTML.
+
+ :param self: Sphinx HTML Writter
+ :param node: The :class:`archive` node to translate to HTML
+ :raise: SkipNode as depart is not implemented
+ """
+ filename = node['filename']
+ input_path = node['input_path']
+
+ # Create a temporary folder to create archive content
+ tmp_dir = tempfile.mkdtemp()
+
+ # Copy selected content to temporary folder
+ base_dir = os.path.join(tmp_dir, node['basedir'])
+
+ def ignore(src, names):
+ patterns = node['filter'].split()
+ ignored_names = []
+ for name in names:
+ for pattern in patterns:
+ if fnmatch.fnmatch(name, pattern):
+ break
+ else:
+ ignored_names.append(name)
+ return ignored_names
+
+ shutil.copytree(input_path, base_dir, ignore=ignore)
+
+ # Compress temporary folder to zip
+ output_filename = os.path.join(
+ self.builder.outdir, '_downloads', filename)
+ root, ext = os.path.splitext(output_filename)
+ assert ext == '.zip'
+ shutil.make_archive(root, 'zip', tmp_dir, node['basedir'])
+
+ # Clean-up temporary folder
+ shutil.rmtree(tmp_dir)
+
+ # Generate HTML
+ relative_path = posixpath.join(self.builder.dlpath, filename)
+ self.body.append('<a href="%s">%s</a>' % (relative_path, filename))
+ raise docutils.nodes.SkipNode
+
+
+def visit_skip(self, node):
+ """No-op node visitor"""
+ raise docutils.nodes.SkipNode
+
+
+# Extension setup
+
+def setup(app):
+ """Sphinx extension registration"""
+ app.add_node(archive,
+ html=(visit_archive_html, None),
+ latex=(visit_skip, None))
+
+ app.add_directive('archive', ArchiveDirective)
+
+ return {'version': '0.1'}