Trac / レポジトリ生成用 CGI を Mercurial 対応に

以前(⇒ レポジトリ・trac 生成 CGI を作る - daily dayflower)作ったレポジトリ等生成ウェブインタフェースを,Mercurial にも対応するように書き換えました。といっても Trac 0.11b2 用にしてしまったので,0.10 では動きません。一応注釈はいれておいたんで,がんばれば 0.10 用にもできますけど((ほんとは try:except ImportError: で囲めばいいんですけどね))。

最初は trac.env.Environment を直接操作してたんですが,デフォルト Wiki ページの生成部分がちょっとひどいことになりそうだったので,以前と同じように標準出力をトラップする形にしてます。このへん(admin 用インタフェース)ももうちょっと整備してほしいところですね。


2008/03/05 追記: デフォルトの Milestone 等がうっとおしいので削除しておくようにしました。

LoadModule wsgi_module modules/mod_wsgi.so

WSGIScriptAlias /trac/create /var/www/app/trac_builder.wsgi

<Directory /www/main/app>
    WSGIApplicationGroup %{GLOBAL}
    SetEnv TracEnvParentDir   "/var/www/trac"
    SetEnv TracReposParentDir "/var/www/repos"
    Order deny,allow
    Allow from all
</Directory>

みたいな使い方。以前と異なりシングルファイル化したので,mod_python とかの場合は,適宜パッケージとしてインストールしてください。個人的に mod_wsgi に移行してしまったので mod_python ではテストしてません。

# -*- coding: utf-8 -*-

import os, sys

class TracProject:
    def create_svn_repo(self, dest):
        import svn.repos
        svn.repos.create(dest, '', '', None, None)

    def create_hg_repo(self, dest):
        import mercurial.hg as hg
        import mercurial.ui
        hg.repository(mercurial.ui.ui(interactive=False, report_untrusted=False),
                      dest, create=1)

    def create_repository(self, dest, type):
        if type == 'hg':
            self.create_hg_repo(dest)
        else:
            self.create_svn_repo(dest)

    def create_trac(self, project_name, project_dir, repository_dir, repository_type):
        class StreamSweeper:
            def __init__(self):
                self.stock = ''
            def write(self, s):
                self.stock += s

        def escape_quot(s):
            return s.replace('"', '\\"')

        log_stdout = ''
        r = 2

        sys.stdout, last_stdout = StreamSweeper(), sys.stdout
        sys.stderr, last_stderr = StreamSweeper(), sys.stderr

        try:
            # for Trac 0.10, "from trac.script.admin"
            from trac.admin.console import TracAdmin
            import trac.env
            from trac.ticket.model import *

            admin = TracAdmin()

            admin.env_set(project_dir)

            r = admin.do_initenv('"%s" %s %s "%s"' %
                                 ( escape_quot(project_name),
                                   'sqlite:db/trac.db',
                                   repository_type,
                                   escape_quot(repository_dir),
                                 ) )
            # need extra 'template' parameter for Trac 0.10
            # that can be retrieve from trac.config.default_dir()

            log_stdout = sys.stdout.stock

            if not r > 0:
                env = trac.env.Environment(project_dir)

                if repository_type == 'hg':
                    env.config.set('components', 'tracext.hg.*', 'enabled')
                    env.config.save()

                for i in range(1, 2+1):
                    component = Component(env, 'component%d' % (i))
                    component.delete()

                for i in range(1, 4+1):
                    milestone = Milestone(env, 'milestone%d' % (i))
                    milestone.delete()

                for i in ['1.0', '2.0']:
                    version = Version(env, i)
                    version.delete()

                #import trac.perm
                #permsys = trac.perm.PermissionSystem(env)
                #permsys.grant_permission('anonymous', 'TRAC_ADMIN')
        finally:
            sys.stdout, sys.stderr = last_stdout, last_stderr

        return ( r, log_stdout )

    def create(self, project_name, project_dir, repository_dir, repository_type):
        try:
            self.create_repository(repository_dir, repository_type)
        except Exception, e:
            return ( 1, str(e) )
        return self.create_trac(project_name, project_dir,
                                repository_dir, repository_type)

import cgi
import trac.web.api

class TracBuilder:
    def __init__(self, req):
        self.req = req

        self._html_wrapper = """
<html>
    <head>
        <title>TracBuilder - %s</title>
        <style type="text/css">
            th { text-align: left; }
        </style>
    </head>
    <body>
%s
    </body>
</html>
        """

        self._html_failure = """
        <h1>Create Trac Project</h1>
        <p>Error occured:</p>
        <pre>%s</pre>
        """

        self._html_success = """
        <h1>Create Trac Project</h1>
        <p>Successfully created:</p>
        <pre>%s</pre>
        """

        self._html_form = """
        <h1>Create Trac Project</h1>
        <form method="post">
            <table>
                <tbody>
                    <tr>
                        <th>Repository Name</th>
                        <td><input type="text" name="repo" /></td>
                    </tr>
                    <tr>
                        <th>Project Name</th>
                        <td><input type="text" name="project" value="My Project" /></td>
                    </tr>
                    <tr>
                        <th>Repository Type</th>
                        <td>
%s
                        </td>
                    </tr>
                    <tr>
                        <th></th>
                        <td><input type="submit" /></td>
                    </tr>
                </tbody>
            </table>
        </form>
        """

        # transform _html_form for repository types
        repo_types = [ ( 'svn', 'Subversion (svn)' ) ]

        try:
            import mercurial.hg
            repo_types.append( ( 'hg', 'Mercurial (hg)' ) )
        except ImportError: pass

        opts = ''
        for r in repo_types:
            checked = ''
            if r[0] == 'svn':
                checked = 'checked=" checked"'

            opts += """
                            <input type="radio" name="type" id="type-%s" value="%s"%s />
                            <label for="type-%s">%s</label>
                            <br />
            """ % ( r[0], r[0], checked, r[0], r[1] )

        self._html_form = self._html_form % ( opts )

    def show_failure(self, message = ''):
        message = cgi.escape(message).encode('utf-8')
        html = self._html_wrapper % ('Failed', self._html_failure % ( message ))
        try:
            self.req.send(html, status = 500)
        except trac.web.api.RequestDone: pass

    def show_success(self, message = ''):
        message = cgi.escape(message).encode('utf-8')
        html = self._html_wrapper % ('Succeeded', self._html_success % ( message ))
        try:
            self.req.send(html)
        except trac.web.api.RequestDone: pass

    def show_form(self):
        try:
            self.req.send(self._html_wrapper % ('Input Information', self._html_form))
        except trac.web.api.RequestDone: pass

    def do_create(self):
        req = self.req

        if req.args['repo'] == '' or req.args['project'] == '':
            self.show_failure('Invalid parameters')
            return

        try:
            path_trac = os.path.join(
                            req.environ.get('trac_builder.project_parent_dir'),
                            req.args['repo'])
            path_repo = os.path.join(
                            req.environ.get('trac_builder.repos_parent_dir'),
                            req.args['repo'])
        except:
            self.show_failure('Bad configuration')
            return
                
        r, status = TracProject().create(req.args['project'],
                                         path_trac, path_repo,
                                         req.args['type'])

        if r > 0:
            # error occured
            self.show_failure(status)
        else:
            # succeed
            self.show_success(status)
            #try:
            #    req.redirect(req.base_url, False)
            #except trac.web.api.RequestDone: pass

    def run(self):
        if self.req.method == 'POST':
            self.do_create()
        else:
            self.show_form()

# for WSGI
def application(environ, start_response):
    if os.environ.get('GATEWAY_INTERFACE', '').startswith('CGI/'):
        environ.setdefault('trac_builder.project_parent_dir',
                           os.environ.get('TRAC_ENV_PARENT_DIR'))
        environ.setdefault('trac_builder.repos_parent_dir',
                           os.environ.get('TRAC_REPOS_PARENT_DIR'))
    else:
        if 'mod_python.options' in environ:
            options = environ['mod_python.options']
        else:
            options = environ
        environ.setdefault('trac_builder.project_parent_dir',
                           options.get('TracEnvParentDir'))
        environ.setdefault('trac_builder.repos_parent_dir',
                           options.get('TracReposParentDir'))

    req = trac.web.api.Request(environ, start_response)
    req.callbacks.update({
        'session': lambda(self): None,
    })

    TracBuilder(req).run()
    return []

# for MOD_PYTHON
def handler(req):
    from trac.web.modpython_frontend import ModPythonGateway
    from mod_python import apache

    options = req.get_options()
    gateway = ModPythonGateway(req, options)
    gateway.run(application)
    return apache.OK

# for CGI
if os.environ.get('GATEWAY_INTERFACE', '').startswith('CGI/'):
    from trac.web.cgi_frontend import CGIGateway

    gateway = CGIGateway()
    gateway.run(application)