diff --git a/hyde/ext/plugins/sphinx.py b/hyde/ext/plugins/sphinx.py new file mode 100644 index 0000000..2f90592 --- /dev/null +++ b/hyde/ext/plugins/sphinx.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +""" +Sphinx plugin. + +This plugin lets you easily include sphinx-generated documentation as part +of your Hyde site. +""" + +from __future__ import absolute_import + +import json +import tempfile + +from hyde.plugin import Plugin +from hyde.fs import File, Folder +from hyde.model import Expando + +import sphinx + + +class SphinxPlugin(Plugin): + """The plugin class for rendering sphinx-generated documentation.""" + + def __init__(self, site): + self.sphinx_build_dir = None + super(SphinxPlugin, self).__init__(site) + + @property + def plugin_name(self): + return "sphinx" + + @property + def settings(self): + settings = Expando({}) + settings.conf_path = "." + settings.block_map = Expando({}) + settings.block_map.body = "body" + try: + user_settings = getattr(self.site.config, self.plugin_name) + except AttributeError: + pass + else: + for name in dir(user_settings): + if not name.startswith("_"): + setattr(settings,name,getattr(user_settings,name)) + return settings + + def begin_site(self): + # Find and adjust all the resource that will be handled by sphinx. + # We need to: + # * change the deploy name from .rst to .html + # * make sure they don't get rendered inside a default block + for resource in self.site.content.walk_resources(): + if resource.source_file.kind == "rst": + new_name = resource.source_file.name_without_extension + ".html" + target_folder = File(resource.relative_deploy_path).parent + resource.relative_deploy_path = target_folder.child(new_name) + resource.meta.default_block = None + + def begin_text_resource(self,resource,text): + """If this is a sphinx input file, replace it with the generated docs. + + This method will replace the text of the file with the sphinx-generated + documentation, lazily running sphinx if it has not yet been called. + """ + if resource.source_file.kind != "rst": + return text + if self.sphinx_build_dir is None: + self._run_sphinx() + output = [] + settings = self.settings + for (nm,content) in self._get_sphinx_output(resource).iteritems(): + try: + block = getattr(settings.block_map,nm) + except AttributeError: + pass + else: + output.append("{%% block %s %%}" % (block,)) + output.append(content) + output.append("{% endblock %}") + return "\n".join(output) + + def site_complete(self): + if self.sphinx_build_dir is not None: + self.sphinx_build_dir.delete() + + def _run_sphinx(self): + """Run sphinx to generate the necessary output files. + + This method creates a temporary directory, copies all sphinx-related + files into it, and then runs the sphinx build process to produce + the documentation fragments. + """ + self.sphinx_build_dir = Folder(tempfile.mkdtemp()) + # Copy all sphinx files into a temporary build directory. + self.sphinx_input_dir = self.sphinx_build_dir.child_folder("input") + self.sphinx_input_dir.make() + for resource in self.site.content.walk_resources(): + if resource.source_file.kind == "rst": + relpath = resource.relative_path + build_file = File(self.sphinx_input_dir.child(relpath)) + build_file.parent.make() + resource.source_file.copy_to(build_file) + # Run sphinx to generate output in the output directory. + self.sphinx_output_dir = self.sphinx_build_dir.child_folder("output") + self.sphinx_output_dir.make() + conf_path = self.site.sitepath.child_folder(self.settings.conf_path) + sphinx_args = ["sphinx-build"] + sphinx_args.extend([ + "-b", "json", + "-c", conf_path.path, + self.sphinx_input_dir.path, + self.sphinx_output_dir.path + ]) + if sphinx.main(sphinx_args) != 0: + raise RuntimeError("sphinx build failed") + + def _get_sphinx_output(self,resource): + relpath = File(resource.relative_path) + relpath = relpath.parent.child(relpath.name_without_extension+".fjson") + with open(self.sphinx_output_dir.child(relpath),"rb") as f: + return json.load(f) +