| #!/usr/bin/python |
| # -*- Mode: Python -*- |
| # vi:si:et:sw=4:sts=4:ts=4 |
| |
| """ |
| parse, merge and write gstdoc-scanobj files |
| """ |
| |
| from __future__ import print_function, unicode_literals |
| |
| import codecs |
| import os |
| import sys |
| |
| def debug(*args): |
| pass |
| |
| # OrderedDict class based on |
| # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747 |
| # Licensed under the Python License |
| class OrderedDict(dict): |
| def __init__(self): |
| self._keys = [] |
| dict.__init__(self) |
| |
| def __delitem__(self, key): |
| dict.__delitem__(self, key) |
| self._keys.remove(key) |
| |
| def __setitem__(self, key, item): |
| dict.__setitem__(self, key, item) |
| if key not in self._keys: self._keys.append(key) |
| |
| def clear(self): |
| dict.clear(self) |
| self._keys = [] |
| |
| def copy(self): |
| dict = dict.copy(self) |
| dict._keys = self._keys[:] |
| return dict |
| |
| def items(self): |
| return zip(self._keys, self.values()) |
| |
| def keys(self): |
| return self._keys |
| |
| def popitem(self): |
| try: |
| key = self._keys[-1] |
| except IndexError: |
| raise KeyError('dictionary is empty') |
| |
| val = self[key] |
| del self[key] |
| |
| return (key, val) |
| |
| def setdefault(self, key, failobj = None): |
| dict.setdefault(self, key, failobj) |
| if key not in self._keys: self._keys.append(key) |
| |
| def update(self, dict): |
| dict.update(self, dict) |
| for key in dict.keys(): |
| if key not in self._keys: self._keys.append(key) |
| |
| def values(self): |
| return map(self.get, self._keys) |
| |
| class Object: |
| def __init__(self, name): |
| self._signals = OrderedDict() |
| self._args = OrderedDict() |
| self.name = name |
| |
| def __repr__(self): |
| return "<Object %s>" % self.name |
| |
| def add_signal(self, signal, overwrite=True): |
| if not overwrite and signal.name in self._signals: |
| raise IndexError("signal %s already in %r" % (signal.name, self)) |
| self._signals[signal.name] = signal |
| |
| def add_arg(self, arg, overwrite=True): |
| if not overwrite and arg.name in self._args: |
| raise IndexError("arg %s already in %r" % (arg.name, self)) |
| self._args[arg.name] = arg |
| |
| class Docable: |
| def __init__(self, **kwargs): |
| for key in self.attrs: |
| setattr(self, key, kwargs[key]) |
| self.dict = kwargs |
| |
| def __repr__(self): |
| return "<%r %s>" % (str(self.__class__), self.name) |
| |
| class Signal(Docable): |
| attrs = ['name', 'returns', 'args'] |
| |
| class Arg(Docable): |
| attrs = ['name', 'type', 'range', 'flags', 'nick', 'blurb', 'default'] |
| |
| class GDoc: |
| def load_file(self, filename): |
| try: |
| lines = codecs.open(filename, encoding='utf-8').readlines() |
| self.load_data("".join(lines)) |
| except IOError: |
| print ("WARNING - could not read from %s" % filename) |
| except UnicodeDecodeError as e: |
| print ("WARNING - could not parse %s: %s" % (filename, e)) |
| |
| def save_file(self, filename, backup=False): |
| """ |
| Save the information to the given file if the file content changed. |
| """ |
| olddata = None |
| try: |
| lines = codecs.open(filename, encoding='utf-8').readlines() |
| olddata = "".join(lines) |
| except IOError: |
| print ("WARNING - could not read from %s" % filename) |
| newdata = self.get_data() |
| if olddata and olddata == newdata: |
| return |
| |
| if olddata: |
| if backup: |
| os.rename(filename, filename + '.bak') |
| |
| handle = codecs.open(filename, "w", encoding='utf-8') |
| handle.write(newdata) |
| handle.close() |
| |
| class Signals(GDoc): |
| def __init__(self): |
| self._objects = OrderedDict() |
| |
| def load_data(self, data): |
| """ |
| Load the .signals lines, creating our list of objects and signals. |
| """ |
| import re |
| smatcher = re.compile( |
| '(?s)' # make . match \n |
| '<SIGNAL>\n(.*?)</SIGNAL>\n' |
| ) |
| nmatcher = re.compile( |
| '<NAME>' |
| '(?P<object>\S*)' # store object |
| '::' |
| '(?P<signal>\S*)' # store signal |
| '</NAME>' |
| ) |
| rmatcher = re.compile( |
| '(?s)' # make . match \n |
| '<RETURNS>(?P<returns>\S*)</RETURNS>\n' # store returns |
| '(?P<args>.*)' # store args |
| ) |
| for block in smatcher.findall(data): |
| nmatch = nmatcher.search(block) |
| if nmatch: |
| o = nmatch.group('object') |
| debug("Found object", o) |
| debug("Found signal", nmatch.group('signal')) |
| if o not in self._objects: |
| object = Object(o) |
| self._objects[o] = object |
| |
| rmatch = rmatcher.search(block) |
| if rmatch: |
| dict = rmatch.groupdict().copy() |
| dict['name'] = nmatch.group('signal') |
| signal = Signal(**dict) |
| self._objects[o].add_signal(signal) |
| |
| def get_data(self): |
| lines = [] |
| for o in self._objects.values(): |
| for s in o._signals.values(): |
| block = """<SIGNAL> |
| <NAME>%(object)s::%(name)s</NAME> |
| <RETURNS>%(returns)s</RETURNS> |
| %(args)s</SIGNAL> |
| """ |
| d = s.dict.copy() |
| d['object'] = o.name |
| lines.append(block % d) |
| |
| return "\n".join(lines) + '\n' |
| |
| class Args(GDoc): |
| def __init__(self): |
| self._objects = OrderedDict() |
| |
| def load_data(self, data): |
| """ |
| Load the .args lines, creating our list of objects and args. |
| """ |
| import re |
| amatcher = re.compile( |
| '(?s)' # make . match \n |
| '<ARG>\n(.*?)</ARG>\n' |
| ) |
| nmatcher = re.compile( |
| '<NAME>' |
| '(?P<object>\S*)' # store object |
| '::' |
| '(?P<arg>\S*)' # store arg |
| '</NAME>' |
| ) |
| rmatcher = re.compile( |
| '(?s)' # make . match \n |
| '<TYPE>(?P<type>\S*)</TYPE>\n' # store type |
| '<RANGE>(?P<range>.*?)</RANGE>\n' # store range |
| '<FLAGS>(?P<flags>\S*)</FLAGS>\n' # store flags |
| '<NICK>(?P<nick>.*?)</NICK>\n' # store nick |
| '<BLURB>(?P<blurb>.*?)</BLURB>\n' # store blurb |
| '<DEFAULT>(?P<default>.*?)</DEFAULT>\n' # store default |
| ) |
| for block in amatcher.findall(data): |
| nmatch = nmatcher.search(block) |
| if nmatch: |
| o = nmatch.group('object') |
| debug("Found object", o) |
| debug("Found arg", nmatch.group('arg')) |
| if o not in self._objects: |
| object = Object(o) |
| self._objects[o] = object |
| |
| rmatch = rmatcher.search(block) |
| if rmatch: |
| dict = rmatch.groupdict().copy() |
| dict['name'] = nmatch.group('arg') |
| arg = Arg(**dict) |
| self._objects[o].add_arg(arg) |
| else: |
| print ("ERROR: could not match arg from block %s" % block) |
| |
| def get_data(self): |
| lines = [] |
| for o in self._objects.values(): |
| for a in o._args.values(): |
| block = """<ARG> |
| <NAME>%(object)s::%(name)s</NAME> |
| <TYPE>%(type)s</TYPE> |
| <RANGE>%(range)s</RANGE> |
| <FLAGS>%(flags)s</FLAGS> |
| <NICK>%(nick)s</NICK> |
| <BLURB>%(blurb)s</BLURB> |
| <DEFAULT>%(default)s</DEFAULT> |
| </ARG> |
| """ |
| d = a.dict.copy() |
| d['object'] = o.name |
| lines.append(block % d) |
| |
| return "\n".join(lines) + '\n' |
| |
| class SingleLine(GDoc): |
| def __init__(self): |
| self._objects = [] |
| |
| def load_data(self, data): |
| """ |
| Load the .interfaces/.prerequisites lines, merge duplicates |
| """ |
| # split data on '\n' |
| lines = data.splitlines(); |
| # merge them into self._objects |
| for line in lines: |
| if line not in self._objects: |
| self._objects.append(line) |
| |
| def get_data(self): |
| lines = sorted(self._objects) |
| return "\n".join(lines) + '\n' |
| |
| def main(argv): |
| modulename = None |
| try: |
| modulename = argv[1] |
| except IndexError: |
| sys.stderr.write('Please provide a documentation module name\n') |
| sys.exit(1) |
| |
| signals = Signals() |
| signals.load_file(modulename + '.signals') |
| signals.load_file(modulename + '.signals.new') |
| signals.save_file(modulename + '.signals', backup=True) |
| os.unlink(modulename + '.signals.new') |
| |
| args = Args() |
| args.load_file(modulename + '.args') |
| args.load_file(modulename + '.args.new') |
| args.save_file(modulename + '.args', backup=True) |
| os.unlink(modulename + '.args.new') |
| |
| ifaces = SingleLine() |
| ifaces.load_file(modulename + '.interfaces') |
| ifaces.load_file(modulename + '.interfaces.new') |
| ifaces.save_file(modulename + '.interfaces', backup=True) |
| os.unlink(modulename + '.interfaces.new') |
| |
| prereq = SingleLine() |
| prereq.load_file(modulename + '.prerequisites') |
| prereq.load_file(modulename + '.prerequisites.new') |
| prereq.save_file(modulename + '.prerequisites', backup=True) |
| os.unlink(modulename + '.prerequisites.new') |
| |
| main(sys.argv) |