#!/usr/local/bin/python2.7
# SPDX-License-Identifier: BSD-2-Clause
"""
repomapper - update and manipulate contributor maps
"""

# This code runs under both Python 2 and Python 3: preserve this property!
from __future__ import print_function

import sys, re, getopt, io

binary_encoding = 'latin-1'

def make_std_wrapper(stream):
    "Standard input/output wrapper factory function"
    # This ensures that the encoding of standard output and standard
    # error on Python 3 matches the binary encoding we use to turn
    # bytes to Unicode in polystr above; it has no effect on Python 2
    # since the output streams are binary
    if isinstance(stream, io.TextIOWrapper):
        # newline="\n" ensures that Python 3 won't mangle line breaks
        # line_buffering=True ensures that interactive command sessions work as expected
        return io.TextIOWrapper(stream.buffer, encoding=binary_encoding, newline="\n", line_buffering=True)
    return stream

#sys.stdin = make_std_wrapper(sys.stdin)
sys.stdout = make_std_wrapper(sys.stdout)
#sys.stderr = make_std_wrapper(sys.stderr)

class Contributor:
    "Associate a usename with a DVCS-style ID."
    def __init__(self, name, fullname, email, tz):
        self.name = name
        self.fullname = fullname
        self.email = email
        self.tz = tz
    def incomplete(self):
        "Does this entry need completion?"
        return self.name == self.fullname or "@" not in self.email
    def __str__(self):
        out = "%s = %s <%s>" % (self.name, self.fullname, self.email)
        if self.tz:
            out += " " + self.tz
        out += "\n"
        if "x" == b"x":
            return out.encode('latin-1')
        return out

class ContribMap:
    "A map of contributors."
    def __init__(self, fn):
        self.payload = {}
        for line in open(fn, "rb"):
            line = line.decode(binary_encoding)
            m = re.match(r"([^ ]+) *= ([^<]+)*<([^<]+)> *(.*)", line)
            if m is None:
                sys.stderr.write("repomapper: malformed attribution line %s.\n" % repr(line))
                sys.exit(1)
            name = m.group(1)
            fullname = m.group(2).strip()
            email = m.group(3)
            tz = m.group(4)
            self.payload[name] = Contributor(name, fullname, email, tz)
    def suffix(self, addr):
        "Add an address suffix to entries lacking one."
        for (_name, obj) in self.payload.items():
            if '@' not in obj.email:
                obj.email += "@" + addr
    def write(self, fp, incomplete=False):
        "Write the current state of this contrib map."
        keys = list(self.payload.keys())
        keys.sort()
        for name in keys:
            if incomplete and not self.payload[name].incomplete():
                continue
            fp.write(str(self.payload[name]))

if __name__ == '__main__':
    host = ""
    passwdfile = None
    updatefile = None
    incomplete = False
    (options, arguments) = getopt.getopt(sys.argv[1:], "h:ip:u:",
                                         ["host=", "passwd="])
    for (opt, val) in options:
        if opt == '-h' or opt == '--host':
            host = val
        elif opt == '-i':
            incomplete = True
        elif opt == '-p' or opt == '--passwd':
            passwdfile = val
        elif opt == "-u":
            updatefile = val
    if len(arguments) < 1:
        sys.stderr.write("repomapper: requires a contrib-map file argument.\n")
        sys.exit(1)

    # Read in an ordered dictionary of existing attributions.
    contribmap = ContribMap(arguments[0])

    # Apply the -h option
    if host:
        contribmap.suffix(host)

    # With -p, read the password data
    if passwdfile:
        passwd = {}
        for line in open(passwdfile, "rb"):
            line = line.decode(binary_encoding)
            try:
                (name, _hash, _uid, _gid, gecos, _home, _dir) = line.split(":")
                if "," in gecos:
                    gecos = gecos.split(",").pop(0)
                passwd[name] = gecos
            except ValueError:
                sys.stderr.write("repomapper: malformed passwd line.\n")
                sys.exit(1)
        # Attempt to fill in the contribmap
        for (name, obj) in contribmap.payload.items():
            if name not in passwd:
                sys.stderr.write("repomapper: %s not in password file.\n" % name)
            elif obj.fullname == name:
                contribmap.payload[name].fullname = passwd[name]
            elif obj.fullname.lower() != passwd[name].lower():
                sys.stderr.write("repomapper: %s -> %s should be %s.\n" % (name, obj.fullname, passwd[name]))
        # Now dump the result
        contribmap.write(sys.stdout, incomplete=False)
        raise SystemExit(0)

    # With -u, copy in all complete entries in the update file
    if updatefile:
        updatemap = ContribMap(updatefile)
        for (name, obj) in updatemap.payload.items():
            if name not in contribmap.payload:
                contribmap.payload[name] = obj
        # Now dump the result
        contribmap.write(sys.stdout, incomplete=False)
        raise SystemExit(0)

    # By default, report on incomplete entries
    contribmap.write(sys.stdout, incomplete=incomplete)

# end
