cgi-edit

data/cgi-edit.py (download)

Source

#!/usr/bin/python
# coding: utf-8
#
# © 2006 Joachim Breitner
#

import cgi
import os
import shutil
import tempfile

import cgitb; cgitb.enable()

import pysvn

def esc(str): return cgi.escape(str, True)

FILETYPES={
    'tex':       'LaTeX Document',
    'part.tex':  'partial LaTeX Document',
    'hs':        'Haskell Code File',
    'py':        'Python Code File',
    'wiki-conf': 'Wiki Configuration File',
}

class Form(cgi.FieldStorage):
    def __getitem__(self,item):
        encoding = self.getfirst('_charset_') or 'UTF-8'
        return self.getfirst(item).decode(encoding)

    def getfirst(self, key, default=None):
        """ Return the first value received."""
        if key in self:
            value = cgi.FieldStorage.__getitem__(self,key)
            if type(value) is type([]):
                return value[0].value
            else:
                return value.value
        else:
            return default

def main ():
    global self_uri, repos, filename, who

    tmpdir = tempfile.mkdtemp('','latexki-cgi-')
    os.chdir(tmpdir)
    
    try:
        basename = os.environ.get('PATH_INFO','/')[1:]
        repos    = os.environ.get('HTTP_LATEXKI_REPOS',None)
        self_uri = os.environ.get('SCRIPT_NAME') + os.environ.get('PATH_INFO','')
        who = os.environ.get('REMOTE_ADDR','unknown').decode('utf8')
        assert repos, "Need HTTP_LATEXKI_REPOS environment variable"
        
        prepare_svn()
        form = Form()

        line_from = None
        line_to   = None
        if 'lines' in form:
            (a,b) = form['lines'].split('-',1)
            line_from = int(a)
            line_to   = int(b)

        error = None
        old_rev = 0
        conf_rev = None
        done = False
        log = ''
        theanswer = '23'
        content = ''

        if 'basename' in form:
            basename = form['basename']
            assert 'type' in form, "Extension choice forgotten"
            ext = form['type']
            if   ext == "!wiki":
                ext = None
            elif ext == "!other":
                assert 'ext' in form, "Extension entry forgotten"
                ext = form['ext']
            (new,old_ext) = exists(basename)
            assert new or ext == old_ext, "File exists with different extension"
        else:
            (new,ext) = exists(basename)
            

        if new and basename:
            dirs = basename.split("/")
            for i in range(0,len(dirs)):
                assert dirs[i].isalnum(), "Please use only alphanumerical page names"
            for i in range(1,len(dirs)):
                subpath = "/".join(dirs[0:i])
                assert not(exists(subpath)[0]), "Directory %s does not exist" % subpath

        if ext:
            filename = basename+"."+ext
        else:
            filename = basename


        dirname  = os.path.dirname(basename)
        if dirname:
            get_directory(dirname)

        if not new:
            if 'revision' in form:
                old_rev = update(form['revision'])
            else:
                old_rev = update()

        if 'content' in form:
            assert 'comment' in form, "Commit comment is compulsory"
            assert 'theanswer' in form, "Calculation answer is compulsory"
            log = form['comment']
            theanswer = form['theanswer']
            content = form['content'].replace('\r\n','\n') # is this an HACK?
            if "http" in log:
                error = "No URLs in the comments, please (Anti-Spam-Measure)"
            elif theanswer != "42":
                error = "Please enter 42 in the anti-spam-box, not %s." % theanswer
            else:
                if len(content) > 0 and content[-1] != '\n':
                    content += '\n'
                if line_from and line_to:
                    old_content  = file(filename,'r').read().decode('utf8')
                    pre_content  = ''.join(old_content.splitlines(True)[:line_from-1])
                    post_content = ''.join(old_content.splitlines(True)[line_to:])
                    content = pre_content + content + post_content
                
                file(filename,'w').write(content.encode('utf8'))
                if new:
                    add()
                    (error,new_rev) = commit(log)
                    if not error:
                        done = True
                else:
                    if 'conf_rev' in form:
                        if 'checked_conflict' in form:
                            new_rev = update(form['conf_rev'])
                            assert conflict(), "This should be a conflict, strange..."
                            file(filename,'w').write(content.encode('utf8'))
                            resolve()
                        else:
                            error = "Please do resolve your conflict"

                    new_rev = update()
                    if conflict():
                        conf_rev = new_rev
                    else:
                        (error,new_rev) = commit(log)
                        if not error:
                            done = True
            
        if conf_rev:
            line_from = line_to = None # Conflicts and line handling bite

        if not content:
            if filename and not new:
                content = file(filename,'r').read().decode('utf8')
                if line_from and line_to:
                    content = ''.join(content.splitlines(True)[line_from-1 : line_to])
            else:
                content = 'Enter new page here. For LaTeX-Pages, please enter a LaTeX-Document'

        print_headers()
        if done:
            print_success(new, basename, ext, new_rev)
        else:
            print_page(new, basename, ext, content, log, theanswer, old_rev, conf_rev, error, line_from, line_to)

    finally:
        os.chdir("/")
        shutil.rmtree(tmpdir)

def prepare_svn():
    global client
    client = pysvn.Client()
    client.set_default_username((who + u' via wiki').encode('utf8'))
    zero = pysvn.Revision( pysvn.opt_revision_kind.number, 0 )
    client.checkout(repos, '.',False,zero)

def get_directory(dirname):
    global client
    client.update(dirname, False)

def update(req_rev=None):
    if req_rev:
        rev = pysvn.Revision( pysvn.opt_revision_kind.number, req_rev )
    else:
        rev = pysvn.Revision( pysvn.opt_revision_kind.head )
    
    rev_list = client.update(filename,False,rev)
    assert len(rev_list) == 1
    return rev_list[0].number

def add():
    client.add(filename)

def conflict():
    return client.status(filename)[0].text_status == pysvn.wc_status_kind.conflicted

def resolve():
    client.resolved(filename)

def commit(log = u'No log message'):
    msg = log
    rev = client.checkin(filename, msg)
    if rev:
        # This looks so like "Either String Int" :-)
        return (None, rev.number)
    else:
        if client.status(filename)[0].text_status == pysvn.wc_status_kind.normal:
            new_rev = update()
            return (None, new_rev)
        else:
            return ("Commit failed :-(" , None)

def exists(basename):
    dirname = os.path.dirname(basename)
    basebasename = os.path.basename(basename)
    files =  client.ls(repos + "/" + dirname)
    sr = (lambda str: os.path.basename(str))
    matches = filter((lambda e: sr(e['name']) == basebasename or
                                sr(e['name']).startswith(basebasename+".")),files)
    if len(matches) == 0:
        return (True, None)
    else:
        assert len(matches) == 1, "More than one file with this basename, fix the repository!"
        if sr(matches[0]['name']) == basebasename:
            return (False,None)
        else:
            ext = sr(matches[0]['name'])[len(basebasename)+1:]
            return (False, ext)

def print_headers():
    print "Content-type: text/html"
    print

def print_page(new, basename, ext, content, log, theanswer, rev, conf_rev, error, line_from, line_to):
    if new:
        if basename:
            title = u"Creating new page “%s”" % basename
            nameform = esc(basename)
        else:
            title = u"Creating new page"
            nameform = u'<input name="basename" type="text" size="50"/>'
        
        pageform = u'''
            <h3>Page Name </h3>
            %(nameform)s
            <h3>Page Type</h3>
            <input type="radio" name="type" value="!wiki" checked="checked">Wiki page</input>
            <input type="radio" name="type" value="tex">LaTeX document</input>
            <input type="radio" name="type" value="part.tex">Partial LaTeX document</input>
            <input type="radio" name="type" value="!other">Other, specify extension:
            <input type="text" name="ext" size="5" /> '''%{'nameform':nameform}
    else:
        title = u"Editing page “%s” at revision %i" % (basename,rev)
        pageform = u'<h3>%(basename)s, a %(type)s' % { 'basename':esc(basename), 'type':ptype(ext)}
    
    errortext = ''
    if error:
        errortext = u'''<strong>There was an error:</strong> %s''' % esc(error)
    
    conftext =  ''
    if conf_rev:
        conftext =  u'''
            <input type="hidden" name="conf_rev" value="%(conf_rev)i"/>
            <input type="checkbox" name="checked_conflict" value="true">
              There was a conflict with revision %(conf_rev)i. I hereby confirm that I have resolved the
              conflict to the best of all <strong>and removed the conflict markers</strong>
            </input><br/>
        ''' % {'conf_rev':conf_rev}

    linetext = ''
    if line_from and line_to:
        linetext = u'<input type="hidden" name="lines" value="%d-%d"/>' % (line_from, line_to)

    form = u'''
    <form action="%(self_uri)s" method="POST">
    %(pageform)s
    <h3>Content</h3>
    <textarea name="content" cols="80" rows="30">%(content)s</textarea>
    <input type="hidden" name="revision" value="%(rev)i"/>
    <h3>Commit log entry</h3>
    Please describe your changes. This is mandatory.<br/>
    <input type="text" name="comment" size="80" value="%(comment)s"/>
    <h3>What is 40 + 2?</h3>
    Sorry for the inconvenience. This is mandatory, to prevent spam.<br/>
    <input type="text" name="theanswer" size="80" value="%(theanswer)s"/>
    <h3>Commit changes</h3>
    <input type="hidden" name="_charset_"/>
    %(conftext)s
    %(linetext)s
    <button type="submit">Commit changes
    </button> (can take a while, please be patient)
    </form>''' % { 'pageform':pageform, 'content': esc(content), 'self_uri':esc(self_uri),
                   'conftext':conftext, 'rev':rev, 'comment':esc(log), 'theanswer':esc(theanswer),
                   'linetext':linetext }
    print (u'''
<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>%(title)s</title>
</head>
<boddy>
<h1>%(title)s</h1>
%(error)s
%(form)s
</body>
</html>''' % { 'title': esc(title), 'form': form, 'error': errortext}).encode('utf8')


def print_success(new, basename, ext, new_rev):
    title = u'Successful commit'
    text = u'Sucessfully commited <a href="/%(basename)s.html">%(basename)s</a> (a %(ext)s) to revision %(rev)i' % {
        'basename': esc(basename), 'ext': ptype(ext), 'rev': new_rev}
    print '''
<html>
<head>
<title>%(title)s</title>
</head>
<body>
<h1>%(title)s</h1>
%(text)s
</body>
</html>''' % { 'title': title, 'text': text}


def ptype(ext):
    if ext: return FILETYPES.get(ext,u"“.%s” file" % esc(ext))
    else:   return u"regular wiki page"


main()
# vim:ts=4:sw=4:expandtab:smarttab