blob: 46736b4307ee6ed505313746a72c63f76baab7cc [file] [log] [blame]
#!/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)