diff --git a/docs/Makefile b/docs/Makefile index d8f042f..7c684cb 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -32,7 +32,7 @@ help: @echo "gitwash-update update git workflow from source repo" clean: - -rm -rf build/* dist/* $(SRCDIR)/api/generated gh-pages + -rm -rf build/* dist/* $(SRCDIR)/api/generated pdf: latex cd build/latex && make all-pdf @@ -104,4 +104,7 @@ nightly: dist rsync -avH --delete dist/ ipython:www/doc/nightly gh-pages: html - sh update_ghpages.sh + python gh-pages.py + +gh-pages-current: html + python gh-pages.py current diff --git a/docs/gh-pages.py b/docs/gh-pages.py new file mode 100755 index 0000000..e785611 --- /dev/null +++ b/docs/gh-pages.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +"""Script to commit the doc build outputs into the github-pages repo. + +Use: + + gh-pages.py [tag] + +If no tag is given, the current output of 'git describe' is used. If given, +that is how the resulting directory will be named. + +In practice, you should use either actual clean tags from a current build or +something like 'current' as a stable URL for the most current version of the """ + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +import os +import re +import shutil +import sys +from os import chdir as cd +from os.path import join as pjoin + +from subprocess import Popen, PIPE, CalledProcessError, check_call + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + +pages_dir = 'gh-pages' +html_dir = 'build/html' +pages_repo = 'git@github.com:ipython/ipython-doc.git' + +#----------------------------------------------------------------------------- +# Functions +#----------------------------------------------------------------------------- +def sh(cmd): + """Execute command in a subshell, return status code.""" + return check_call(cmd, shell=True) + + +def sh2(cmd): + """Execute command in a subshell, return stdout. + + Stderr is unbuffered from the subshell.x""" + p = Popen(cmd, stdout=PIPE, shell=True) + out = p.communicate()[0] + retcode = p.returncode + if retcode: + raise CalledProcessError(retcode, cmd) + else: + return out.rstrip() + + +def sh3(cmd): + """Execute command in a subshell, return stdout, stderr + + If anything appears in stderr, print it out to sys.stderr""" + p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True) + out, err = p.communicate() + retcode = p.returncode + if retcode: + raise CalledProcessError(retcode, cmd) + else: + return out.rstrip(), err.rstrip() + + +def init_repo(path): + """clone the gh-pages repo if we haven't already.""" + sh("git clone %s %s"%(pages_repo, path)) + here = os.getcwd() + cd(path) + sh('git checkout gh-pages') + cd(here) + + +def render_htmlindex(fname, tag): + rel = '
  • Release: {t}'.format(t=tag) + rep = re.compile('') + out = [] + with file(fname) as f: + for line in f: + out.append(line) + if rep.search(line): + out.append(rep.sub(rel, line)) + return ''.join(out) + + +def new_htmlindex(fname, tag): + new_page = render_htmlindex(fname, tag) + os.rename(fname, fname+'~') + with file(fname, 'w') as f: + f.write(new_page) + + +#----------------------------------------------------------------------------- +# Script starts +#----------------------------------------------------------------------------- +if __name__ == '__main__': + # The tag can be given as a positional argument + try: + tag = sys.argv[1] + except IndexError: + tag = sh2('git describe') + + startdir = os.getcwd() + if not os.path.exists(pages_dir): + init_repo(pages_dir) + + dest = pjoin(pages_dir, tag) + + # don't `make html` here, because gh-pages already depends on html in Makefile + # sh('make html') + + # This is pretty unforgiving: we unconditionally nuke the destination + # directory, and then copy the html tree in there + shutil.rmtree(dest, ignore_errors=True) + shutil.copytree(html_dir, dest) + + try: + cd(pages_dir) + sh('git checkout gh-pages') + status = sh2('git status | head -1') + branch = re.match('\# On branch (.*)$', status).group(1) + if branch != 'gh-pages': + e = 'On %r, git branch is %r, MUST be "gh-pages"' % (pages_dir, + branch) + raise RuntimeError(e) + + sh('git add %s' % tag) + new_htmlindex('index.html', tag) + sh('git add index.html') + sh('git commit -m"Created new doc release, named: %s"' % tag) + print + print 'Most recent 3 commits:' + sys.stdout.flush() + sh('git --no-pager log --oneline HEAD~3..') + finally: + cd(startdir) + + print + print 'Now verify the build in: %r' % dest + print "If everything looks good, 'git push'" diff --git a/docs/source/conf.py b/docs/source/conf.py index c41f56e..60a9e48 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -44,7 +44,6 @@ 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 deleted file mode 100644 index 9b72b87..0000000 --- a/docs/sphinxext/sphinxtogithub.py +++ /dev/null @@ -1,381 +0,0 @@ -#! /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 deleted file mode 100755 index 57dd59c..0000000 --- a/docs/update_ghpages.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/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