these are specific to objectsexceptions c re verbose def parselineline m re matchregex line if not m return none static extern capi name def decl excname m groups if def isdecl false if extern or capi raise notimplementederrorline kind static if static else none elif excname name f pyexcexcname isdecl false kind static else isdecl true if static kind static elif extern kind extern elif capi kind capi else kind none return name isdecl kind class builtintypedeclnamedtuple builtintypedecl file lno name kind kinds static extern capi forward classmethod def fromlinecls line filename lno this is similar to capi capiitem fromline parsed parselineline if not parsed return none name isdecl kind parsed if not isdecl return none return cls fromparsedname kind filename lno classmethod def fromparsedcls name kind filename lno if not kind kind forward return cls fromvaluesfilename lno name kind classmethod def fromvaluescls filename lno name kind if kind not in cls kinds raise valueerrorf unsupported kind kind r self clsfilename lno name kind if self kind not in extern capi and self api raise notimplementederrorself elif self kind capi and not self api raise notimplementederrorself return self property def relfileself return self filelenreporoot 1 property def apiself return self relfile startswithcapiprefix property def internalself return self relfile startswithinternalprefix property def privateself if not self name startswith return false return self api and not self internal property def publicself if self kind capi return false return not self internal and not self private class builtintypeinfonamedtuple builtintypeinfo file lno name static decl classmethod def fromlinecls line filename lno declsnone parsed parselineline if not parsed return none name isdecl kind parsed if isdecl return none return cls fromparsedname kind filename lno declsdecls classmethod def fromparsedcls name kind filename lno declsnone if not kind static false elif kind static static true else raise notimplementederrorfilename line kind decl decls getname if decls else none return clsfilename lno name static decl property def relfileself return self filelenreporoot 1 property def exportedself return not self static property def apiself if not self decl return false return self decl api property def internalself if not self decl return false return self decl internal property def privateself if not self decl return false return self decl private property def publicself if not self decl return false return self decl public property def inmoduleself return self relfile startswith modules os path sep def renderrowvaluesself kinds row name self name k for k in kinds filename f self relfile self lno if self static kind static else if self internal kind internal elif self private kind private elif self public kind public else kind global row kind kind rowkind kind return row def ensuredecldecl decls prev decls getdecl name if prev if decl kind forward return none if prev kind forward if decl kind prev kind and decl file prev file assert decl lno prev lno decl prev return none raise notimplementederrorf duplicate decl was prev declsdecl name decl def iterbuiltintypesfilenamesnone decls seen set for filename in iterheaderfiles seen addfilename with openfilename as infile for lno line in enumerateinfile 1 decl builtintypedecl fromlineline filename lno if not decl continue ensuredecldecl decls srcfiles for filename in iterfilenames if filename endswith c srcfiles appendfilename continue if filename in seen continue with openfilename as infile for lno line in enumerateinfile 1 decl builtintypedecl fromlineline filename lno if not decl continue ensuredecldecl decls for filename in srcfiles with openfilename as infile localdecls for lno line in enumerateinfile 1 parsed parselineline if not parsed continue name isdecl kind parsed if isdecl decl builtintypedecl fromparsedname kind filename lno if not decl raise notimplementederrorfilename line ensuredecldecl localdecls else builtin builtintypeinfo fromparsed name kind filename lno declsdecls if name in decls else localdecls if not builtin raise notimplementederrorfilename line yield builtin def resolvematchershowmodulesfalse def matchinfo lognone if not info inmodule return true if log is not none logf ignored info name r return false return match cli rendering def resolveformatfmt if not fmt return table elif isinstancefmt str and fmt in formats return fmt else raise notimplementederrorfmt def getrendererfmt fmt resolveformatfmt if isinstancefmt str try return formatsfmt except keyerror raise valueerrorf unsupported format fmt r else raise notimplementederrorfmt def rendertabletypes types sortedtypes keylambda t t name colspecs tables resolvecolumns name 33 static global internal private public filename 30 header div rowfmt tables buildtablecolspecs leader sumc width2 for c in colspecs 3 yield leader f api 29 yield leader 29 yield header yield div kinds c0 for c in colspecs1 1 counts k 0 for k in kinds base k for k in kinds for t in types row t renderrowvalueskinds kind row kind yield rowfmt formatrow countskind 1 yield yield f total sumcounts values 3 for kind in kinds yield f kind 10 countskind 3 def renderreprtypes for t in types yield reprt formats table rendertable repr renderrepr static static s extern extern s pytypeobject s pyapi_data capi s s pytypeobject s s w name s s def decl these are specific to objects exceptions c simpleextendsexception middlingextendsexception complexextendsexception w s s w excname s this is similar to _capi capiitem from_line cli rendering
from collections import namedtuple
import os.path
import re
import textwrap

from c_common import tables
from . import REPO_ROOT
from ._files import iter_header_files, iter_filenames


CAPI_PREFIX = os.path.join('Include', '')
INTERNAL_PREFIX = os.path.join('Include', 'internal', '')

REGEX = re.compile(textwrap.dedent(rf), re.VERBOSE)


def _parse_line(line):
    m = re.match(REGEX, line)
    if not m:
        return None
    (static, extern, capi,
     name,
     def_, decl,
     excname,
     ) = m.groups()
    if def_:
        isdecl = False
        if extern or capi:
            raise NotImplementedError(line)
        kind = 'static' if static else None
    elif excname:
        name = f'_PyExc_{excname}'
        isdecl = False
        kind = 'static'
    else:
        isdecl = True
        if static:
            kind = 'static'
        elif extern:
            kind = 'extern'
        elif capi:
            kind = 'capi'
        else:
            kind = None
    return name, isdecl, kind


class BuiltinTypeDecl(namedtuple('BuiltinTypeDecl', 'file lno name kind')):

    KINDS = {
        'static',
        'extern',
        'capi',
        'forward',
    }

    @classmethod
    def from_line(cls, line, filename, lno):
        
        parsed = _parse_line(line)
        if not parsed:
            return None
        name, isdecl, kind = parsed
        if not isdecl:
            return None
        return cls.from_parsed(name, kind, filename, lno)

    @classmethod
    def from_parsed(cls, name, kind, filename, lno):
        if not kind:
            kind = 'forward'
        return cls.from_values(filename, lno, name, kind)

    @classmethod
    def from_values(cls, filename, lno, name, kind):
        if kind not in cls.KINDS:
            raise ValueError(f'unsupported kind {kind!r}')
        self = cls(filename, lno, name, kind)
        if self.kind not in ('extern', 'capi') and self.api:
            raise NotImplementedError(self)
        elif self.kind == 'capi' and not self.api:
            raise NotImplementedError(self)
        return self

    @property
    def relfile(self):
        return self.file[len(REPO_ROOT) + 1:]

    @property
    def api(self):
        return self.relfile.startswith(CAPI_PREFIX)

    @property
    def internal(self):
        return self.relfile.startswith(INTERNAL_PREFIX)

    @property
    def private(self):
        if not self.name.startswith('_'):
            return False
        return self.api and not self.internal

    @property
    def public(self):
        if self.kind != 'capi':
            return False
        return not self.internal and not self.private


class BuiltinTypeInfo(namedtuple('BuiltinTypeInfo', 'file lno name static decl')):

    @classmethod
    def from_line(cls, line, filename, lno, *, decls=None):
        parsed = _parse_line(line)
        if not parsed:
            return None
        name, isdecl, kind = parsed
        if isdecl:
            return None
        return cls.from_parsed(name, kind, filename, lno, decls=decls)

    @classmethod
    def from_parsed(cls, name, kind, filename, lno, *, decls=None):
        if not kind:
            static = False
        elif kind == 'static':
            static = True
        else:
            raise NotImplementedError((filename, line, kind))
        decl = decls.get(name) if decls else None
        return cls(filename, lno, name, static, decl)

    @property
    def relfile(self):
        return self.file[len(REPO_ROOT) + 1:]

    @property
    def exported(self):
        return not self.static

    @property
    def api(self):
        if not self.decl:
            return False
        return self.decl.api

    @property
    def internal(self):
        if not self.decl:
            return False
        return self.decl.internal

    @property
    def private(self):
        if not self.decl:
            return False
        return self.decl.private

    @property
    def public(self):
        if not self.decl:
            return False
        return self.decl.public

    @property
    def inmodule(self):
        return self.relfile.startswith('Modules' + os.path.sep)

    def render_rowvalues(self, kinds):
        row = {
            'name': self.name,
            **{k: '' for k in kinds},
            'filename': f'{self.relfile}:{self.lno}',
        }
        if self.static:
            kind = 'static'
        else:
            if self.internal:
                kind = 'internal'
            elif self.private:
                kind = 'private'
            elif self.public:
                kind = 'public'
            else:
                kind = 'global'
        row['kind'] = kind
        row[kind] = kind
        return row


def _ensure_decl(decl, decls):
    prev = decls.get(decl.name)
    if prev:
        if decl.kind == 'forward':
            return None
        if prev.kind != 'forward':
            if decl.kind == prev.kind and decl.file == prev.file:
                assert decl.lno != prev.lno, (decl, prev)
                return None
            raise NotImplementedError(f'duplicate {decl} (was {prev}')
    decls[decl.name] = decl


def iter_builtin_types(filenames=None):
    decls = {}
    seen = set()
    for filename in iter_header_files():
        seen.add(filename)
        with open(filename) as infile:
            for lno, line in enumerate(infile, 1):
                decl = BuiltinTypeDecl.from_line(line, filename, lno)
                if not decl:
                    continue
                _ensure_decl(decl, decls)
    srcfiles = []
    for filename in iter_filenames():
        if filename.endswith('.c'):
            srcfiles.append(filename)
            continue
        if filename in seen:
            continue
        with open(filename) as infile:
            for lno, line in enumerate(infile, 1):
                decl = BuiltinTypeDecl.from_line(line, filename, lno)
                if not decl:
                    continue
                _ensure_decl(decl, decls)

    for filename in srcfiles:
        with open(filename) as infile:
            localdecls = {}
            for lno, line in enumerate(infile, 1):
                parsed = _parse_line(line)
                if not parsed:
                    continue
                name, isdecl, kind = parsed
                if isdecl:
                    decl = BuiltinTypeDecl.from_parsed(name, kind, filename, lno)
                    if not decl:
                        raise NotImplementedError((filename, line))
                    _ensure_decl(decl, localdecls)
                else:
                    builtin = BuiltinTypeInfo.from_parsed(
                            name, kind, filename, lno,
                            decls=decls if name in decls else localdecls)
                    if not builtin:
                        raise NotImplementedError((filename, line))
                    yield builtin


def resolve_matcher(showmodules=False):
    def match(info, *, log=None):
        if not info.inmodule:
            return True
        if log is not None:
            log(f'ignored {info.name!r}')
        return False
    return match





def resolve_format(fmt):
    if not fmt:
        return 'table'
    elif isinstance(fmt, str) and fmt in _FORMATS:
        return fmt
    else:
        raise NotImplementedError(fmt)


def get_renderer(fmt):
    fmt = resolve_format(fmt)
    if isinstance(fmt, str):
        try:
            return _FORMATS[fmt]
        except KeyError:
            raise ValueError(f'unsupported format {fmt!r}')
    else:
        raise NotImplementedError(fmt)


def render_table(types):
    types = sorted(types, key=(lambda t: t.name))
    colspecs = tables.resolve_columns(
            'name:<33 static:^ global:^ internal:^ private:^ public:^ filename:<30')
    header, div, rowfmt = tables.build_table(colspecs)
    leader = ' ' * sum(c.width+2 for c in colspecs[:3]) + '   '
    yield leader + f'{"API":^29}'
    yield leader + '-' * 29
    yield header
    yield div
    kinds = [c[0] for c in colspecs[1:-1]]
    counts = {k: 0 for k in kinds}
    base = {k: '' for k in kinds}
    for t in types:
        row = t.render_rowvalues(kinds)
        kind = row['kind']
        yield rowfmt.format(**row)
        counts[kind] += 1
    yield ''
    yield f'total: {sum(counts.values()):>3}'
    for kind in kinds:
        yield f'  {kind:>10}: {counts[kind]:>3}'


def render_repr(types):
    for t in types:
        yield repr(t)


_FORMATS = {
    'table': render_table,
    'repr': render_repr,
}