#!/usr/bin/python3
#
# lincredits -- Beautify the Linux CREDITS file in various formats
#   Written by Chris Lawrence <lawrencc@debian.org>
#   (C) 1999-2019 Chris Lawrence
#
# This program is freely distributable per the following license:
#
##  Permission to use, copy, modify, and distribute this software and its
##  documentation for any purpose and without fee is hereby granted,
##  provided that the above copyright notice appear in all copies and that
##  both that copyright notice and this permission notice appear in
##  supporting documentation.
##
##  I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
##  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
##  BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
##  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
##  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
##  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
##  SOFTWARE.
#
# Version 0.4: 12 January 1999
#
# TODO: Add support for the MAINTAINERS file format
#       [Unfortunately the S tag means different things in the two formats,
#        and each block starts with a line with no header]



import sys
import getopt
import re
import html
import argparse

CREDITMAP = {
    'N' : 'name',
    'E' : 'email',
    'M' : 'maintainer',
    'F' : 'files',
    'P' : 'pgp',
    'W' : 'url',
    'D' : 'desc',
    'S' : 'mail',
    'R' : 'reviewer',
    'T' : 'devtree',
    'X' : 'exclude',
    'K' : 'keyword', # regex
    'L' : 'email-list',
    'Q' : 'patchwork',
    'B' : 'bugs-url',
    'C' : 'chat-url',
    }

MAINTMAP = CREDITMAP.copy()
MAINTMAP['S'] = 'status'

def parseblock(lines, maintainers=False):
    creditinfo = {}
    creditline = re.compile(r':\s+')

    mapping = MAINTMAP if maintainers else CREDITMAP
    
    for line in lines:
        dat = creditline.split(line, 1)
        # Skip improperly formatted headers
        if len(dat) < 2:
            continue
        (dtype, data) = dat
        var = mapping.get(dtype)
        if var:
            creditinfo.setdefault(var, []).append(data)
        else:
            print('Warning: Unknown header:', dtype, file=sys.stderr)

    return creditinfo

def parse(infile):
    nameline = re.compile('N:\s+')
    headerline = re.compile('[A-Z]:\s+')
    
    linebuff = []
    people = {}
    past_header = False
    maintainers = False
    
    for line in infile:
        line = line.rstrip()

        if not past_header:
            if '----------' in line:
                past_header = True
            elif 'List of maintainers' in line:
                # Heuristic to detect maintainers file
                maintainers = True
            continue

        # Skip comments
        if line.startswith('#'):
            continue

        if maintainers and line and not headerline.match(line):
            blockname = line
            linebuff = []
            continue
        elif nameline.match(line):
            blockname = nameline.split(line, 1)[1]
            linebuff = []
            continue
            
        if linebuff and not line:
            info = parseblock(linebuff, maintainers)
            people[blockname] = info
        else:
            linebuff.append(line)

    if linebuff: # Flush any remaining data
        info = parseblock(linebuff)
        people[blockname] = info

    return people, maintainers

def format_text(info, filename, outfile, maintainers):
    filetype = 'Maintainers' if maintainers else 'Credits'
    
    print(f'{filetype} from {filename}', file=outfile)
    
    for (block, binfo) in info.items():
        print(block, file=outfile, end=' ')
        if maintainers:
            print(file=outfile)

        if 'email' in binfo:
            outfile.write('<'+binfo['email'][0]+'>:\n')
            bits = binfo['email'][1:]
            if bits:
                outfile.write('  Alternate addresses:\n')
                for bit in bits:
                    outfile.write('    <'+bit+'>\n')
        elif not maintainers:
            print(':', file=outfile)

        if 'maintainer' in binfo:
            print('  Maintained by:', file=outfile)
            for maint in binfo['maintainer']:
                print('   ', maint, file=outfile)
        
        if 'url' in binfo:
            outfile.write('  Website:\n')
            bits = binfo['url']
            for bit in bits:
                outfile.write('    '+bit+'\n')
        if 'pgp' in binfo:
            outfile.write('  PGP key fingerprint:\n')
            bits = binfo['pgp']
            for bit in bits:
                outfile.write('    '+bit+'\n')
        if 'desc' in binfo:
            bits = binfo['desc']
            outfile.write('  Contributions:\n')
            for bit in bits:
                outfile.write('    '+bit+'\n')
        if 'mail' in binfo:
            bits = binfo['mail']
            outfile.write('  Physical address:\n')
            for bit in bits:
                outfile.write('    '+bit+'\n')
        outfile.write('\n')
    return

def make_email_link(addr):
    return '&lt;<A HREF="mailto:%s">%s</A>&gt;' % (addr, html.escape(addr))

def make_url_link(addr):
    return '<A HREF="%s">%s</A>' % (addr, html.escape(addr))

def format_html(info, filename, outfile, maintainers):
    outfile.write('<HTML><HEAD>\n')
    outfile.write('<TITLE>CREDITS from %s</TITLE>\n' % html.escape(filename) )
    outfile.write('</HEAD>\n\n<BODY><DL>\n')
    
    for block, binfo in info.items():
        if block:
            outfile.write('<DT>'+html.escape(block))
        else:
            outfile.write('<DT>')
            
        if 'email' in binfo:
            outfile.write(' '+make_email_link(binfo['email'][0]))
            outfile.write(':\n<DD><DL>\n')
            bits = binfo['email'][1:]
            if bits:
                outfile.write('<DT>Alternate addresses:\n<DD><UL>\n')
                for bit in bits:
                    outfile.write('<LI>'+make_email_link(bit)+'\n')
                outfile.write('</UL>\n')
        else:
            outfile.write(':\n<DD><DL>\n')
        if 'url' in binfo:
            outfile.write('<DT>Website:\n<DD><UL>\n')
            bits = binfo['url']
            for bit in bits:
                outfile.write('<LI>'+make_url_link(bit)+'\n')
            outfile.write('</UL>\n')
        if 'pgp' in binfo:
            outfile.write('<DT>PGP key fingerprint:\n<DD><UL>\n')
            bits = binfo['pgp']
            for bit in bits:
                outfile.write('<LI><TT>'+html.escape(bit)+'</TT>\n')
            outfile.write('</UL>\n')
        if 'desc' in binfo:
            bits = binfo['desc']
            outfile.write('<DT>Contributions:\n<DD><UL>\n')
            for bit in bits:
                outfile.write('<LI>'+html.escape(bit)+'\n')
            outfile.write('</UL>\n')
        if 'mail' in binfo:
            bits = binfo['mail']
            outfile.write('<DT>Physical address:\n<DD>\n')
            for bit in bits:
                outfile.write(html.escape(bit)+'<BR>\n')
        outfile.write('</DL>\n')

    outfile.write('</DL></BODY>\n</HTML>\n')
    return

# Swiped from running GNU recode -h
texfix = [
    "~", "!`", "", "\\pound{}",	# 160-163
    "", "", "", "\\S{}",        # 164-167
    "\\\"{}",			# 168
    "\\copyright{}",		# 169
    "",				# 170
    "``",			# 171
    "\\neg{}",			# 172
    "\\-",			# 173
    "",				# 174
    "",				# 175
    "\\mbox{$^\\circ$}",	# 176
    "\\mbox{$\\pm$}",		# 177
    "\\mbox{$^2$}",		# 178
    "\\mbox{$^3$}",		# 179
    "\\'{}",			# 180
    "\\mbox{$\\mu$}",		# 181
    "",				# 182
    "\\cdotp",			# 183
    "\\,{}",			# 184
    "\\mbox{$^1$}",		# 185
    "",				# 186
    "''",			# 187
    "\\frac1/4{}",		# 188
    "\\frac1/2{}",		# 189
    "\\frac3/4{}",		# 190
    "?`",			# 191
    "\\`A",			# 192
    "\\'A",			# 193
    "\\^A",			# 194
    "\\~A",			# 195
    "\\\"A",			# 196
    "\\AA{}",			# 197
    "\\AE{}",			# 198
    "\\c{C}",			# 199
    "\\`E",			# 200
    "\\'E",			# 201
    "\\^E",			# 202
    "\\\"E",			# 203
    "\\`I",			# 204
    "\\'I",			# 205
    "\\^I",			# 206
    "\\\"I",			# 207
    "",				# 208
    "\\~N",			# 209
    "\\`O",			# 210
    "\\'O",			# 211
    "\\^O",			# 212
    "\\~O",			# 213
    "\\\"O",			# 214
    "",				# 215
    "\\O{}",			# 216
    "\\`U",			# 217
    "\\'U",			# 218
    "\\^U",			# 219
    "\\\"U",			# 220
    "\\'Y",			# 221
    "",				# 222
    "\\ss{}",			# 223
    "\\`a",			# 224
    "\\'a",			# 225
    "\\^a",			# 226
    "\\~a",			# 227
    "\\\"a",			# 228
    "\\aa{}",			# 229
    "\\ae{}",			# 230
    "\\c{c}",			# 231
    "\\`e",			# 232
    "\\'e",			# 233
    "\\^e",			# 234
    "\\\"e",			# 235
    "\\`{\\i}",			# 236
    "\\'{\\i}",			# 237
    "\\^{\\i}",			# 238
    "\\\"{\\i}",		# 239
    "",				# 240
    "\\~n",			# 241
    "\\`o",			# 242
    "\\'o",			# 243
    "\\^o",			# 244
    "\\~o",			# 245
    "\\\"o",			# 246
    "",				# 247
    "\\o{}",			# 248
    "\\`u",			# 249
    "\\'u",			# 250
    "\\^u",			# 251
    "\\\"u",			# 252
    "\\'y",			# 253
    "",				# 254
    "\\\"y",			# 255
]

def texify(text, href=False):
    for ch in '#$%&_{}':
        text = text.replace(ch, '\\'+ch)
    if href:
        return text

    text = text.replace('<', '$<$')
    text = text.replace('>', '$>$')
    text = text.replace('~', '\~{}')
    for i in range(160, 255):
        text = text.replace(chr(i), texfix[i-160])
    return text

def format_latex(info, filename, outfile, maintainers):
    outfile.write('\\documentclass{article}\n')
    print('\\usepackage{hyperref}', file=outfile)
    outfile.write('\\title{CREDITS from %s}\n' % texify(filename))
    outfile.write('\\author{Generated by \\texttt{lincredits}}\n')
    outfile.write('\\setlength{\parindent}{0in}\n')
    outfile.write('\\begin{document}\n\\maketitle\n')
    
    for block, binfo in info.items():
        if block == 'David Gentzel':
            print(repr(binfo), file=sys.stderr)
        
        thisinfo = binfo
        outfile.write('\\begin{minipage}{\\textwidth}\n')
        outfile.write(texify(block))
        keys = list(thisinfo.keys())
        madelist = False
        if 'email' in thisinfo:
            addr = thisinfo['email'][0]
            taddr = texify(addr, href=True)
            print(' $<$\\href{mailto:%s}{%s}$>$:' % (addr, taddr),
                  file=outfile)
            bits = thisinfo['email'][1:]
            if bits:
                outfile.write('\\begin{list}{}{}\n')
                outfile.write('\\item Alternate addresses:\n')
                outfile.write('\\begin{itemize}\n')
                for bit in bits:
                    tbit = texify(bit, href=True)
                    print('\\item $<$\\href{mailto:%s}{%s}$>$' % (bit, tbit),
                          file=outfile)
                outfile.write('\\end{itemize}\n')
                madelist=True
            elif len(keys) > 2:
                outfile.write('\\begin{list}{}{}\n')
                madelist=True
        elif len(keys) > 1:
            outfile.write(':\n\\begin{list}{}{}\n')
            madelist=True
            
        if 'url' in thisinfo:
            outfile.write('\\item Website:\n')
            outfile.write('\\begin{itemize}\n')
            bits = thisinfo['url']
            for bit in bits:
                bit = texify(bit, href=True)
                print('\\item \\url{%s}' % (bit), file=outfile)
            outfile.write('\\end{itemize}\n')
        if 'pgp' in thisinfo:
            outfile.write('\\item PGP key fingerprint:\n')
            outfile.write('\\begin{itemize}\n')
            bits = thisinfo['pgp']
            for bit in bits:
                outfile.write('\\item \\texttt{'+texify(bit)+'}\n')
            outfile.write('\\end{itemize}\n')
        if 'desc' in thisinfo:
            bits = thisinfo['desc']
            outfile.write('\\item Contributions:\n')
            outfile.write('\\begin{itemize}\n')
            for bit in bits:
                outfile.write('\\item '+texify(bit)+'\n')
            outfile.write('\\end{itemize}\n')
        if 'mail' in thisinfo:
            bits = thisinfo['mail']
            outfile.write('\\item Physical address:\n')
            outfile.write('\\begin{list}{}{}\n')
            for bit in bits:
                outfile.write('\\item '+texify(bit)+'\n')
            outfile.write('\\end{list}\n')

        if madelist:
            outfile.write('\\end{list}\n')
        outfile.write('\\end{minipage}\n\n')
        outfile.write('\\vspace{\\baselineskip}\n')

    outfile.write('\\end{document}')
    return

USAGE = ('lincredits: Parse the Linux CREDITS file\n\n'
         'Usage: lincredits [options] <filename>\n'
         'Supported options:\n'
         '  --text:  Output as formatted text (default)\n'
         '  --html:  Output as HTML formatted text\n'
         '  --latex: Output as LaTeX source\n'
         '  --output <filename>: Output to a file instead of standard output\n'
         '  --help:  Show this help text\n'
         )

def main():
    parser = argparse.ArgumentParser(description='Process Linux CREDITS and MAINTAINERS files')
    parser.add_argument('filename', metavar='FILE',
                        help="CREDITS or MAINTAINERS file to parse")
    parser.add_argument('--html', dest='formatter', action='store_const',
                        const=format_html, default=format_text,
                        help='Output as HTML-formatted text')
    parser.add_argument('--latex', '--LaTeX',
                        dest='formatter', action='store_const',
                        const=format_latex,
                        help='Output as LaTeX source')
    parser.add_argument('--text', dest='formatter', action='store_const',
                        const=format_text,
                        help='Output as plain text (default)')
    parser.add_argument('-o', '-output', dest='outfile', type=str,
                        metavar='OUTFILE',
                        help='Output to OUTFILE instead of standard output')
    
    args = parser.parse_args()

    if args.outfile:
        outfile = open(args.outfile, 'w')
    else:
        outfile = sys.stdout
        
    infile = open(args.filename, 'r')
    info, maintainers = parse(infile)
    infile.close()

    args.formatter(info, args.filename, outfile, maintainers)
    if outfile != sys.stdout:
        outfile.close()
    return

if __name__ == '__main__':
    main()
