Blame gtkdoc-depscan.in

Packit Service 7866ab
#!@PYTHON@
Packit Service 7866ab
Packit Service 7866ab
from __future__ import print_function
Packit Service 7866ab
Packit Service 7866ab
import gzip, os.path, re
Packit Service 7866ab
Packit Service 7866ab
from os import environ, popen, walk
Packit Service 7866ab
from optparse import OptionParser
Packit Service 7866ab
from sys import stderr
Packit Service 7866ab
Packit Service 7866ab
from xml.sax import ContentHandler, make_parser
Packit Service 7866ab
from xml.sax.handler import feature_external_ges
Packit Service 7866ab
Packit Service 7866ab
default_books = ['atk', 'gdk', 'gdk-pixbuf', 'glib', 'gio', 'gobject', 'gtk', 'pango']
Packit Service 7866ab
Packit Service 7866ab
__comment_regex = re.compile(r'/\*.*?\*/', re.DOTALL)
Packit Service 7866ab
__word_regex = re.compile(r'\b[A-Za-z_][A-Za-z0-9_]*\b')
Packit Service 7866ab
Packit Service 7866ab
u2a_table = { 0x2018:0x27, 0x2019:0x27, 0x201C:0x22, 0x201D:0x22 }
Packit Service 7866ab
Packit Service 7866ab
class Book(object):
Packit Service 7866ab
    def __init__(self, name, folders, version=None):
Packit Service 7866ab
        self.__catalog   = None
Packit Service 7866ab
        self.__name      = name
Packit Service 7866ab
        self.__symbols   = None
Packit Service 7866ab
        self.__timestamp = 0
Packit Service 7866ab
        self.__title     = None
Packit Service 7866ab
        self.__version   = version
Packit Service 7866ab
Packit Service 7866ab
        for f in folders:
Packit Service 7866ab
            catalogs = map(
Packit Service 7866ab
                lambda n: os.path.join(f, name, n % name),
Packit Service 7866ab
                ['%s.devhelp2', '%s.devhelp2.gz'])
Packit Service 7866ab
            catalogs = map(
Packit Service 7866ab
                lambda n: (os.path.getmtime(n), n),
Packit Service 7866ab
                filter(os.path.isfile, catalogs))
Packit Service 7866ab
Packit Service 7866ab
            catalogs.sort()
Packit Service 7866ab
Packit Service 7866ab
            if catalogs:
Packit Service 7866ab
                self.__catalog = catalogs[-1][1]
Packit Service 7866ab
                break
Packit Service 7866ab
Packit Service 7866ab
        if not self.__catalog:
Packit Service 7866ab
            raise IOError('No devhelp book found for "%s"' % name)
Packit Service 7866ab
Packit Service 7866ab
    def __cmp__(self, other):
Packit Service 7866ab
        if isinstance(other, Book):
Packit Service 7866ab
            return cmp(self.name, other.name)
Packit Service 7866ab
Packit Service 7866ab
        return 0
Packit Service 7866ab
Packit Service 7866ab
    def __repr__(self):
Packit Service 7866ab
        return '<Book name="%s">' % self.__name
Packit Service 7866ab
Packit Service 7866ab
    def parse(self):
Packit Service 7866ab
        timestamp = os.path.getmtime(self.__catalog)
Packit Service 7866ab
Packit Service 7866ab
        if not self.__symbols or timestamp > self.__timestamp:
Packit Service 7866ab
            class DevhelpContentHandler(ContentHandler):
Packit Service 7866ab
                def __init__(self, book, symbols):
Packit Service 7866ab
                    self.__book = book
Packit Service 7866ab
                    self.__symbols = symbols
Packit Service 7866ab
Packit Service 7866ab
                def startElement(self, name, attrs):
Packit Service 7866ab
                    if 'book' == name:
Packit Service 7866ab
                        self.title = attrs.get('title')
Packit Service 7866ab
                        return
Packit Service 7866ab
Packit Service 7866ab
                    if 'keyword' == name:
Packit Service 7866ab
                        symbol = Symbol.from_xml(self.__book, attrs)
Packit Service 7866ab
                        if symbol: self.__symbols[symbol.name] = symbol
Packit Service 7866ab
                        return
Packit Service 7866ab
Packit Service 7866ab
            self.__symbols, self.__timestamp = dict(), timestamp
Packit Service 7866ab
            handler = DevhelpContentHandler(self, self.__symbols)
Packit Service 7866ab
Packit Service 7866ab
            parser = make_parser()
Packit Service 7866ab
            parser.setFeature(feature_external_ges, False)
Packit Service 7866ab
            parser.setContentHandler(handler)
Packit Service 7866ab
Packit Service 7866ab
            if self.__catalog.endswith('.gz'):
Packit Service 7866ab
                parser.parse(gzip.open(self.__catalog))
Packit Service 7866ab
Packit Service 7866ab
            else:
Packit Service 7866ab
                parser.parse(open(self.__catalog))
Packit Service 7866ab
Packit Service 7866ab
            self.__title = handler.title
Packit Service 7866ab
Packit Service 7866ab
    def _get_symbols(self):
Packit Service 7866ab
        self.parse(); return self.__symbols
Packit Service 7866ab
    def _get_title(self):
Packit Service 7866ab
        self.parse(); return self.__title
Packit Service 7866ab
Packit Service 7866ab
    def find_requirements(self):
Packit Service 7866ab
        requirements = dict()
Packit Service 7866ab
Packit Service 7866ab
        for symbol in self.symbols.values():
Packit Service 7866ab
            if not symbol.matches:
Packit Service 7866ab
                continue
Packit Service 7866ab
Packit Service 7866ab
            if symbol.since and symbol.since > self.version:
Packit Service 7866ab
                symbol_list = requirements.get(symbol.since, [])
Packit Service 7866ab
                requirements[symbol.since] = symbol_list
Packit Service 7866ab
                symbol_list.append(symbol)
Packit Service 7866ab
Packit Service 7866ab
        return requirements
Packit Service 7866ab
Packit Service 7866ab
    catalog = property(lambda self: self.__catalog)
Packit Service 7866ab
    name    = property(lambda self: self.__name)
Packit Service 7866ab
    version = property(lambda self: self.__version)
Packit Service 7866ab
Packit Service 7866ab
    symbols = property(_get_symbols)
Packit Service 7866ab
    title   = property(_get_title)
Packit Service 7866ab
Packit Service 7866ab
class Symbol(object):
Packit Service 7866ab
    known_attributes = ('name', 'type', 'link', 'deprecated', 'since')
Packit Service 7866ab
Packit Service 7866ab
    class DeprecationInfo(object):
Packit Service 7866ab
        def __init__(self, text):
Packit Service 7866ab
            if text.count(':'):
Packit Service 7866ab
                pair = text.split(':', 1)
Packit Service 7866ab
Packit Service 7866ab
                self.__version = Symbol.VersionInfo(pair[0])
Packit Service 7866ab
                self.__details = pair[1].strip()
Packit Service 7866ab
Packit Service 7866ab
            else:
Packit Service 7866ab
                self.__version = None
Packit Service 7866ab
                self.__details = text.strip()
Packit Service 7866ab
Packit Service 7866ab
        def __cmp__(self, other):
Packit Service 7866ab
            if isinstance(other, Symbol.DeprecationInfo):
Packit Service 7866ab
                return cmp(self.version, other.version)
Packit Service 7866ab
Packit Service 7866ab
            if isinstance(other, Symbol.VersionInfo):
Packit Service 7866ab
                return cmp(self.version, other)
Packit Service 7866ab
Packit Service 7866ab
            return 1
Packit Service 7866ab
Packit Service 7866ab
        def __str__(self):
Packit Service 7866ab
            if not self.__version:
Packit Service 7866ab
                return self.__details and str(self.__details) or 'Deprecated'
Packit Service 7866ab
Packit Service 7866ab
            if self.__details:
Packit Service 7866ab
                return 'Since %s: %s' % (self.__version, self.__details)
Packit Service 7866ab
Packit Service 7866ab
            return 'Since %s' % self.__version
Packit Service 7866ab
Packit Service 7866ab
        details = property(lambda self: self.__details)
Packit Service 7866ab
        version = property(lambda self: self.__version)
Packit Service 7866ab
Packit Service 7866ab
    class VersionInfo(object):
Packit Service 7866ab
        def __init__(self, text):
Packit Service 7866ab
            match = re.match(r'^\w*\s*((?:\d+\.)*\d+)', text)
Packit Service 7866ab
Packit Service 7866ab
            self.__numbers = map(int, match.group(1).split('.'))
Packit Service 7866ab
            self.__hash = reduce(lambda x, y: x * 1000 + y, reversed(self.__numbers))
Packit Service 7866ab
            self.__text = text.strip()
Packit Service 7866ab
Packit Service 7866ab
        def __get_number(self, index):
Packit Service 7866ab
            if len(self.__numbers) > index:
Packit Service 7866ab
                return self.__numbers[index]
Packit Service 7866ab
Packit Service 7866ab
            return 0
Packit Service 7866ab
Packit Service 7866ab
        def __cmp__(self, other):
Packit Service 7866ab
            if isinstance(other, Symbol.VersionInfo):
Packit Service 7866ab
                return cmp(self.numbers, other.numbers)
Packit Service 7866ab
Packit Service 7866ab
            return 1
Packit Service 7866ab
Packit Service 7866ab
        def __hash__(self):
Packit Service 7866ab
            return self.__hash
Packit Service 7866ab
Packit Service 7866ab
        def __repr__(self):
Packit Service 7866ab
            return '.'.join(map(str, self.__numbers))
Packit Service 7866ab
Packit Service 7866ab
        major   = property(lambda self: self.__get_number(0))
Packit Service 7866ab
        minor   = property(lambda self: self.__get_number(1))
Packit Service 7866ab
        patch   = property(lambda self: self.__get_number(2))
Packit Service 7866ab
        numbers = property(lambda self: self.__numbers)
Packit Service 7866ab
        text    = property(lambda self: self.__text)
Packit Service 7866ab
Packit Service 7866ab
    @classmethod
Packit Service 7866ab
    def from_xml(cls, book, attrs):
Packit Service 7866ab
        name, type, link, deprecated, since = map(attrs.get, Symbol.known_attributes)
Packit Service 7866ab
Packit Service 7866ab
        name = name.strip().translate(u2a_table)
Packit Service 7866ab
Packit Service 7866ab
        if name.endswith('()'):
Packit Service 7866ab
            if not type in ('function', 'macro'):
Packit Service 7866ab
                type = (name[0].islower() and 'function' or 'macro')
Packit Service 7866ab
Packit Service 7866ab
            name = name[:-2].strip()
Packit Service 7866ab
Packit Service 7866ab
        words = name.split(' ')
Packit Service 7866ab
Packit Service 7866ab
        if len(words) > 1:
Packit Service 7866ab
            if words[0] in ('enum', 'struct', 'union'):
Packit Service 7866ab
                if not type: type = words[0]
Packit Service 7866ab
                name = name[len(words[0]):].strip()
Packit Service 7866ab
            elif 'property' == words[-1]:
Packit Service 7866ab
                assert('The' == words[0])
Packit Service 7866ab
                owner = link.split('#', 1)[1].split('-', 1)[0]
Packit Service 7866ab
                type, name = 'property', '%s::%s' % (owner, name.split('"')[1])
Packit Service 7866ab
            elif 'signal' == words[-1]:
Packit Service 7866ab
                assert('The' == words[0])
Packit Service 7866ab
                owner = link.split('#', 1)[1].split('-', 1)[0]
Packit Service 7866ab
                type, name = 'signal', '%s:%s' % (owner, name.split('"')[1])
Packit Service 7866ab
Packit Service 7866ab
        if not type: return None
Packit Service 7866ab
Packit Service 7866ab
        if None != deprecated: deprecated = Symbol.DeprecationInfo(deprecated)
Packit Service 7866ab
        if since: since = Symbol.VersionInfo(since)
Packit Service 7866ab
Packit Service 7866ab
        if name.count(' '):
Packit Service 7866ab
            print >>stderr, (
Packit Service 7866ab
                'WARNING: Malformed symbol name: "%s" (type=%s) in %s.' % (
Packit Service 7866ab
                name, type, book.name))
Packit Service 7866ab
Packit Service 7866ab
        return Symbol(book, name, type, link, deprecated, since)
Packit Service 7866ab
Packit Service 7866ab
    def __init__(self, book, name, type, link=None, deprecated=None, since=None):
Packit Service 7866ab
        self.__book       = book
Packit Service 7866ab
        self.__name       = name
Packit Service 7866ab
        self.__type       = type
Packit Service 7866ab
        self.__link       = link
Packit Service 7866ab
        self.__deprecated = deprecated
Packit Service 7866ab
        self.__since      = since
Packit Service 7866ab
        self.__matches    = []
Packit Service 7866ab
Packit Service 7866ab
    def __repr__(self):
Packit Service 7866ab
        return (
Packit Service 7866ab
            '<Symbol: %s, type=%s, since=%s, deprecated=%s>' % (
Packit Service 7866ab
            self.name, self.type, self.since, self.deprecated))
Packit Service 7866ab
Packit Service 7866ab
Packit Service 7866ab
    book       = property(lambda self: self.__book)
Packit Service 7866ab
    name       = property(lambda self: self.__name)
Packit Service 7866ab
    type       = property(lambda self: self.__type)
Packit Service 7866ab
    link       = property(lambda self: self.__link)
Packit Service 7866ab
    deprecated = property(lambda self: self.__deprecated)
Packit Service 7866ab
    matches    = property(lambda self: self.__matches)
Packit Service 7866ab
    since      = property(lambda self: self.__since)
Packit Service 7866ab
Packit Service 7866ab
def parse_cmdline():
Packit Service 7866ab
    options = OptionParser(version="@VERSION@")
Packit Service 7866ab
Packit Service 7866ab
    options.add_option('-b', '--book', dest='books',
Packit Service 7866ab
                       help='name of a devhelp book to consider',
Packit Service 7866ab
                       default=[], action='append')
Packit Service 7866ab
    options.add_option('-d', '--html-dir', metavar='PATH', dest='dirs',
Packit Service 7866ab
                       help='path of additional folders with devhelp books',
Packit Service 7866ab
                       default=[], action='append')
Packit Service 7866ab
    options.add_option('-s', '--summarize', action='store_true', default=False,
Packit Service 7866ab
                       help='print only a brief summary', dest='summarize')
Packit Service 7866ab
    options.add_option('-u', '--list-unknown', action='store_true', default=False,
Packit Service 7866ab
                       help='list symbols not found in any book', dest='unknown')
Packit Service 7866ab
    options.add_option('-v', '--verbose', action='store_true', default=False,
Packit Service 7866ab
                       help='print additional information')
Packit Service 7866ab
Packit Service 7866ab
    return options.parse_args()
Packit Service 7866ab
Packit Service 7866ab
def merge_gnome_path(options):
Packit Service 7866ab
    path = environ.get('GNOME2_PATH')
Packit Service 7866ab
    path = path and path.split(':') or []
Packit Service 7866ab
Packit Service 7866ab
    prefix = popen(
Packit Service 7866ab
        '@PKG_CONFIG@ --variable=prefix glib-2.0'
Packit Service 7866ab
        ).readline().rstrip()
Packit Service 7866ab
Packit Service 7866ab
    path.insert(0, prefix)
Packit Service 7866ab
    path = filter(None, [p.strip() for p in path])
Packit Service 7866ab
Packit Service 7866ab
    path = [[
Packit Service 7866ab
        os.path.join(p, 'share', 'devhelp', 'books'),
Packit Service 7866ab
        os.path.join(p, 'share', 'gtk-doc', 'html')]
Packit Service 7866ab
        for p in path]
Packit Service 7866ab
Packit Service 7866ab
    path = reduce(list.__add__, path)
Packit Service 7866ab
    path = filter(os.path.isdir, path)
Packit Service 7866ab
Packit Service 7866ab
    options.dirs += path
Packit Service 7866ab
Packit Service 7866ab
def summarize_matches(matches):
Packit Service 7866ab
    counts = {}
Packit Service 7866ab
    for filename, lineno, symbol in matches:
Packit Service 7866ab
        if not isinstance(symbol, Symbol):
Packit Service 7866ab
            if options.verbose:
Packit Service 7866ab
                print('%s:%d: unknown symbol %s' % (filename, lineno, symbol))
Packit Service 7866ab
            continue
Packit Service 7866ab
Packit Service 7866ab
        since = '%s-%s' % (symbol.book.name, symbol.since)
Packit Service 7866ab
        name = symbol.name
Packit Service 7866ab
        if since not in counts:
Packit Service 7866ab
            counts[since] = {}
Packit Service 7866ab
        counts[since][name] = counts[since].get(name, 0) + 1
Packit Service 7866ab
Packit Service 7866ab
    for since, stats in counts.items():
Packit Service 7866ab
        counts[since] = list(sorted(stats.items(), key=lambda x: -x[1]))
Packit Service 7866ab
Packit Service 7866ab
    return counts
Packit Service 7866ab
Packit Service 7866ab
if '__main__' == __name__:
Packit Service 7866ab
    options, args = parse_cmdline()
Packit Service 7866ab
Packit Service 7866ab
    merge_gnome_path(options)
Packit Service 7866ab
Packit Service 7866ab
    if not options.books:
Packit Service 7866ab
        options.books = default_books
Packit Service 7866ab
Packit Service 7866ab
    def trace(message, *args):
Packit Service 7866ab
        if options.verbose: print(message % args)
Packit Service 7866ab
Packit Service 7866ab
    def parse_book(name):
Packit Service 7866ab
        try:
Packit Service 7866ab
            match = re.match(r'^(.*?)(?::(\d+(?:\.\d+)*))?$', name)
Packit Service 7866ab
            name, version = match.groups()
Packit Service 7866ab
            trace('reading book: %s', name)
Packit Service 7866ab
Packit Service 7866ab
            version = version and Symbol.VersionInfo(version)
Packit Service 7866ab
            return name, Book(name, options.dirs, version)
Packit Service 7866ab
Packit Service 7866ab
        except IOError as e:
Packit Service 7866ab
            print >>stderr, 'WARNING: %s.' % e
Packit Service 7866ab
Packit Service 7866ab
    def scan_source_file(name):
Packit Service 7866ab
        contents = None
Packit Service 7866ab
Packit Service 7866ab
        try:
Packit Service 7866ab
            contents = __comment_regex.sub('', file(name).read())
Packit Service 7866ab
Packit Service 7866ab
        except IOError as e:
Packit Service 7866ab
            print >>stderr, e
Packit Service 7866ab
Packit Service 7866ab
        if contents:
Packit Service 7866ab
            trace('scanning: %s', name)
Packit Service 7866ab
            lines = contents.split('\n')
Packit Service 7866ab
Packit Service 7866ab
            for lineno in range(len(lines)):
Packit Service 7866ab
                for word in __word_regex.findall(lines[lineno]):
Packit Service 7866ab
                    symbol = symbols.get(word)
Packit Service 7866ab
Packit Service 7866ab
                    if symbol:
Packit Service 7866ab
                        symbol.matches.append((name, lineno, symbol))
Packit Service 7866ab
Packit Service 7866ab
                    elif options.unknown and word.find('_') > 0:
Packit Service 7866ab
                        unknown_symbols.append((name, lineno, word))
Packit Service 7866ab
Packit Service 7866ab
    unknown_symbols = []
Packit Service 7866ab
    matches, symbols = dict(), dict()
Packit Service 7866ab
    books = dict(filter(None, map(parse_book, set(options.books))))
Packit Service 7866ab
Packit Service 7866ab
    for book in books.values():
Packit Service 7866ab
        symbols.update(book.symbols)
Packit Service 7866ab
Packit Service 7866ab
    for name in args:
Packit Service 7866ab
        if os.path.isdir(name):
Packit Service 7866ab
            for path, dirs, files in walk(name):
Packit Service 7866ab
                for f in files:
Packit Service 7866ab
                    if f.endswith('.c'):
Packit Service 7866ab
                        scan_source_file(os.path.join(path, f))
Packit Service 7866ab
Packit Service 7866ab
        else:
Packit Service 7866ab
            scan_source_file(name)
Packit Service 7866ab
Packit Service 7866ab
    matches = []
Packit Service 7866ab
Packit Service 7866ab
    for book in books.values():
Packit Service 7866ab
        requirements = book.find_requirements().items()
Packit Service 7866ab
        requirements.sort()
Packit Service 7866ab
Packit Service 7866ab
        if requirements:
Packit Service 7866ab
            for symbol in requirements[-1][1]:
Packit Service 7866ab
                matches += symbol.matches
Packit Service 7866ab
Packit Service 7866ab
    if options.unknown:
Packit Service 7866ab
        matches += unknown_symbols
Packit Service 7866ab
Packit Service 7866ab
    matches.sort()
Packit Service 7866ab
Packit Service 7866ab
    if options.summarize:
Packit Service 7866ab
        summary = summarize_matches(matches)
Packit Service 7866ab
        for since in sorted(summary.keys()):
Packit Service 7866ab
            print('%s required for' % since)
Packit Service 7866ab
            for x in summary[since]:
Packit Service 7866ab
                print('    %u %s' % (x[1], x[0]))
Packit Service 7866ab
    else:
Packit Service 7866ab
        for filename, lineno, symbol in matches:
Packit Service 7866ab
            if isinstance(symbol, Symbol):
Packit Service 7866ab
                args = filename, lineno, symbol.book.name, symbol.since, symbol.name
Packit Service 7866ab
                print('%s:%d: %s-%s required for %s' % args)
Packit Service 7866ab
Packit Service 7866ab
            elif options.verbose:
Packit Service 7866ab
                print('%s:%d: unknown symbol %s' % (filename, lineno, symbol))
Packit Service 7866ab
Packit Service 7866ab
    if options.unknown:
Packit Service 7866ab
        unknown = [m[2].split('_')[0].lower() for m in unknown_symbols]
Packit Service 7866ab
        unknown = list(set(unknown))
Packit Service 7866ab
        unknown.sort()
Packit Service 7866ab
Packit Service 7866ab
        print('unknown prefixes: %s' % ', '.join(unknown))
Packit Service 7866ab
Packit Service 7866ab
    raise SystemExit(matches and 1 or 0)