1#!/usr/bin/python 2# -*- Mode: Python -*- 3# vi:si:et:sw=4:sts=4:ts=4 4 5""" 6parse, merge and write gstdoc-scanobj files 7""" 8 9from __future__ import print_function, unicode_literals 10 11import codecs 12import os 13import sys 14 15def debug(*args): 16 pass 17 18# OrderedDict class based on 19# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747 20# Licensed under the Python License 21class OrderedDict(dict): 22 def __init__(self): 23 self._keys = [] 24 dict.__init__(self) 25 26 def __delitem__(self, key): 27 dict.__delitem__(self, key) 28 self._keys.remove(key) 29 30 def __setitem__(self, key, item): 31 dict.__setitem__(self, key, item) 32 if key not in self._keys: self._keys.append(key) 33 34 def clear(self): 35 dict.clear(self) 36 self._keys = [] 37 38 def copy(self): 39 dict = dict.copy(self) 40 dict._keys = self._keys[:] 41 return dict 42 43 def items(self): 44 return zip(self._keys, self.values()) 45 46 def keys(self): 47 return self._keys 48 49 def popitem(self): 50 try: 51 key = self._keys[-1] 52 except IndexError: 53 raise KeyError('dictionary is empty') 54 55 val = self[key] 56 del self[key] 57 58 return (key, val) 59 60 def setdefault(self, key, failobj = None): 61 dict.setdefault(self, key, failobj) 62 if key not in self._keys: self._keys.append(key) 63 64 def update(self, dict): 65 dict.update(self, dict) 66 for key in dict.keys(): 67 if key not in self._keys: self._keys.append(key) 68 69 def values(self): 70 return map(self.get, self._keys) 71 72class Object: 73 def __init__(self, name): 74 self._signals = OrderedDict() 75 self._args = OrderedDict() 76 self.name = name 77 78 def __repr__(self): 79 return "<Object %s>" % self.name 80 81 def add_signal(self, signal, overwrite=True): 82 if not overwrite and signal.name in self._signals: 83 raise IndexError("signal %s already in %r" % (signal.name, self)) 84 self._signals[signal.name] = signal 85 86 def add_arg(self, arg, overwrite=True): 87 if not overwrite and arg.name in self._args: 88 raise IndexError("arg %s already in %r" % (arg.name, self)) 89 self._args[arg.name] = arg 90 91class Docable: 92 def __init__(self, **kwargs): 93 for key in self.attrs: 94 setattr(self, key, kwargs[key]) 95 self.dict = kwargs 96 97 def __repr__(self): 98 return "<%r %s>" % (str(self.__class__), self.name) 99 100class Signal(Docable): 101 attrs = ['name', 'returns', 'args'] 102 103class Arg(Docable): 104 attrs = ['name', 'type', 'range', 'flags', 'nick', 'blurb', 'default'] 105 106class GDoc: 107 def load_file(self, filename): 108 try: 109 lines = codecs.open(filename, encoding='utf-8').readlines() 110 self.load_data("".join(lines)) 111 except IOError: 112 print ("WARNING - could not read from %s" % filename) 113 except UnicodeDecodeError as e: 114 print ("WARNING - could not parse %s: %s" % (filename, e)) 115 116 def save_file(self, filename, backup=False): 117 """ 118 Save the information to the given file if the file content changed. 119 """ 120 olddata = None 121 try: 122 lines = codecs.open(filename, encoding='utf-8').readlines() 123 olddata = "".join(lines) 124 except IOError: 125 print ("WARNING - could not read from %s" % filename) 126 newdata = self.get_data() 127 if olddata and olddata == newdata: 128 return 129 130 if olddata: 131 if backup: 132 os.rename(filename, filename + '.bak') 133 134 handle = codecs.open(filename, "w", encoding='utf-8') 135 handle.write(newdata) 136 handle.close() 137 138class Signals(GDoc): 139 def __init__(self): 140 self._objects = OrderedDict() 141 142 def load_data(self, data): 143 """ 144 Load the .signals lines, creating our list of objects and signals. 145 """ 146 import re 147 smatcher = re.compile( 148 '(?s)' # make . match \n 149 '<SIGNAL>\n(.*?)</SIGNAL>\n' 150 ) 151 nmatcher = re.compile( 152 '<NAME>' 153 '(?P<object>\S*)' # store object 154 '::' 155 '(?P<signal>\S*)' # store signal 156 '</NAME>' 157 ) 158 rmatcher = re.compile( 159 '(?s)' # make . match \n 160 '<RETURNS>(?P<returns>\S*)</RETURNS>\n' # store returns 161 '(?P<args>.*)' # store args 162 ) 163 for block in smatcher.findall(data): 164 nmatch = nmatcher.search(block) 165 if nmatch: 166 o = nmatch.group('object') 167 debug("Found object", o) 168 debug("Found signal", nmatch.group('signal')) 169 if o not in self._objects: 170 object = Object(o) 171 self._objects[o] = object 172 173 rmatch = rmatcher.search(block) 174 if rmatch: 175 dict = rmatch.groupdict().copy() 176 dict['name'] = nmatch.group('signal') 177 signal = Signal(**dict) 178 self._objects[o].add_signal(signal) 179 180 def get_data(self): 181 lines = [] 182 for o in self._objects.values(): 183 for s in o._signals.values(): 184 block = """<SIGNAL> 185<NAME>%(object)s::%(name)s</NAME> 186<RETURNS>%(returns)s</RETURNS> 187%(args)s</SIGNAL> 188""" 189 d = s.dict.copy() 190 d['object'] = o.name 191 lines.append(block % d) 192 193 return "\n".join(lines) + '\n' 194 195class Args(GDoc): 196 def __init__(self): 197 self._objects = OrderedDict() 198 199 def load_data(self, data): 200 """ 201 Load the .args lines, creating our list of objects and args. 202 """ 203 import re 204 amatcher = re.compile( 205 '(?s)' # make . match \n 206 '<ARG>\n(.*?)</ARG>\n' 207 ) 208 nmatcher = re.compile( 209 '<NAME>' 210 '(?P<object>\S*)' # store object 211 '::' 212 '(?P<arg>\S*)' # store arg 213 '</NAME>' 214 ) 215 rmatcher = re.compile( 216 '(?s)' # make . match \n 217 '<TYPE>(?P<type>\S*)</TYPE>\n' # store type 218 '<RANGE>(?P<range>.*?)</RANGE>\n' # store range 219 '<FLAGS>(?P<flags>\S*)</FLAGS>\n' # store flags 220 '<NICK>(?P<nick>.*?)</NICK>\n' # store nick 221 '<BLURB>(?P<blurb>.*?)</BLURB>\n' # store blurb 222 '<DEFAULT>(?P<default>.*?)</DEFAULT>\n' # store default 223 ) 224 for block in amatcher.findall(data): 225 nmatch = nmatcher.search(block) 226 if nmatch: 227 o = nmatch.group('object') 228 debug("Found object", o) 229 debug("Found arg", nmatch.group('arg')) 230 if o not in self._objects: 231 object = Object(o) 232 self._objects[o] = object 233 234 rmatch = rmatcher.search(block) 235 if rmatch: 236 dict = rmatch.groupdict().copy() 237 dict['name'] = nmatch.group('arg') 238 arg = Arg(**dict) 239 self._objects[o].add_arg(arg) 240 else: 241 print ("ERROR: could not match arg from block %s" % block) 242 243 def get_data(self): 244 lines = [] 245 for o in self._objects.values(): 246 for a in o._args.values(): 247 block = """<ARG> 248<NAME>%(object)s::%(name)s</NAME> 249<TYPE>%(type)s</TYPE> 250<RANGE>%(range)s</RANGE> 251<FLAGS>%(flags)s</FLAGS> 252<NICK>%(nick)s</NICK> 253<BLURB>%(blurb)s</BLURB> 254<DEFAULT>%(default)s</DEFAULT> 255</ARG> 256""" 257 d = a.dict.copy() 258 d['object'] = o.name 259 lines.append(block % d) 260 261 return "\n".join(lines) + '\n' 262 263class SingleLine(GDoc): 264 def __init__(self): 265 self._objects = [] 266 267 def load_data(self, data): 268 """ 269 Load the .interfaces/.prerequisites lines, merge duplicates 270 """ 271 # split data on '\n' 272 lines = data.splitlines(); 273 # merge them into self._objects 274 for line in lines: 275 if line not in self._objects: 276 self._objects.append(line) 277 278 def get_data(self): 279 lines = sorted(self._objects) 280 return "\n".join(lines) + '\n' 281 282def main(argv): 283 modulename = None 284 try: 285 modulename = argv[1] 286 except IndexError: 287 sys.stderr.write('Please provide a documentation module name\n') 288 sys.exit(1) 289 290 signals = Signals() 291 signals.load_file(modulename + '.signals') 292 signals.load_file(modulename + '.signals.new') 293 signals.save_file(modulename + '.signals', backup=True) 294 os.unlink(modulename + '.signals.new') 295 296 args = Args() 297 args.load_file(modulename + '.args') 298 args.load_file(modulename + '.args.new') 299 args.save_file(modulename + '.args', backup=True) 300 os.unlink(modulename + '.args.new') 301 302 ifaces = SingleLine() 303 ifaces.load_file(modulename + '.interfaces') 304 ifaces.load_file(modulename + '.interfaces.new') 305 ifaces.save_file(modulename + '.interfaces', backup=True) 306 os.unlink(modulename + '.interfaces.new') 307 308 prereq = SingleLine() 309 prereq.load_file(modulename + '.prerequisites') 310 prereq.load_file(modulename + '.prerequisites.new') 311 prereq.save_file(modulename + '.prerequisites', backup=True) 312 os.unlink(modulename + '.prerequisites.new') 313 314main(sys.argv) 315