diff --git a/.gitignore b/.gitignore index 1f1c943..9f22902 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build docs/dist docs/build/* docs/source/api/generated +docs/gh-pages *.py[co] build *.egg-info diff --git a/docs/Makefile b/docs/Makefile index dee26be..7e1bbf1 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -102,3 +102,6 @@ gitwash-update: nightly: dist rsync -avH --delete dist/ ipython:www/doc/nightly + +gh-pages: html + sh update_ghpages.sh diff --git a/docs/source/conf.py b/docs/source/conf.py index b955056..c41f56e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -20,7 +20,7 @@ import sys, os # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. -sys.path.append(os.path.abspath('../sphinxext')) +sys.path.insert(0, os.path.abspath('../sphinxext')) # Import support for ipython console session syntax highlighting (lives # in the sphinxext directory defined above) @@ -44,6 +44,7 @@ extensions = [ 'inheritance_diagram', 'ipython_console_highlighting', 'numpydoc', # to preprocess docstrings + 'sphinxtogithub', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/sphinxext/sphinxtogithub.py b/docs/sphinxext/sphinxtogithub.py new file mode 100644 index 0000000..9b72b87 --- /dev/null +++ b/docs/sphinxext/sphinxtogithub.py @@ -0,0 +1,381 @@ +#! /usr/bin/env python +""" +sphinxtogithub extension Copyright Michael Jones + +BSD License + +Original at: https://github.com/michaeljones/sphinx-to-github +""" +from optparse import OptionParser +import os +import sys +import shutil + + +class DirHelper(object): + + def __init__(self, is_dir, list_dir, walk, rmtree): + + self.is_dir = is_dir + self.list_dir = list_dir + self.walk = walk + self.rmtree = rmtree + +class FileSystemHelper(object): + + def __init__(self, open_, path_join, move, exists): + + self.open_ = open_ + self.path_join = path_join + self.move = move + self.exists = exists + +class Replacer(object): + "Encapsulates a simple text replace" + + def __init__(self, from_, to): + + self.from_ = from_ + self.to = to + + def process(self, text): + + return text.replace( self.from_, self.to ) + +class FileHandler(object): + "Applies a series of replacements the contents of a file inplace" + + def __init__(self, name, replacers, opener): + + self.name = name + self.replacers = replacers + self.opener = opener + + def process(self): + + text = self.opener(self.name).read() + + for replacer in self.replacers: + text = replacer.process( text ) + + self.opener(self.name, "w").write(text) + +class Remover(object): + + def __init__(self, exists, remove): + self.exists = exists + self.remove = remove + + def __call__(self, name): + + if self.exists(name): + self.remove(name) + +class ForceRename(object): + + def __init__(self, renamer, remove): + + self.renamer = renamer + self.remove = remove + + def __call__(self, from_, to): + + self.remove(to) + self.renamer(from_, to) + +class VerboseRename(object): + + def __init__(self, renamer, stream): + + self.renamer = renamer + self.stream = stream + + def __call__(self, from_, to): + + self.stream.write( + "Renaming directory '%s' -> '%s'\n" + % (os.path.basename(from_), os.path.basename(to)) + ) + + self.renamer(from_, to) + + +class DirectoryHandler(object): + "Encapsulates renaming a directory by removing its first character" + + def __init__(self, name, root, renamer): + + self.name = name + self.new_name = name[1:] + self.root = root + os.sep + self.renamer = renamer + + def path(self): + + return os.path.join(self.root, self.name) + + def relative_path(self, directory, filename): + + path = directory.replace(self.root, "", 1) + return os.path.join(path, filename) + + def new_relative_path(self, directory, filename): + + path = self.relative_path(directory, filename) + return path.replace(self.name, self.new_name, 1) + + def process(self): + + from_ = os.path.join(self.root, self.name) + to = os.path.join(self.root, self.new_name) + self.renamer(from_, to) + + +class HandlerFactory(object): + + def create_file_handler(self, name, replacers, opener): + + return FileHandler(name, replacers, opener) + + def create_dir_handler(self, name, root, renamer): + + return DirectoryHandler(name, root, renamer) + + +class OperationsFactory(object): + + def create_force_rename(self, renamer, remover): + + return ForceRename(renamer, remover) + + def create_verbose_rename(self, renamer, stream): + + return VerboseRename(renamer, stream) + + def create_replacer(self, from_, to): + + return Replacer(from_, to) + + def create_remover(self, exists, remove): + + return Remover(exists, remove) + + +class Layout(object): + """ + Applies a set of operations which result in the layout + of a directory changing + """ + + def __init__(self, directory_handlers, file_handlers): + + self.directory_handlers = directory_handlers + self.file_handlers = file_handlers + + def process(self): + + for handler in self.file_handlers: + handler.process() + + for handler in self.directory_handlers: + handler.process() + + +class NullLayout(object): + """ + Layout class that does nothing when asked to process + """ + def process(self): + pass + +class LayoutFactory(object): + "Creates a layout object" + + def __init__(self, operations_factory, handler_factory, file_helper, dir_helper, verbose, stream, force): + + self.operations_factory = operations_factory + self.handler_factory = handler_factory + + self.file_helper = file_helper + self.dir_helper = dir_helper + + self.verbose = verbose + self.output_stream = stream + self.force = force + + def create_layout(self, path): + + contents = self.dir_helper.list_dir(path) + + renamer = self.file_helper.move + + if self.force: + remove = self.operations_factory.create_remover(self.file_helper.exists, self.dir_helper.rmtree) + renamer = self.operations_factory.create_force_rename(renamer, remove) + + if self.verbose: + renamer = self.operations_factory.create_verbose_rename(renamer, self.output_stream) + + # Build list of directories to process + directories = [d for d in contents if self.is_underscore_dir(path, d)] + underscore_directories = [ + self.handler_factory.create_dir_handler(d, path, renamer) + for d in directories + ] + + if not underscore_directories: + if self.verbose: + self.output_stream.write( + "No top level directories starting with an underscore " + "were found in '%s'\n" % path + ) + return NullLayout() + + # Build list of files that are in those directories + replacers = [] + for handler in underscore_directories: + for directory, dirs, files in self.dir_helper.walk(handler.path()): + for f in files: + replacers.append( + self.operations_factory.create_replacer( + handler.relative_path(directory, f), + handler.new_relative_path(directory, f) + ) + ) + + # Build list of handlers to process all files + filelist = [] + for root, dirs, files in self.dir_helper.walk(path): + for f in files: + if f.endswith(".html"): + filelist.append( + self.handler_factory.create_file_handler( + self.file_helper.path_join(root, f), + replacers, + self.file_helper.open_) + ) + if f.endswith(".js"): + filelist.append( + self.handler_factory.create_file_handler( + self.file_helper.path_join(root, f), + [self.operations_factory.create_replacer("'_sources/'", "'sources/'")], + self.file_helper.open_ + ) + ) + + return Layout(underscore_directories, filelist) + + def is_underscore_dir(self, path, directory): + + return (self.dir_helper.is_dir(self.file_helper.path_join(path, directory)) + and directory.startswith("_")) + + + +def sphinx_extension(app, exception): + "Wrapped up as a Sphinx Extension" + + if not app.builder.name in ("html", "dirhtml"): + return + + if not app.config.sphinx_to_github: + if app.config.sphinx_to_github_verbose: + print "Sphinx-to-github: Disabled, doing nothing." + return + + if exception: + if app.config.sphinx_to_github_verbose: + print "Sphinx-to-github: Exception raised in main build, doing nothing." + return + + dir_helper = DirHelper( + os.path.isdir, + os.listdir, + os.walk, + shutil.rmtree + ) + + file_helper = FileSystemHelper( + open, + os.path.join, + shutil.move, + os.path.exists + ) + + operations_factory = OperationsFactory() + handler_factory = HandlerFactory() + + layout_factory = LayoutFactory( + operations_factory, + handler_factory, + file_helper, + dir_helper, + app.config.sphinx_to_github_verbose, + sys.stdout, + force=True + ) + + layout = layout_factory.create_layout(app.outdir) + layout.process() + + +def setup(app): + "Setup function for Sphinx Extension" + + app.add_config_value("sphinx_to_github", True, '') + app.add_config_value("sphinx_to_github_verbose", True, '') + + app.connect("build-finished", sphinx_extension) + + +def main(args): + + usage = "usage: %prog [options] " + parser = OptionParser(usage=usage) + parser.add_option("-v","--verbose", action="store_true", + dest="verbose", default=False, help="Provides verbose output") + opts, args = parser.parse_args(args) + + try: + path = args[0] + except IndexError: + sys.stderr.write( + "Error - Expecting path to html directory:" + "sphinx-to-github \n" + ) + return + + dir_helper = DirHelper( + os.path.isdir, + os.listdir, + os.walk, + shutil.rmtree + ) + + file_helper = FileSystemHelper( + open, + os.path.join, + shutil.move, + os.path.exists + ) + + operations_factory = OperationsFactory() + handler_factory = HandlerFactory() + + layout_factory = LayoutFactory( + operations_factory, + handler_factory, + file_helper, + dir_helper, + opts.verbose, + sys.stdout, + force=False + ) + + layout = layout_factory.create_layout(path) + layout.process() + + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/docs/update_ghpages.sh b/docs/update_ghpages.sh new file mode 100755 index 0000000..57dd59c --- /dev/null +++ b/docs/update_ghpages.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env sh +# pick repo for gh-pages branch +repo=origin + +if [ ! -d gh-pages ]; then + echo "setting up gh-pages subdir" + mkdir gh-pages || exit -1 + cp -r ../.git gh-pages/ || exit -1 + cd gh-pages || exit -1 + init=0 + git checkout $repo/gh-pages || init=1 + if [ "$init" != "0" ]; then + echo "initializing gh-pages repo" + git symbolic-ref HEAD refs/heads/gh-pages || exit -1 + rm .git/index || exit -1 + git clean -fdx || exit -1 + touch index.html + git add . + git commit -a -m 'init gh-pages' || exit -1 + git push origin HEAD:gh-pages + fi + cd .. +fi +echo "updating local gh-pages with html build" +rsync -va build/html/ gh-pages/ --delete --exclude .git || exit -1 +cd gh-pages +git add . +git commit -a || exit -1 +echo "pushing to remote gh-pages" +# pwd +git push $repo HEAD:gh-pages