From f96cbaf78d9f8cc3eb582b95331bfc91dffb7127 2011-02-10 20:42:24
From: MinRK <benjaminrk@gmail.com>
Date: 2011-02-10 20:42:24
Subject: [PATCH] replace in-repo gh-pages with external ipython-doc, as in datarray

---

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 = '<li> Release: <a href="{t}/index.html">{t}</a>'.format(t=tag)
+    rep = re.compile('<!-- RELEASE -->')
+    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] <html directory>"
-    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 <path>\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