#!/usr/bin/python

# u1lint: Wrapper script for pylint or pyflakes
#
# Author: Rodney Dawes <rodney.dawes@canonical.com>
#
# Copyright 2009-2010 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Wrapper script for pylint command."""

import ConfigParser
import os
import subprocess

from xdg.BaseDirectory import xdg_data_dirs

SRCDIR = os.environ.get('SRCDIR', os.getcwd())

def find_pylintrc():
    """Return the first pylintrc found."""
    # Use the pylintrc in the source tree if there is one
    full_name = os.path.join(SRCDIR, 'pylintrc')
    if os.path.exists(full_name):
        return full_name

    # If no pylintrc in the source tree, use the first in $XDG_DATA_DIRS
    # Hopefully this is the one we installed, and hasn't been overridden
    for name in xdg_data_dirs:
        full_name = os.path.join(name, 'ubuntuone-dev-tools', 'pylintrc')
        if os.path.exists(full_name):
            return full_name
    return None

PYLINTRC = find_pylintrc()

def _read_pylintrc_ignored():
    """Get the ignored files list from pylintrc"""
    try:
        config = ConfigParser.ConfigParser()
        config.read([PYLINTRC])

        return config.get("MASTER", "ignore").split(",")
    except (TypeError, ConfigParser.NoOptionError):
        return None

def _group_lines_by_file(data):
    """Format file:line:message output as lines grouped by file."""
    did_fail = False
    outputs = []
    filename = ""
    for line in data.splitlines():
        current = line.split(":", 3)
        if line.startswith("    "):
            outputs.append("    " + current[0] + "")
        elif line.startswith("build/") or len(current) < 3:
            pass
        elif filename == current[0]:
            # pylint warning W0511 is a custom note
            if not "[W0511]" in current[2]:
                did_fail = True
            outputs.append("    " + current[1] + ": " + current[2])
        elif filename != current[0]:
            filename = current[0]
            outputs.append("")
            outputs.append(filename + ":")
            # pylint warning W0511 is a custom note
            if not "[W0511]" in current[2]:
                did_fail = True
            outputs.append("    " + current[1] + ": " + current[2])

    return (did_fail, "\n".join(outputs))

def _find_files():
    """Find all Python files under the current tree."""
    pyfiles = []
    # pylint: disable=W0612
    for root, dirs, files in os.walk(SRCDIR, topdown=False):
        for filename in files:
            filepath = "%s/" % root

            # Skip files in build/
            if filepath.startswith("%s/build/" % SRCDIR):
                continue

            # Skip protobuf-generated and backup files
            if filename.endswith("_pb2.py") or filename.endswith("~"):
                continue

            if filename.endswith(".py") or filepath.endswith("bin/"):
                pyfiles.append(os.path.join(root, filename))

    pyfiles.sort()
    return pyfiles


def main():
    """Do the deed."""
    failed = False
    ignored = _read_pylintrc_ignored()

    # So that we can match the path correctly
    if ignored:
        moreignores = [os.path.join(SRCDIR, item) for item in ignored]
        ignored.extend(moreignores)
    else:
        ignored = []

    if os.environ.get('USE_PYFLAKES'):
        pylint_args = ["pyflakes"]
    else:
        pylint_args = ["pylint",
                       "--output-format=parseable",
                       "--include-ids=yes",]
        if PYLINTRC:
            pylint_args.append("--rcfile=" + PYLINTRC)

    for path in _find_files():
        is_build = path.startswith(os.path.join(SRCDIR, "_build"))
        if path not in ignored and not is_build:
            pylint_args.append(path)

    sp = subprocess.Popen(pylint_args,
                          bufsize=4096, stdout=subprocess.PIPE)
    notices = sp.stdout

    output = "".join(notices.readlines())
    if output != "":
        print "== Python Lint Notices =="
        (failed, grouped) = _group_lines_by_file(output)
        print grouped
        print ""

    returncode = sp.wait()
    # XXX Testing that W0511 does not cause a failure
    if failed:
        if returncode != 0:
            exit(returncode)
        else:
            exit(1)


if __name__ == '__main__':
    main()
