#!/usr/bin/python3
# Rewrite the scalajs-dom component tarball made by uscan without the
# Scaladoc comments (/** ... */) of its Scala sources.
#
# Why: scala-js-dom's LICENSE says the documentation of the facades comes
# from the Mozilla Developer Network and is under CC-BY-SA 2.5 or later.
# That documentation is the Scaladoc comments. They are not compiled, so
# removing them changes no compiled output, and the component is then
# plain MIT (Expat) code. See debian/README.source.
#
# How: every character of a /** ... */ comment except newlines becomes a
# space, and lines that held such a comment lose their trailing blanks. Line
# and column numbers of all code stay the same, so the Scala.js IR (which
# records source positions) is identical too. Ordinary /* */ and //
# comments and code are kept as they are; strings too, except that MDN's
# "deprecated" banner sentence is removed from two @deprecated messages.
#
# Usage: debian/repack-scalajs-dom ../scala3_<version>.orig-scalajs-dom.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 sys
import tarfile


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

    def blank(a, b):
        nonlocal line
        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:
                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).replace(MDN_BANNER, '')


# MDN's "deprecated" banner, quoted in two @deprecated messages.
MDN_BANNER = (
    'This feature is no longer recommended. Though some browsers might '
    'still support it, it may have already been removed from the relevant '
    'web standards, may be in the process of being dropped, or may only be '
    'kept for compatibility purposes. Avoid using it, and update existing '
    'code if possible. ')


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()
    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'):
                body = strip_doc_comments(body.decode('utf-8')).encode('utf-8')
                m.size = len(body)
            tout.addfile(m, io.BytesIO(body) if m.isfile() else None)
    with open(path, 'wb') as f:
        f.write(lzma.compress(buf.getvalue(), preset=6))


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