packages feed

proarrow-0.4.0.0: tools/haddock/interfaces.py

#!/usr/bin/env python3
"""Collect library interfaces and check the combined Haddock index."""

import argparse
import json
from html.parser import HTMLParser
from pathlib import Path
import shutil
import sys


def collect(package, builddir):
    package = package.resolve()
    project = next((p for p in (package, *package.parents) if (p / 'cabal.project').is_file()), None)
    if project is None:
        raise ValueError('No cabal.project found above the package directory')
    plan = json.loads((project / builddir / 'cache/plan.json').read_text())
    for component, relative in (
        ('lib', 'doc/html/proarrow/proarrow.haddock'),
        ('lib:testing', 'doc/html/proarrow/testing/testing.haddock'),
    ):
        entries = [entry for entry in plan['install-plan']
                   if entry.get('pkg-name') == 'proarrow'
                   and entry.get('component-name') == component
                   and Path(entry.get('pkg-src', {}).get('path', '')).resolve() == package]
        if len(entries) != 1:
            raise ValueError(f'Expected one local proarrow {component} in the build plan, found {len(entries)}')
        source = Path(entries[0]['dist-dir']) / relative
        shutil.copyfile(source, package / 'docs' / source.name)


def check_index(index):
    class Captions(HTMLParser):
        def __init__(self):
            super().__init__()
            self.current = None
            self.packages = set()

        def handle_starttag(self, tag, attrs):
            if tag == 'p' and 'caption' in dict(attrs).get('class', '').split():
                self.current = []

        def handle_data(self, data):
            if self.current is not None:
                self.current.append(data)

        def handle_endtag(self, tag):
            if tag == 'p' and self.current is not None:
                self.packages.add(''.join(self.current).strip().rsplit('-', 1)[0])
                self.current = None

    captions = Captions()
    captions.feed(index.read_text())
    missing = {'proarrow', 'proarrow:testing'} - captions.packages
    if missing:
        raise ValueError(f'{index} does not list packages: {", ".join(sorted(missing))}')


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--check-index', action='store_true')
    parser.add_argument('--builddir', type=Path, default=Path('dist-newstyle'))
    args, _ = parser.parse_known_args()
    try:
        if args.check_index:
            check_index(Path('docs/index.html'))
        else:
            collect(Path.cwd().resolve(), args.builddir)
    except (OSError, ValueError, KeyError) as error:
        sys.exit(f'Haddock documentation: {error}')


if __name__ == '__main__':
    main()