#!/usr/bin/python3
# Rewrite the main orig tarball made by uscan without the MDN documentation.
#
# Some sources of the Scala.js library (scala.scalajs.js) say that their doc
# comments marked "MDN" are by Mozilla Contributors under CC-BY-SA, and a few
# more quote MDN next to a link to it. In every .scala file that carries that
# notice, such a marker, or (in scala/scalajs/js) a link to MDN, each
# /** ... */ comment, and the /* ... */ comment holding the notice, is
# replaced by spaces. Newlines are kept, so line and column numbers of all code, and the
# compiled classes and IR, do not change. See debian/README.source.
#
# Usage: debian/repack-mdn ../scala-js_<version>+ds.orig.tar.xz
# The tarball is rewritten in place, reproducibly (sorted members, owner
# root, mtimes kept from the input, xz preset 6).

import io
import lzma
import os
import re
import sys
import tarfile

NOTICE = 'Mozilla Contributors'
MARKER = re.compile(r'^\s*\*\s*MDN\s*$', re.M)


def has_mdn(name, src):
    return (NOTICE in src or MARKER.search(src) is not None
            or ('/scala/scalajs/js/' in name and 'developer.mozilla.org' in src))


def strip_comments(src):
    out = list(src)
    touched = set()
    i, n, line = 0, len(src), 0

    def blank(a, b):
        ln = line
        for k in range(a, b):
            if src[k] == '\n':
                ln += 1
            else:
                out[k] = ' '
                touched.add(ln)

    while i < n:
        c = src[i]
        if c == '\n':
            line += 1
            i += 1
        elif src.startswith('"""', i):
            j = src.find('"""', i + 3)
            j = n if j < 0 else j + 3
            while j < n and src[j] == '"':
                j += 1
            line += src.count('\n', i, j)
            i = j
        elif c == '"':
            j = i + 1
            while j < n and src[j] != '"' and src[j] != '\n':
                j += 2 if src[j] == '\\' else 1
            i = j + 1
        elif c == "'":
            # character literal: 'x', '\n', 'A'; otherwise skip the quote
            if i + 2 < n and src[i + 1] != '\\' and src[i + 2] == "'":
                i += 3
            elif i + 1 < n and src[i + 1] == '\\':
                j = src.find("'", i + 2)
                i = i + 1 if j < 0 or j - i > 8 else j + 1
            else:
                i += 1
        elif src.startswith('//', i):
            j = src.find('\n', i)
            i = n if j < 0 else j
        elif src.startswith('/*', i):
            start = i
            depth, j = 1, i + 2
            while j < n and depth:
                if src.startswith('/*', j):
                    depth += 1
                    j += 2
                elif src.startswith('*/', j):
                    depth -= 1
                    j += 2
                else:
                    j += 1
            doc = src.startswith('/**', start) and not src.startswith('/**/', start)
            if doc or NOTICE in src[start:j]:
                blank(start, j)
            line += src.count('\n', start, j)
            i = j
        else:
            i += 1

    lines = ''.join(out).split('\n')
    for ln in touched:
        lines[ln] = lines[ln].rstrip(' \t')
    return '\n'.join(lines)


def main(path):
    with tarfile.open(path, 'r:xz') as tin:
        members = sorted(tin.getmembers(), key=lambda m: m.name)
        data = {m.name: tin.extractfile(m).read() if m.isfile() else None
                for m in members}
    buf = io.BytesIO()
    changed = []
    with tarfile.open(fileobj=buf, mode='w', format=tarfile.GNU_FORMAT) as tout:
        for m in members:
            m.uid = m.gid = 0
            m.uname = m.gname = 'root'
            body = data[m.name]
            if m.isfile() and m.name.endswith('.scala'):
                src = body.decode('utf-8')
                if has_mdn(m.name, src):
                    body = strip_comments(src).encode('utf-8')
                    m.size = len(body)
                    changed.append(m.name)
            tout.addfile(m, io.BytesIO(body) if m.isfile() else None)
    tmp = path + '.tmp'
    with open(tmp, 'wb') as f:
        f.write(lzma.compress(buf.getvalue(), preset=6))
    if os.path.islink(path):
        os.unlink(path)
    os.replace(tmp, path)
    for name in changed:
        print('stripped', name)


if __name__ == '__main__':
    if len(sys.argv) != 2:
        sys.exit('usage: debian/repack-mdn <orig tarball>')
    main(sys.argv[1])
