#!/usr/bin/python
'''svn pre-commit script

Most of it comes from: http://wordaligned.org/articles/a-subversion-pre-commit-hook
'''
from __future__ import print_function
import io
import lxml.etree as etree
from optparse import OptionParser
import subprocess
import sys


def command_output(cmd):
    '''Capture a command's standard output'''
    return subprocess.Popen(
        cmd.split(), stdout=subprocess.PIPE).communicate()[0]


def files_changed(look_cmd):
    '''List the files added or updated by this transaction

    "svnlook changed" gives output like:
    U   trunk/file1.cpp
    A   trunk/file2.cpp
    '''
    def filename(line):
        return line[4:]

    def added_or_updated(line):
        return line and line[0] in ('A', 'U')

    return [
        filename(line)
        for line in command_output(look_cmd % 'changed').split('\n')
        if added_or_updated(line)]


def file_contents(filename, look_cmd):
    '''Return a file's contents for this transaction'''
    return command_output("%s %s" % (look_cmd % "cat", filename))


def is_valid_xml(fname):
    '''Check if an xml file is valid

    Args:
        fname: svn path of the file to check

    Raises:
        SyntaxWarning: the file is not valid
    '''
    tree = etree.parse(fname)
    if tree.docinfo.system_url:
        dtd = etree.DTD(tree.docinfo.system_url)
        if not dtd.validate(tree):
            raise SyntaxWarning('\n'.join([str(entry) for entry in dtd.error_log]))


def check_xml_files(look_cmd):
    '''Check if xml files in this transaction are wellformed and valid'''
    errors = 0
    for ff in files_changed(look_cmd):
        if ff.endswith('.xml'):
            try:
                is_valid_xml(io.BytesIO(file_contents(ff, look_cmd)))
            except etree.XMLSyntaxError as e:
                errors += 1
                print(ff, 'is not wellformed\nError:', e,
                      file=sys.stderr)
            except SyntaxWarning as e:
                errors += 1
                print(ff, 'is not valid according to DTD\n', e,
                      file=sys.stderr)

    return errors


def main():
    usage = '''usage: %prog REPOS TXN

    Run pre-commit options on a repository transaction.'''

    parser = OptionParser(usage=usage)
    parser.add_option("-r", "--revision",
                        help="Test mode. TXN actually refers to a revision.",
                        action="store_true", default=False)
    errors = 0
    (opts, (repos, txn_or_rvn)) = parser.parse_args()
    look_opt = ("--transaction", "--revision")[opts.revision]
    look_cmd = "svnlook %s %s %s %s" % (
        "%s", repos, look_opt, txn_or_rvn)
    errors += check_xml_files(look_cmd)
    return errors

if __name__ == "__main__":
    sys.exit(main())
