# 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:: :filename: :filter: :basedir: 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('%s' % (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'}