view doxyhook.py @ 3:3a06b35bd591 default tip

Avoid infinite recursion on hook call ("clone bomb")
author Louis Opter <louis.opter@dotcloud.com>
date Thu, 25 Mar 2010 20:09:55 +0100
parents a85cbad8dadb
children
line wrap: on
line source

#!/usr/bin/env python

# The MIT License
#
# Copyright (c) 2010 Louis Opter <kalessin@kalessin.fr>
#
# 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.

import os
import re
import shutil
import subprocess
import sys
import tempfile

from mercurial import ui, hg, commands

OUTPUT = '/var/www/doc/api'

class DoxyfileNotFound(Exception):
    def __str__(self):
        return ('can\'t find a doxyfile to generate the documentation')

class TempDir(object):
    """Wrap tempfile.mkdtemp to use the with statement"""

    def __init__(self):
        pass

    def __enter__(self):
        self.__path = tempfile.mkdtemp()
        return self.__path

    def __exit__(self, exc_type, exc_value, traceback):
        shutil.rmtree(self.__path)

def clone_repo(sui, source, dest):
    """Clone repo at directory source to directory dest"""

    # We have to use a different ui than the source ui. If you don't take this
    # precaution your hook will be called recursively by the clone command,
    # leading to a clone bomb.
    cui = ui.ui()
    commands.clone(cui, source, dest, opts={'uncompressed': True})
    sui.status('doxyhook: working copy created.\n')

def gen_doc(clone):
    """
    Generate the documentation with the first doxyfile found under clone
    directory.

    Return the output directory of the doc or None if no doc was generated.
    """

    # Regular expression for a doxygen configuration file:
    doxyre = re.compile('.*[dD]oxyfile$')
    # Regular expression used on a line of the doxygen configuration file to
    # find the output directory:
    outputre = re.compile('OUTPUT_DIRECTORY\\s*=\\s*(.*)$')

    files = os.listdir(clone)
    try:
        doxyfile = (f for f in files if not os.path.isdir(f) and doxyre.match(f)).next()
    except StopIteration:
        raise DoxyfileNotFound

    with open(os.path.join(clone, doxyfile)) as doxyconf:
        for line in doxyconf:
            output = outputre.match(line)
            if output:
                output = output.group(1)
                break

    if output:
        with open(os.devnull, 'w') as ignore:
            oldpwd = os.getcwd()
            # We have to change our directory so the doc is generated at the
            # right place.
            os.chdir(clone)
            subprocess.call(['doxygen', doxyfile], stdout=ignore, stderr=ignore)
            os.chdir(oldpwd)
        # clone will be ignored if output is an absolute path
        html = os.path.join(clone, output, 'html')
        if os.path.isdir(html):
            return html

    return None

def move_doc(source, dest):
    """Move the doc from source to dest"""

    if os.path.isdir(dest):
        shutil.rmtree(dest)
    shutil.move(source, dest)

def hook(ui, repo, hooktype, **kwargs):
    if hooktype == 'changegroup':
        with TempDir() as clone:
            clone_repo(ui, repo.root, clone)
            try:
                output = gen_doc(clone)
                if output:
                    ui.status('doxyhook: doc generated.\n')
                    move_doc(output, OUTPUT)
                else:
                    ui.status('doxyhook: no doc generated.\n')
            except (OSError, DoxyfileNotFound), ex:
                ui.status('doxyhook: %s.\n' % str(ex))
    else:
        ui.status('doxyhook: is a changegroup hook only.\n')

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print 'Usage: %s mercurial-repo' % os.path.basename(sys.argv[0])
        sys.exit(1)

    ui = ui.ui()
    repo = hg.repository(ui, sys.argv[1])
    hook(ui, repo, 'changegroup')
    sys.exit(0)