Package ais :: Module aisxmlbinmsg2py
[hide private]
[frames] | no frames]

Source Code for Module ais.aisxmlbinmsg2py

   1  #!/usr/bin/env python 
   2   
   3  __version__ = '$Revision: 4791 $'.split()[1] 
   4  __date__ = '$Date: 2006-09-24 14:01:41 -0400 (Sun, 24 Sep 2006) $'.split()[1] 
   5  __author__ = 'Kurt Schwehr' 
   6   
   7  __doc__=''' 
   8   
   9  Tools to generate python code to serialize/deserialize messages 
  10  between python and ais binary.  Trying to be as inline as possible, so 
  11  no XML on the fly like in ais-py.   
  12   
  13  serialize: python to ais binary 
  14  deserialize: ais binary to python 
  15   
  16  The generated code uses translators.py, binary.py, and aisstring.py 
  17  which should be packaged with the resulting files. 
  18   
  19  @requires: U{lxml<http://codespeak.net/lxml/>} 
  20  @requires: U{epydoc<http://epydoc.sourceforge.net/>} >= 3.0alpha3 
  21  @requires: U{BitVector<http://cheeseshop.python.org/pypi/BitVector>} >= 1.2 
  22   
  23  @author: U{'''+__author__+'''<http://schwehr.org/>} 
  24  @version: ''' + __version__ +''' 
  25  @copyright: 2006 
  26  @var __date__: Date of last svn commit 
  27  @undocumented: __version__ __author__ __doc__ parser 
  28  @since: 2006-Sep-24 
  29  @status: under development 
  30  @organization: U{CCOM<http://ccom.unh.edu/>} 
  31  @license: GPL v2 
  32   
  33  @todo: add a link to generated doc string to bring up the html for the pretty version 
  34  @todo: write a separate validation script that distinguishes standard messages and bin messages 
  35  @todo: make sure binary is only used in AIS ITU messages and not within the binary messages! 
  36  @todo: arrays 
  37  @bug: NOT complete 
  38  ''' 
  39   
  40  import sys, os 
  41  from decimal import Decimal 
  42  from lxml import etree  
  43   
44 -def suggestType(name,curType,printout=True):
45 ''' 46 Try to suggest a type name if one did not work. 47 48 @param printout: if true, write a suggestion to stdout. 49 50 >>> suggestType('myFieldName','unsigned int') 51 Recommend switching "unsigned int" to "uint" for field "myFieldName" 52 'uint' 53 54 >>> suggestType('JohnWarfon','yoyodyne') 55 Sorry! No recommendation available for bad type "yoyodyne" for field "JohnWarfon" 56 ''' 57 newType = None 58 if curType.lower()=='unsigned int': 59 newType = 'uint' 60 elif curType.lower()=='unsigned decimal': 61 newType = 'udecimal' 62 63 if printout: 64 if None != newType: 65 print 'Recommend switching "'+curType+'" to "'+newType+'" for field "'+name+'"' 66 else: 67 print 'Sorry! No recommendation available for bad type "'+curType+'" for field "'+name+'"' 68 return newType
69 70
71 -def hasSubTag(et,subtag):
72 ''' 73 @return: true if the tag a sub tag with name subtag 74 ''' 75 if 0<len(et.xpath(subtag)): return True 76 return False
77 78 79 #def writeBeginning(o,aisBinMsgET):
80 -def writeBeginning(o):
81 ''' 82 Write the doc string header for the message file 83 84 @param o: Open output file to write code to. 85 @param msgET: element tree for the ais message definition. 86 Must be pre-expanded with the expandais.py command. 87 ''' 88 import datetime 89 d = datetime.datetime.utcnow() 90 dateStr = str(d.year)+'-'+("%02d" %d.month)+'-'+("%02d"%d.day) 91 92 # FIX: what to do for __version__, @since, etc? 93 # Need to pass in info about the source file, etc. 94 95 # Minor trickery to get svn to ignore the keywords in the next few lines 96 o.write('''#!/usr/bin/env python 97 98 __version__ = '$Revision: 4791 $'.split()[1] 99 __date__ = '$Da'''+'''te: '''+dateStr+''' $'.split()[1] 100 __author__ = 'xmlbinmsg' 101 102 __doc__=\'\'\' 103 104 Autogenerated python functions to serialize/deserialize binary messages. 105 106 Generated by: '''+__file__+''' 107 108 Need to then wrap these functions with the outer AIS packet and then 109 convert the whole binary blob to a NMEA string. Those functions are 110 not currently provided in this file. 111 112 serialize: python to ais binary 113 deserialize: ais binary to python 114 115 The generated code uses translators.py, binary.py, and aisstring.py 116 which should be packaged with the resulting files. 117 118 ''') 119 120 o.write(''' 121 @requires: U{epydoc<http://epydoc.sourceforge.net/>} > 3.0alpha3 122 @requires: U{BitVector<http://cheeseshop.python.org/pypi/BitVector>} 123 124 @author: \'\'\'+__author__+\'\'\' 125 @version: \'\'\' + __version__ +\'\'\' 126 @var __date__: Date of last svn commit 127 @undocumented: __version__ __author__ __doc__ parser 128 @status: under development 129 @license: Generated code has no license 130 @todo: FIX: put in a description of the message here with fields and types. 131 \'\'\' 132 133 import sys 134 from decimal import Decimal 135 from BitVector import BitVector 136 137 import binary, aisstring 138 139 # FIX: check to see if these will be needed 140 TrueBV = BitVector(bitstring="1") 141 "Why always rebuild the True bit? This should speed things up a bunch" 142 FalseBV = BitVector(bitstring="0") 143 "Why always rebuild the False bit? This should speed things up a bunch" 144 145 146 ''') 147 return
148
149 -def generatePython(infile,outfile, prefixName=False,verbose=False):
150 ''' 151 @param infile: xml ais binary message definition file 152 @param outfile: where to dump the python code 153 ''' 154 155 aisMsgsET = etree.parse(infile).getroot() 156 157 o = file(outfile,'w') 158 os.chmod(outfile,0755) 159 160 writeBeginning(o) 161 162 for msgET in aisMsgsET: 163 if msgET.tag != 'message': continue 164 print msgET.tag, msgET.attrib['name'] 165 166 if len(msgET.xpath('include-struct')) > 0: 167 sys.exit("ERROR: cannot handle xml that still has include-struct tags.\n Please use expandais.py.") 168 buildHelpers(o,msgET,prefixName=prefixName,verbose=verbose) 169 buildEncode(o,msgET,prefixName=prefixName,verbose=verbose) 170 buildDecode(o,msgET,prefixName=prefixName) 171 buildDecodeParts(o,msgET,prefixName=prefixName) # functions that only decode one field 172 buildPrint(o,msgET,prefixName=prefixName) 173 buildLUT(o,msgET,prefixName=prefixName) 174 buildSQL(o,msgET,prefixName=prefixName) 175 176 o.write('\n\n######################################################################\n') 177 o.write('# UNIT TESTING\n') 178 o.write('######################################################################\n') 179 o.write('import unittest\n') 180 181 for msgET in aisMsgsET: 182 if msgET.tag != 'message': continue 183 print 'Building unit tests for message ...', msgET.attrib['name'] 184 185 buildUnitTest(o,msgET, prefixName=prefixName) 186 187 for msgET in aisMsgsET: 188 if msgET.tag != 'message': continue 189 buildMain(o,msgET, prefixName=prefixName) 190 191 return
192 193 ###################################################################### 194 # Build Helpers 195 ###################################################################### 196
197 -def buildHelpers(o,msgET, verbose=False, prefixName=False):
198 ''' 199 emit the fieldList and other things??? 200 201 @param o: open file where resulting code will be written 202 @param msgET: Element Tree starting at a message node 203 204 @todo: for lookuptable/entry values, make it also print the decoded value. 205 @todo: use a different name for message and field 206 @todo: put in comments with the python and sql data type for each field 207 ''' 208 209 if verbose: 210 msgname = msgET.attrib['name'] 211 print 'Building helpers for',msgname 212 213 if prefixName: 214 o.write(prefixName,'FieldList = (\n') 215 else: 216 o.write('fieldList = (\n') 217 218 for field in msgET.xpath('field'): 219 name = field.attrib['name'] 220 o.write('\t\''+name+'\',\n') 221 222 o.write(')\n\n') 223 224 # FIX: add some documentation to the fieldList 225 # FIX: like that it is used as the default csv list 226 227 if prefixName: 228 o.write(prefixName,'FieldListPostgres = (\n') 229 else: 230 o.write('fieldListPostgres = (\n') 231 finished=[] # postgis names 232 for field in msgET.xpath('field'): 233 name = field.attrib['name'] 234 if 'postgisName' in field.attrib: 235 name = field.attrib['postgisName'] 236 if name in finished: continue # already done, so skip 237 finished.append(name) 238 o.write('\t\''+name+'\',\t# PostGIS data type\n') 239 else: 240 o.write('\t\''+name+'\',\n') 241 242 o.write(')\n\n') 243 244 del finished 245 246 247 248 if prefixName: 249 o.write(prefixName,'ToPgFields = {\n') 250 else: 251 o.write('toPgFields = {\n') 252 for field in msgET.xpath('field'): 253 if 'postgisName' not in field.attrib: continue 254 name = field.attrib['name'] 255 pgName = field.attrib['postgisName'] 256 o.write('\t\''+name+'\':\''+pgName+'\',\n') 257 o.write('}\n') 258 o.write('''\'\'\' 259 Go to the Postgis field names from the straight field name 260 \'\'\' 261 262 ''') 263 264 265 if prefixName: 266 o.write(prefixName,'FromPgFields = {\n') 267 else: 268 o.write('fromPgFields = {\n') 269 finished=[] # postgis names 270 for field in msgET.xpath('field'): 271 if 'postgisName' not in field.attrib: continue 272 name = field.attrib['name'] 273 pgName = field.attrib['postgisName'] 274 275 if pgName in finished: continue # already done, so skip 276 finished.append(pgName) 277 o.write('\t\''+pgName+'\':(') 278 xp = 'field[@postgisName=\''+pgName+'\']' 279 parts = ['\''+part.attrib['name']+'\'' for part in msgET.xpath(xp)] 280 o.write(','.join(parts)+',),\n') 281 o.write('}\n') 282 o.write('''\'\'\' 283 Go from the Postgis field names to the straight field name 284 \'\'\' 285 286 ''') 287 288 289 if prefixName: 290 o.write(prefixName,'PgTypes = {\n') 291 else: 292 o.write('pgTypes = {\n') 293 finished=[] # postgis names 294 for field in msgET.xpath('field'): 295 if 'postgisName' not in field.attrib: continue 296 pgType = field.attrib['postgisType'] 297 pgName = field.attrib['postgisName'] 298 if pgName in finished: continue # already done, so skip 299 finished.append(pgName) 300 o.write('\t\''+pgName+'\':\''+pgType+'\',\n') 301 o.write('}\n') 302 o.write('''\'\'\' 303 Lookup table for each postgis field name to get its type. 304 \'\'\' 305 306 ''')
307 308 309 310 311 312 313 314 ###################################################################### 315 # SIMPLE PRINT 316 ###################################################################### 317
318 -def getMaxFieldNameLen(msgET):
319 '''Get the maximum string length of any field name''' 320 maxStrLen=0 321 for field in msgET.xpath('field'): 322 fieldLen = len(field.attrib['name']) 323 if fieldLen>maxStrLen: maxStrLen = fieldLen 324 return maxStrLen
325
326 -def padStrRight(aStr,strlen):
327 '''Pad a string out to the length requested with spaces out to the right''' 328 return aStr + ' '*(strlen-len(aStr))
329
330 -def haveLocatableMessage(msgET):
331 '''Make sure this message has both long/x and lat/y fields. 332 @rtype: bool 333 ''' 334 #if getLongitudeFieldName(msgET) and getLatitudeFieldName(msgET): return True 335 336 if 0==len(msgET.xpath('field[contains(@name, "longitude")]')): return False 337 if 0==len(msgET.xpath('field[contains(@name, "latitude")]')): return False 338 return True
339
340 -def getLongitudeFieldName(msgET):
341 ''' 342 Dig up the first field name that include longitude and return it 343 @todo: might want to allow a search for a special tag to mark this 344 ''' 345 return msgET.xpath('field[contains(@name, "longitude")]')[0].attrib['name']
346
347 -def getLatitudeFieldName(msgET):
348 ''' 349 Dig up the first field name that include longitude and return it 350 @todo: might want to allow a search for a special tag to mark this 351 ''' 352 return msgET.xpath('field[contains(@name, "latitude")]')[0].attrib['name']
353
354 -def buildPrint(o,msgET, verbose=False, prefixName=False):
355 ''' 356 Write a simple in order print for the resulting dictionary. 357 358 @param o: open file where resulting code will be written 359 @param msgET: Element Tree starting at a message node 360 361 @todo: for lookuptable/entry values, make it also print the decoded value. 362 @todo: use a different name for message and field 363 ''' 364 assert(msgET.tag=='message') 365 msgName = msgET.attrib['name'] 366 #msgName = msgname = name # FIX: make these all msgName 367 368 print 'Generating print for',msgName # FIX: verbose? 369 # +1 for the ':' 370 maxFieldLen = 1 + getMaxFieldNameLen(msgET) 371 372 373 ############################## 374 # Html builder 375 ############################## 376 377 printHtmlName = 'printHtml' 378 if prefixName: printHtmlName = name+'printHtml' 379 380 #################### 381 ####### HTML format 382 #################### 383 #o.write('\telif \'html\'==format:\n') 384 o.write('\n') 385 o.write('def '+printHtmlName+'(params, out=sys.stdout):\n') 386 o.write('\t\tout.write("<h3>'+msgName+'<h3>\\n")\n') 387 o.write('\t\tout.write("<table border=\\"1\\">\\n")\n') 388 #o.write('\t\tout.write("<tr bgcolor=\\"#9acd32\\">\\n")\n') 389 o.write('\t\tout.write("<tr bgcolor=\\"orange\\">\\n")\n') 390 o.write('\t\tout.write("<th align=\\"left\\">Field Name</th>\\n")\n') 391 o.write('\t\tout.write("<th align=\\"left\\">Type</th>\\n")\n') 392 o.write('\t\tout.write("<th align=\\"left\\">Value</th>\\n")\n') 393 o.write('\t\tout.write("<th align=\\"left\\">Value in Lookup Table</th>\\n")\n') 394 o.write('\t\tout.write("<th align=\\"left\\">Units</th>\\n")\n') 395 396 397 for field in msgET.xpath('field'): 398 o.write('\t\tout.write("\\n")\n') 399 o.write('\t\tout.write("<tr>\\n")\n') 400 fieldname = field.attrib['name'] 401 fieldtype = field.attrib['type'] 402 o.write('\t\tout.write("<td>'+fieldname+'</td>\\n")\n') 403 o.write('\t\tout.write("<td>'+fieldtype+'</td>\\n")\n') 404 405 numbits = int(field.attrib['numberofbits']) 406 required = None; 407 if hasSubTag(field,'required'): 408 required = field.xpath('required')[0].text 409 unavailable=None; 410 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 411 arraylen=1 412 if 'arraylength' in field.attrib: arraylen=int(field.attrib['arraylength']) 413 414 if 1==arraylen or fieldtype=='aisstr6': 415 o.write('\t\tif \''+fieldname+'\' in params:\n\t\t\tout.write("\t<td>"+str(params[\''+fieldname+'\'])+"</td>\\n")\n') 416 if not hasSubTag(field,'lookuptable'): 417 # Just duplicate the value through 418 o.write('\t\t\tout.write("\t<td>"+str(params[\''+fieldname+'\'])+"</td>\\n")\n') 419 else: 420 lutName = fieldname+'DecodeLut' 421 if prefixName: lutName = msgname+fieldname.capitalize()+'DecodeLut' 422 423 o.write('\t\t\tif str(params[\''+fieldname+'\']) in '+lutName+':\n') 424 o.write('\t\t\t\tout.write("<td>"+'+lutName+'[str(params[\''+fieldname+'\'])]+"</td>")\n') 425 o.write('\t\t\telse:\n') 426 o.write('\t\t\t\tout.write("<td><i>Missing LUT entry</i></td>")\n') 427 else: sys.exit ('FIX: handle arrays in the buildPrint func') 428 429 if hasSubTag(field,'units'): 430 o.write('\t\tout.write("<td>'+field.xpath('units')[0].text+'</td>\\n")\n') 431 432 o.write('\t\tout.write("</tr>\\n")\n') 433 434 o.write('\t\tout.write("</table>\\n")\n') 435 o.write('\n') 436 437 ############################## 438 ####### KML GoogleEarth 439 ############################## 440 441 printKmlName = 'printKml' 442 if prefixName: printKmlName = msgName+'printKml' 443 if not haveLocatableMessage(msgET): printKmlName=None 444 else: 445 o.write('\n') 446 o.write('def printKml(params, out=sys.stdout):\n') 447 o.write('\t\'\'\'KML (Keyhole Markup Language) for Google Earth, but without the header/footer\'\'\'\n') 448 o.write('\tout.write("\\\t<Placemark>\\n")\n') 449 o.write('\tout.write("\\t\t<name>"+str(params[\''+msgET.attrib['titlefield']+'\'])+"</name>\\n")\n') 450 o.write('\tout.write("\\t\\t<description>\\n")\n') 451 452 o.write('\timport StringIO\n') 453 o.write('\tbuf = StringIO.StringIO()\n') 454 o.write('\tprintHtml(params,buf)\n') 455 o.write('\timport cgi\n') 456 o.write('\tout.write(cgi.escape(buf.getvalue()))\n') 457 458 o.write('\tout.write("\\t\\t</description>\\n")\n') 459 o.write('\tout.write("\\t\\t<styleUrl>#m_ylw-pushpin_copy0</styleUrl>\\n")\n') 460 o.write('\tout.write("\\t\\t<Point>\\n")\n') 461 o.write('\tout.write("\\t\\t\\t<coordinates>")\n') 462 o.write('\tout.write(str(params[\''+getLongitudeFieldName(msgET)+'\']))\n') 463 o.write('\tout.write(\',\')\n') 464 o.write('\tout.write(str(params[\''+getLatitudeFieldName(msgET)+'\']))\n') 465 o.write('\tout.write(",0</coordinates>\\n")\n') 466 o.write('\tout.write("\\t\\t</Point>\\n")\n') 467 o.write('\tout.write("\\t</Placemark>\\n")\n') 468 o.write('\n') 469 470 471 ############################## 472 ##### Main print dispatch 473 ############################## 474 475 funcName = 'printFields' 476 if prefixName: funcName = msgName+'PrintFields' 477 478 o.write('def '+funcName+'(params, out=sys.stdout, format=\'std\', fieldList=None, dbType=\'postgres\'):\n') 479 480 ######################################## 481 # doc string 482 o.write("\t'''Print a "+msgName+" message to stdout.\n\n") 483 o.write('\tFields in params:\n') 484 for field in msgET.xpath('field'): 485 desc = field[0].text.replace('\n',' ') # get ride of new lines 486 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 487 if len(field.xpath("required")) == 1: 488 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 489 490 o.write('\n') 491 o.write('\t@param params: Dictionary of field names/values. \n') 492 o.write('\t@param out: File like object to write to\n') 493 494 o.write("\t@rtype: stdout\n") 495 o.write("\t@return: text to out\n") 496 o.write("\t'''\n\n") 497 498 ######################################## 499 # Actually build the code 500 501 o.write('\tif \'std\'==format:\n') 502 o.write('\t\tout.write("'+msgName+':\\n")\n') 503 504 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 505 506 507 for field in msgET.xpath('field'): 508 fieldname = field.attrib['name'] 509 fieldtype = field.attrib['type'] 510 numbits = int(field.attrib['numberofbits']) 511 required = None; 512 if hasSubTag(field,'required'): 513 required = field.xpath('required')[0].text 514 unavailable=None; 515 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 516 arraylen=1 517 if 'arraylength' in field.attrib: 518 arraylen=int(field.attrib['arraylength']) 519 if verbose: print 'Processing field ...',fieldname,'('+fieldtype+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 520 else: 521 if verbose: print 'Processing field ...',fieldname,'(',fieldtype+' ',numbits,')' 522 523 if 1==arraylen or fieldtype=='aisstr6': 524 o.write('\t\tif \''+fieldname+'\' in params: out.write("\t'+padStrRight(fieldname+':',maxFieldLen)+' "+str(params[\''+fieldname+'\'])+"\\n")\n') 525 # FIX: elif aisstr6 strip @@@ and then print 526 else: 527 print '\n','ERROR:',fieldname,fieldtype 528 sys.exit ('ERROR: FIX: handle arrays in the buildPrint func') 529 530 #################### 531 ####### Comma separated values (csv) 532 #################### 533 o.write(''' elif 'csv'==format: 534 if None == options.fieldList: 535 options.fieldList = fieldList 536 needComma = False; 537 for field in fieldList: 538 if needComma: out.write(',') 539 needComma = True 540 if field in params: 541 out.write(str(params[field])) 542 # else: leave it empty 543 out.write("\\n") 544 ''') 545 546 #################### 547 ####### Other dispatchers 548 #################### 549 o.write('\telif \'html\'==format:\n') 550 o.write('\t\t'+printHtmlName+'(params,out)\n') 551 552 o.write(''' elif 'sql'==format: 553 sqlInsertStr(params,out,dbType=dbType) 554 ''') 555 556 if haveLocatableMessage(msgET): 557 # Has a lon/lat pair somewhere in there. 558 o.write('\telif \'kml\'==format:\n') 559 o.write('\t\t'+printKmlName+'(params,out)\n') 560 561 o.write('\telif \'kml-full\'==format:\n') 562 563 o.write('\t\tout.write(\"<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>\\n")\n') 564 o.write('\t\tout.write("<kml xmlns=\\"http://earth.google.com/kml/2.1\\">\\n")\n') 565 o.write('\t\tout.write("<Document>\\n")\n') 566 # o.write('\t\tout.write("\t<name>Position</name>\\n")\n') 567 o.write('\t\tout.write("\t<name>'+msgName+'</name>\\n")\n') 568 569 o.write('\t\t'+printKmlName+'(params,out)\n') 570 571 o.write('\t\tout.write("</Document>\\n")\n') 572 o.write('\t\tout.write("</kml>\\n")\n') 573 574 575 576 #################### 577 ####### Make safety check 578 #################### 579 o.write('\telse: \n') 580 o.write('\t\tprint "ERROR: unknown format:",format\n') 581 o.write('\t\tassert False\n') 582 583 o.write('\n\treturn # Nothing to return\n\n')
584 585 586 ###################################################################### 587 # Build Lookup tables for enumerated int or uint fields 588 ###################################################################### 589 590
591 -def buildSQL(o,msgET, verbose=False, prefixName=False):
592 ''' 593 Write SQL code 594 595 @param o: open file where resulting code will be written 596 @param msgET: Element Tree starting at a message node 597 @param verbose: talk lots in the process 598 @param prefixName: set to a string to have the commands prefixed by that character. 599 ''' 600 assert(msgET.tag=='message') 601 msgName = msgET.attrib['name'] 602 print 'Generating SQL commands for', msgName 603 604 createFuncName = 'sqlCreate' 605 if prefixName: createFuncName = msgName+'SqlCreate' 606 insertFuncName = 'sqlInsert' 607 if prefixName: insertFuncName = msgName+'SqlInsert' 608 609 o.write('''###################################################################### 610 # SQL SUPPORT 611 ###################################################################### 612 613 ''') 614 615 # Should this really default to true for the uscg fields? 616 o.write('def '+createFuncName+'''Str(outfile=sys.stdout, fields=None, extraFields=None 617 ,addCoastGuardFields=True 618 ,dbType='postgres' 619 ): 620 \'\'\' 621 Return the SQL CREATE command for this message type 622 @param outfile: file like object to print to. 623 @param fields: which fields to put in the create. Defaults to all. 624 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 625 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format 626 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres') 627 @type addCoastGuardFields: bool 628 @return: sql create string 629 @rtype: str 630 631 @see: sqlCreate 632 \'\'\' 633 outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields,dbType=dbType))) 634 635 ''') 636 637 o.write('def '+createFuncName+'''(fields=None, extraFields=None, addCoastGuardFields=True, dbType='postgres'): 638 \'\'\' 639 Return the sqlhelp object to create the table. 640 641 @param fields: which fields to put in the create. Defaults to all. 642 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 643 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format 644 @type addCoastGuardFields: bool 645 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres') 646 @return: An object that can be used to generate a return 647 @rtype: sqlhelp.create 648 \'\'\' 649 if None == fields: fields = fieldList 650 import sqlhelp 651 ''') 652 653 o.write('\tc = sqlhelp.create(\''+msgName+'\',dbType=dbType)\n') 654 o.write('\tc.addPrimaryKey()\n'); 655 656 for field in msgET.xpath('field'): 657 fieldName = field.attrib['name'] 658 fieldType = field.attrib['type'] 659 660 postgis = False 661 if 'postgisType' in field.attrib: 662 postgis=True 663 o.write('\tif dbType != \'postgres\':\n\t') 664 o.write('\tif \''+fieldName+'\' in fields: c.add') 665 # FIX: add the ADD command here. 666 if fieldType in ('int','uint'): o.write('Int (\''+fieldName+'\')') 667 elif fieldType in ('float' ): o.write('Real(\''+fieldName+'\')') 668 elif fieldType == 'bool': o.write('Bool(\''+fieldName+'\')') 669 elif fieldType in ('decimal','udecimal'): 670 if not hasSubTag(field,'decimalplaces'): 671 print '\n ** ERROR: missing decimalplaces field for ',fieldName,'\n' 672 assert (False) 673 scaleSQL = int(field.xpath('decimalplaces')[0].text) # number of decimal places 674 numBits = int(field.attrib['numberofbits']) 675 if not hasSubTag(field,'scale'): 676 print 'ERROR: missing required <scale> for',fieldName,'of type',fieldType 677 scaleVal = float(field.xpath('scale')[0].text) 678 precision = scaleSQL + len(str(int((2**numBits)/scaleVal))) 679 #precision += 1 # FIX: do I really need this safety factor 680 o.write('Decimal(\''+fieldName+'\','+str(precision)+','+str(scaleSQL)+')') 681 elif fieldType == 'binary': 682 numBits = int(field.attrib['numberofbits']) 683 if -1 == numBits: numBits=1024 # FIX: what is a good maximum for AIS? 684 o.write('BitVarying(\''+fieldName+'\','+str(numBits)+')') 685 elif fieldType == 'aisstr6': 686 arrayLength = int(field.attrib['arraylength']) 687 o.write('VarChar(\''+fieldName+'\','+str(arrayLength)+')') 688 else: 689 print '\n\n *** Unknown type in SQL code generation for field',fieldName+':',fieldType,'\n\n' 690 assert(False) 691 o.write('\n') 692 693 o.write(''' 694 if addCoastGuardFields: 695 # c.addInt('cg_rssi') # Relative signal strength indicator 696 # c.addInt('cg_d') # dBm receive strength 697 # c.addInt('cg_T') # Receive timestamp from the AIS equipment 698 # c.addInt('cg_S') # Slot received in 699 # c.addVarChar('cg_x',10) # Idonno 700 c.addVarChar('cg_r',15) # Receiver station ID - should usually be an MMSI, but sometimes is a string 701 c.addInt('cg_sec') # UTC seconds since the epoch 702 703 c.addTimestamp('cg_timestamp') # UTC decoded cg_sec - not actually in the data stream 704 ''') 705 706 707 # 708 # Postgres with PostGIS added 709 # 710 postgisFields = msgET.xpath('field[@postgisType]') 711 if len(postgisFields)<1: 712 if verbose: print 'No postgis fields' 713 else: 714 o.write('\n\tif dbType == \'postgres\':\n') 715 finished=[] # Should be a set? 716 print 'processing postgis fields for create:' 717 for field in postgisFields: 718 #print ' ',field.attrib['name'],field.attrib['postgisName'] 719 pgName = field.attrib['postgisName'] 720 if pgName in finished: 721 print 'already handled',pgName 722 continue 723 finished.append(pgName) 724 pgType = field.attrib['postgisType'] 725 #print ' doing',pgName,pgType 726 components = msgET.xpath('field[@postgisName=\''+pgName+'\']') 727 #print ' all components' 728 #for c in components: 729 # print ' ',c.attrib['postgisName'] 730 o.write('\t\tc.addPostGIS(\''+pgName+'\',\''+pgType+'\','+str(len(components))+');\n') 731 732 o.write('\n\treturn c\n\n') 733 734 735 # FIX: function name 736 o.write('''def '''+insertFuncName+'''Str(params, outfile=sys.stdout, extraParams=None, dbType='postgres'): 737 \'\'\' 738 Return the SQL INSERT command for this message type 739 @param params: dictionary of values keyed by field name 740 @param outfile: file like object to print to. 741 @param extraParams: A sequence of tuples containing (name,sql type) for additional fields 742 @return: sql create string 743 @rtype: str 744 745 @see: sqlCreate 746 \'\'\' 747 outfile.write(str(sqlInsert(params,extraParams,dbType=dbType))) 748 749 750 ''') 751 752 # FIX: function name 753 o.write('''def '''+insertFuncName+'''(params,extraParams=None,dbType='postgres'): 754 \'\'\' 755 Give the SQL INSERT statement 756 @param params: dict keyed by field name of values 757 @param extraParams: any extra fields that you have created beyond the normal ais message fields 758 @rtype: sqlhelp.insert 759 @return: insert class instance 760 @todo: allow optional type checking of params? 761 @warning: this will take invalid keys happily and do what??? 762 \'\'\' 763 import sqlhelp 764 i = sqlhelp.insert(\'''' + msgName + '''\',dbType=dbType) 765 766 if dbType=='postgres': 767 finished = [] 768 for key in params: 769 if key in finished: 770 continue 771 772 if key not in toPgFields and key not in fromPgFields: 773 if type(params[key])==Decimal: i.add(key,float(params[key])) 774 else: i.add(key,params[key]) 775 else: 776 if key in fromPgFields: 777 val = params[key] 778 # Had better be a WKT type like POINT(-88.1 30.321) 779 i.addPostGIS(key,val) 780 finished.append(key) 781 else: 782 # Need to construct the type. 783 pgName = toPgFields[key] 784 #valStr='GeomFromText(\\''+pgTypes[pgName]+'(' 785 valStr=pgTypes[pgName]+'(' 786 vals = [] 787 for nonPgKey in fromPgFields[pgName]: 788 vals.append(str(params[nonPgKey])) 789 finished.append(nonPgKey) 790 valStr+=' '.join(vals)+')' 791 i.addPostGIS(pgName,valStr) 792 else: 793 for key in params: 794 if type(params[key])==Decimal: i.add(key,float(params[key])) 795 else: i.add(key,params[key]) 796 797 if None != extraParams: 798 for key in extraParams: 799 i.add(key,extraParams[key]) 800 801 return i 802 ''')
803 804 805 806 ###################################################################### 807 # Build Lookup tables for enumerated int or uint fields 808 ###################################################################### 809 810
811 -def buildLUT(o,msgET, verbose=False, prefixName=False):
812 ''' 813 Write lookup tables for enumerated types (uint or int, maybe bool too). 814 815 @todo: FIX: what to do about multiple entries with the same text? Need to ban that kind of thing 816 @todo: Make doc strings for each LUT. 817 818 @param o: open file where resulting code will be written 819 @param msgET: Element Tree starting at a message node 820 ''' 821 assert(msgET.tag=='message') 822 msgname = msgET.attrib['name'] 823 824 print 'Generating lookup tables for',msgname # FIX: verbose? 825 826 for field in msgET.xpath('field'): 827 name = field.attrib['name'] 828 if not hasSubTag(field,'lookuptable'): continue 829 lut = field.xpath('lookuptable')[0] 830 831 lutName = name 832 if prefixName: lutName = msgname+name.capitalize() 833 834 o.write(lutName+'EncodeLut = {\n') 835 for entry in lut.xpath('entry'): 836 o.write('\t\''+entry.text+'\':\''+entry.attrib['key']+'\',\n') 837 o.write('\t} #'+lutName+'EncodeLut\n') 838 o.write('\n') 839 840 # FIX: make doc string for LUT here 841 842 o.write(lutName+'DecodeLut = {\n') 843 for entry in lut.xpath('entry'): 844 o.write('\t\''+entry.attrib['key']+'\':\''+entry.text+'\',\n') 845 o.write('\t} # '+lutName+'EncodeLut\n') 846 o.write('\n')
847 848 # FIX: make doc string for LUT here 849 850 851 852 853 ###################################################################### 854 # ENCODERS 855 ######################################################################
856 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
857 ''' 858 Build the encoder for boolean variables 859 @type o: file like obj 860 @param o: where write the code 861 @type name: str 862 @param name: field name 863 @type type: str 864 @param type: bool, etc. 865 @type numbits: int = 1 866 @param numbits: How many bits per unit datum (must be 1 for bools) 867 @type required: bool or None 868 @param required: If not None, then the value must be set to this. 869 @type arraylen: int >= 1 870 @param arraylen: many bools will there be? FIX: handle variable 871 @type unavailable: bool or None 872 @param unavailable: the default value to use if none given (if not None) 873 @return: None 874 ''' 875 876 if verbose: print 'bool encode',name,': unvail=',unavailable 877 878 assert type.lower()=='bool' 879 assert numbits==1 880 if arraylen != 1: assert False # FIX... handle arrays 881 if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n') 882 if None != required: 883 assert type(required)==bool 884 if required: o.write('\t\tbvList.append(TrueBV)\n') 885 else: o.write('\t\tbvList.append(FalseBV)\n') 886 if verbose: o.write('\n') 887 return 888 889 if None==unavailable: 890 o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n') 891 o.write('\telse: bvList.append(FalseBV)\n') 892 else: # Have a default value that can be filled in 893 assert type(unavailable)==bool 894 o.write("\tif '"+name+"' in params:\n") 895 o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n') 896 o.write('\t\telse: bvList.append(FalseBV)\n') 897 o.write('\telse:\n') 898 if unavailable: o.write('\t\tbvList.append(TrueBV)\n') 899 else: o.write('\t\tbvList.append(FalseBV)\n') 900 if verbose: o.write('\n')
901
902 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
903 ''' 904 Build the encoder for unsigned integer variables 905 906 @type o: file like obj 907 @param o: where write the code 908 @type name: str 909 @param name: field name 910 @type type: str 911 @param type: uint, bool, etc. 912 @type numbits: int >= 1 913 @param numbits: How many bits per unit datum (must be 1..32) 914 @type required: bool or None 915 @param required: If not None, then the value must be set to this. 916 @type arraylen: int >= 1 917 @param arraylen: many unsigned ints will there be? FIX: handle variable 918 @type unavailable: bool or None 919 @param unavailable: the default value to use if none given (if not None) 920 @return: None 921 ''' 922 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 923 924 assert type=='uint' 925 assert numbits>=1 and numbits<=32 926 if arraylen != 1: assert False # FIX... handle arrays 927 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 928 929 if None != required: 930 if verbose: print ' required:',required 931 required=int(required) 932 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n') 933 if verbose: o.write('\n') 934 return 935 936 if None==unavailable: 937 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n') 938 else: # Have a default value that can be filled in 939 #assert type(unavailable)== 940 int(unavailable) # Make sure unavailable is a number object 941 o.write("\tif '"+name+"' in params:\n") 942 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n') 943 o.write('\telse:\n') 944 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n') 945 946 if verbose: o.write('\n')
947 948
949 -def encodeFloat(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
950 ''' 951 Build the encoder for IEEE float variables 952 953 @type o: file like obj 954 @param o: where write the code 955 @type name: str 956 @param name: field name 957 @type fieldType: str 958 @param fieldType: uint, bool, etc. 959 @type numbits: int >= 1 960 @param numbits: How many bits per unit datum (must be 1..32) 961 @type required: bool or None 962 @param required: If not None, then the value must be set to this. 963 @type arraylen: int >= 1 964 @param arraylen: many unsigned ints will there be? FIX: handle variable 965 @type unavailable: bool or None 966 @param unavailable: the default value to use if none given (if not None) 967 @return: None 968 ''' 969 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 970 971 assert numbits==32 # Force by the IEEE spec 972 if arraylen != 1: assert False # FIX... handle arrays 973 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 974 975 if None != required: 976 if verbose: print ' required:',required 977 required=int(required) 978 o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n') 979 if verbose: o.write('\n') 980 return 981 982 if None==unavailable: 983 o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n') 984 else: # Have a default value that can be filled in 985 int(unavailable) # Make sure unavailable is a number object 986 o.write("\tif '"+name+"' in params:\n") 987 o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n') 988 o.write('\telse:\n') 989 o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n') 990 991 if verbose: o.write('\n')
992 993
994 -def encodeAisstr6(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
995 ''' 996 Build the encoder for aisstr6 variables. Generally are arrays. 997 @bug: do we need to optionally check for a valid string? 998 999 @type o: file like obj 1000 @param o: where write the code 1001 @type name: str 1002 @param name: field name 1003 @type fieldType: str 1004 @param fieldType: uint, bool, etc. 1005 @type numbits: int >= 1 1006 @param numbits: How many bits per unit datum (must be 1..32) 1007 @type required: bool or None 1008 @param required: If not None, then the value must be set to this. 1009 @type arraylen: int >= 1 1010 @param arraylen: many unsigned ints will there be? FIX: handle variable 1011 @type unavailable: bool or None 1012 @param unavailable: the default value to use if none given (if not None) 1013 @return: None 1014 ''' 1015 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 1016 totLen = str(numbits*arraylen) 1017 assert numbits==6 # Each character must be 6 bits 1018 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 1019 1020 if None != required: 1021 if verbose: print ' required:',required 1022 required=int(required) 1023 o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+totLen+'))\n') 1024 if verbose: o.write('\n') 1025 return 1026 1027 if None==unavailable: 1028 o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 1029 else: # Have a default value that can be filled in 1030 o.write("\tif '"+name+"' in params:\n") 1031 o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 1032 o.write('\telse:\n') 1033 o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+totLen+'))\n') 1034 1035 if verbose: o.write('\n')
1036 1037
1038 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1039 ''' 1040 Build the encoder for signed integer variables 1041 1042 @type o: file like obj 1043 @param o: where write the code 1044 @type name: str 1045 @param name: field name 1046 @type type: str 1047 @param type: uint, bool, etc. 1048 @type numbits: int >= 1 1049 @param numbits: How many bits per unit datum (must be 1..32) 1050 @type required: bool or None 1051 @param required: If not None, then the value must be set to this. 1052 @type arraylen: int >= 1 1053 @param arraylen: many signed ints will there be? FIX: handle variable 1054 @type unavailable: number or None 1055 @param unavailable: the default value to use if none given (if not None) 1056 @return: None 1057 ''' 1058 if verbose: print ' encodeInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1059 1060 assert numbits>=1 and numbits<=32 1061 if arraylen != 1: assert False # FIX... handle arrays 1062 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1063 1064 if None != required: 1065 if verbose: print ' required:',required 1066 required=int(required) 1067 o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n') 1068 if verbose: o.write('\n') 1069 return 1070 1071 1072 if None==unavailable: 1073 o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n') 1074 else: # Have a default value that can be filled in 1075 #assert type(unavailable)== 1076 int(unavailable) # Make sure unavailable is a number object 1077 o.write("\tif '"+name+"' in params:\n") 1078 o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n') 1079 o.write('\telse:\n') 1080 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n') 1081 1082 if verbose: o.write('\n')
1083 1084 1085 1086 # FIX: Ummm... why am I passing the type? I guess it makes the one print statement easier
1087 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1088 ''' 1089 Build the encoder for signed decimal variables 1090 1091 @type o: file like obj 1092 @param o: where write the code 1093 @type name: str 1094 @param name: field name 1095 @type type: str 1096 @param type: decimal 1097 @type numbits: int >= 1 1098 @param numbits: How many bits per unit datum (must be 1..32) 1099 @type required: bool or None 1100 @param required: If not None, then the value must be set to this. 1101 @type arraylen: int >= 1 1102 @param arraylen: many decimals will there be? FIX: handle variable 1103 @type unavailable: Decimal or None 1104 @param unavailable: the default value to use if none given (if not None) 1105 @return: None 1106 ''' 1107 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1108 1109 assert numbits>=1 and numbits<=32 1110 if arraylen != 1: assert False # FIX... handle arrays 1111 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1112 1113 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 1114 if None == scale: 1115 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 1116 print 'Beware canadians bearing travel videos' 1117 scale='1' 1118 1119 if None != required: 1120 if verbose: print ' required:',required 1121 required=int(required) 1122 o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n') 1123 if verbose: o.write('\n') 1124 return 1125 1126 # FIX: can I get rid of the Decimal around params? 1127 if None==unavailable: 1128 o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 1129 else: # Have a default value that can be filled in 1130 o.write("\tif '"+name+"' in params:\n") 1131 o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 1132 o.write('\telse:\n') 1133 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n') 1134 1135 if verbose: o.write('\n')
1136 1137 1138
1139 -def encodeUDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1140 ''' 1141 Build the encoder for signed decimal variables 1142 1143 @type o: file like obj 1144 @param o: where write the code 1145 @type name: str 1146 @param name: field name 1147 @type type: str 1148 @param type: decimal 1149 @type numbits: int >= 1 1150 @param numbits: How many bits per unit datum (must be 1..32) 1151 @type required: bool or None 1152 @param required: If not None, then the value must be set to this. 1153 @type arraylen: int >= 1 1154 @param arraylen: many decimals will there be? FIX: handle variable 1155 @type unavailable: Decimal or None 1156 @param unavailable: the default value to use if none given (if not None) 1157 @return: None 1158 ''' 1159 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1160 assert type=='udecimal' 1161 assert numbits>=1 and numbits<=32 1162 if arraylen != 1: assert False # FIX... handle arrays 1163 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1164 1165 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 1166 if None == scale: 1167 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 1168 print 'Beware canadians bearing travel videos' 1169 scale='1' 1170 1171 if None != required: 1172 if verbose: print ' required:',required 1173 required=int(required) 1174 assert(0<=required) 1175 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(int(Decimal(required)*Decimal(scale)))+'),'+str(numbits)+'))\n') 1176 if verbose: o.write('\n') 1177 return 1178 1179 # FIX: can I get rid of the Decimal around params? 1180 if None==unavailable: 1181 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 1182 else: # Have a default value that can be filled in 1183 o.write("\tif '"+name+"' in params:\n") 1184 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 1185 o.write('\telse:\n') 1186 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int('+str(int(Decimal(unavailable)*Decimal(scale)))+')),'+str(numbits)+'))\n') 1187 1188 if verbose: o.write('\n')
1189 1190 1191
1192 -def encodeBinary(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1193 ''' 1194 Build the encoder for binary variables. This is just a pass through. 1195 This is used for the ais binary message wrapper (e.g. msg 8). Do 1196 not use it within binary messages. 1197 1198 @type o: file like obj 1199 @param o: where write the code 1200 @type name: str 1201 @param name: field name 1202 @type type: str 1203 @param type: binary 1204 @type numbits: int >= 1 1205 @param numbits: How many bits per unit datum (must be 1..1024 or so) 1206 @type required: bool or None 1207 @param required: If not None, then the value must be set to this. 1208 @type arraylen: int >= 1 1209 @param arraylen: many decimals will there be? FIX: handle variable 1210 @type unavailable: Decimal or None 1211 @param unavailable: the default value to use if none given (if not None) 1212 @return: None 1213 ''' 1214 if verbose: print ' encode'+name+':',type,numbits,'Req:',required,'alen:',arraylen,unavailable 1215 assert type=='binary' 1216 assert (numbits>=1 and numbits<=1024) or numbits==-1 1217 assert (None == required) # don't allow this 1218 assert (None == unavailable) # don't allow this 1219 1220 if arraylen != 1: assert False # Do not handle arrays. Arrays of bits is just not necessary. 1221 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1222 1223 # FIX: can I get rid of the Decimal around params? 1224 o.write('\tbvList.append(params[\''+name+'\'])\n') # Just pass it through 1225 1226 if verbose: o.write('\n')
1227 1228 1229 ###################################################################### 1230 # DECODERS 1231 ###################################################################### 1232
1233 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1234 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1235 ''' 1236 Build the decoder for boolean variables 1237 1238 @type o: file like obj 1239 @param o: where write the code 1240 @type name: str 1241 @param name: field name 1242 @type type: str 1243 @param type: uint, bool, etc. 1244 @type startindex: int 1245 @param startindex: bit that begins the bool(s) 1246 @type numbits: int = 1 1247 @param numbits: How many bits per unit datum (must be 1 for bools) 1248 @type required: bool or None 1249 @param required: If not None, then the value must be set to this. 1250 @type arraylen: int >= 1 1251 @param arraylen: many bools will there be? FIX: handle variable 1252 @type unavailable: bool or None 1253 @param unavailable: the default value to use if none given (if not None) 1254 @type bv: str 1255 @param bv: BitVector containing the incoming data 1256 @type dataDict: str 1257 @param dataDict: dictionary in which to place the results 1258 @type decodeOnly: bool 1259 @param decodeOnly: Set to true to only get the code for decoding 1260 @rtype: int 1261 @return: index one past the end of where this read 1262 ''' 1263 assert(type=='bool') 1264 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1265 #int(startindex); int(numbits) # Make sure it is a number 1266 assert numbits==1 1267 assert arraylen == 1 # FIX... handle arrays 1268 1269 if None != required: 1270 assert type(required)==bool 1271 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1272 if required: o.write('True\n') 1273 else: o.write('False\n') 1274 if not decodeOnly: o.write('\n') 1275 return int(startindex)+int(numbits) 1276 1277 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1278 o.write('bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))') 1279 if not decodeOnly: o.write('\n') 1280 1281 return int(startindex)+int(numbits)
1282 1283
1284 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1285 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1286 ''' 1287 Build the decoder for unsigned integer variables 1288 1289 @type o: file like obj 1290 @param o: where write the code 1291 @type name: str 1292 @param name: field name 1293 @type type: str 1294 @param type: uint, etc. 1295 @type startindex: int 1296 @param startindex: bit that begins the uint(s) 1297 @type numbits: int >= 1 1298 @param numbits: How many bits per unit datum 1299 @type required: int or None 1300 @param required: If not None, then the value must be set to this. 1301 @type arraylen: int >= 1 1302 @param arraylen: many ints will there be? FIX: handle variable 1303 @type unavailable: int or None 1304 @param unavailable: the default value to use if none given (if not None) 1305 @type bv: str 1306 @param bv: BitVector containing the incoming data 1307 @type dataDict: str 1308 @param dataDict: dictionary in which to place the results 1309 @type decodeOnly: bool 1310 @param decodeOnly: Set to true to only get the code for decoding 1311 @rtype: int 1312 @return: index one past the end of where this read 1313 ''' 1314 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1315 if None==arraylen: arraylen=1 1316 assert arraylen == 1 # FIX... handle arrays 1317 assert numbits>=1 1318 if not decodeOnly: verbose=False 1319 1320 if None != required: 1321 int(required) # Make sure required is a number 1322 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1323 o.write(str(required)) 1324 if not decodeOnly: o.write('\n') 1325 return startindex+numbits 1326 1327 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1328 o.write('int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])') 1329 if not decodeOnly: o.write('\n') 1330 if verbose: o.write('\n') 1331 1332 return startindex+numbits
1333 1334
1335 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1336 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1337 ''' 1338 Build the decoder for unsigned integer variables 1339 1340 @type o: file like obj 1341 @param o: where write the code 1342 @type name: str 1343 @param name: field name 1344 @type type: str 1345 @param type: int 1346 @type startindex: int 1347 @param startindex: bit that begins the int(s) 1348 @type numbits: int >= 1 1349 @param numbits: How many bits per unit datum 1350 @type required: int or None 1351 @param required: If not None, then the value must be set to this. 1352 @type arraylen: int >= 1 1353 @param arraylen: many ints will there be? FIX: handle variable 1354 @type unavailable: int or None 1355 @param unavailable: the default value to use if none given (if not None) 1356 @type bv: str 1357 @param bv: BitVector containing the incoming data 1358 @type dataDict: str 1359 @param dataDict: dictionary in which to place the results 1360 @type decodeOnly: bool 1361 @param decodeOnly: Set to true to only get the code for decoding 1362 @rtype: int 1363 @return: index one past the end of where this read 1364 ''' 1365 assert type=='int' 1366 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1367 if None==arraylen: arraylen=1 1368 end = startindex+int(numbits)*int(arraylen) 1369 assert arraylen == 1 # FIX... handle arrays 1370 assert numbits>=1 1371 1372 if None != required: 1373 int(required) # Make sure required is a number 1374 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1375 o.write(str(required)) 1376 if not decodeOnly: o.write('\n') 1377 return end 1378 1379 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1380 o.write('binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])') 1381 if not decodeOnly: o.write('\n') 1382 if verbose: o.write('\n') 1383 1384 return end
1385
1386 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1387 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1388 ''' 1389 Build the decoder for IEEE float variables 1390 1391 @type o: file like obj 1392 @param o: where write the code 1393 @type name: str 1394 @param name: field name 1395 @type type: str 1396 @param type: int 1397 @type startindex: int 1398 @param startindex: bit that begins the int(s) 1399 @type numbits: int >= 1 1400 @param numbits: How many bits per unit datum 1401 @type required: float or None 1402 @param required: If not None, then the value must be set to this. 1403 @type arraylen: int >= 1 1404 @param arraylen: many ints will there be? FIX: handle variable 1405 @type unavailable: float or None 1406 @param unavailable: the default value to use if none given (if not None) 1407 @type bv: str 1408 @param bv: BitVector containing the incoming data 1409 @type dataDict: str 1410 @param dataDict: dictionary in which to place the results 1411 @type decodeOnly: bool 1412 @param decodeOnly: Set to true to only get the code for decoding 1413 @rtype: int 1414 @return: index one past the end of where this read 1415 ''' 1416 assert type=='float' 1417 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1418 if None==arraylen: arraylen=1 1419 end = startindex+int(numbits)*int(arraylen) 1420 assert arraylen == 1 # FIX... handle arrays 1421 assert numbits>=1 1422 1423 if None != required: 1424 float(required) # Make sure required is a number 1425 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1426 o.write(str(required)) 1427 if not decodeOnly: o.write('\n') 1428 if verbose: o.write('\n') 1429 return end 1430 1431 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1432 o.write('binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])') 1433 if not decodeOnly: o.write('\n') 1434 1435 return end
1436 1437
1438 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1439 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1440 ''' 1441 Build the decoder for aisstr6 variables. Generally arrays. 1442 @bug: FIX: validate strings?? 1443 @type o: file like obj 1444 @param o: where write the code 1445 @type name: str 1446 @param name: field name 1447 @type type: str 1448 @param type: 'aisstr6' 1449 @type startindex: int 1450 @param startindex: bit that begins the int(s) 1451 @type numbits: int >= 1 1452 @param numbits: How many bits per unit datum 1453 @type required: restricted str or None 1454 @param required: If not None, then the value must be set to this. 1455 @type arraylen: int >= 1 1456 @param arraylen: many ints will there be? FIX: handle variable 1457 @type unavailable: restricted str or None 1458 @param unavailable: the default value to use if none given (if not None) 1459 @type bv: str 1460 @param bv: BitVector containing the incoming data 1461 @type dataDict: str 1462 @param dataDict: dictionary in which to place the results 1463 @type decodeOnly: bool 1464 @param decodeOnly: Set to true to only get the code for decoding 1465 @rtype: int 1466 @return: index one past the end of where this read 1467 ''' 1468 assert type=='aisstr6' 1469 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1470 if None==arraylen: arraylen=1 1471 end = startindex+int(numbits)*int(arraylen) 1472 assert arraylen >= 1 # FIX... handle arrays 1473 assert numbits>=1 1474 1475 if None != required: 1476 float(required) # Make sure required is a number 1477 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1478 o.write(required) 1479 if not decodeOnly: o.write('\n') 1480 return end 1481 1482 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1483 o.write('aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])') 1484 if not decodeOnly: o.write('\n') 1485 1486 return end
1487 1488
1489 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1490 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1491 ''' 1492 Build the decoder for signed decimal variables 1493 1494 @type o: file like obj 1495 @param o: where write the code 1496 @type name: str 1497 @param name: field name 1498 @type type: str 1499 @param type: 'decimal' 1500 @type startindex: int 1501 @param startindex: bit that begins the int(s) 1502 @type numbits: int >= 1 1503 @param numbits: How many bits per unit datum 1504 @type required: Decimal or None 1505 @param required: If not None, then the value must be set to this. 1506 @type arraylen: int >= 1 1507 @param arraylen: many ints will there be? FIX: handle variable 1508 @type unavailable: Decimal or None 1509 @param unavailable: the default value to use if none given (if not None) 1510 @type bv: str 1511 @param bv: BitVector containing the incoming data 1512 @type dataDict: str 1513 @param dataDict: dictionary in which to place the results 1514 @type decodeOnly: bool 1515 @param decodeOnly: Set to true to only get the code for decoding 1516 @rtype: int 1517 @return: index one past the end of where this read 1518 ''' 1519 assert type=='decimal' 1520 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1521 if None==arraylen: arraylen=1 1522 end = startindex+int(numbits)*int(arraylen) 1523 assert arraylen == 1 # FIX... handle arrays 1524 assert numbits>=1 and numbits <= 32 1525 1526 if None == scale: scale='1' # Warning about this was in the encode section 1527 1528 if None != required: 1529 Decimal(required) # Make sure required is a number 1530 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1531 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1532 if not decodeOnly: o.write('\n') 1533 return end 1534 1535 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1536 o.write('Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')') 1537 if not decodeOnly: o.write('\n') 1538 1539 return end
1540 1541 1542 1543
1544 -def decodeUDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1545 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1546 ''' 1547 Build the decoder for unsigned decimal variables 1548 1549 @type o: file like obj 1550 @param o: where write the code 1551 @type name: str 1552 @param name: field name 1553 @type type: str 1554 @param type: 'udecimal' 1555 @type startindex: int 1556 @param startindex: bit that begins the int(s) 1557 @type numbits: int >= 1 1558 @param numbits: How many bits per unit datum 1559 @type required: Decimal or None 1560 @param required: If not None, then the value must be set to this. 1561 @type arraylen: int >= 1 1562 @param arraylen: many ints will there be? FIX: handle variable 1563 @type unavailable: Decimal or None 1564 @param unavailable: the default value to use if none given (if not None) 1565 @type bv: str 1566 @param bv: BitVector containing the incoming data 1567 @type dataDict: str 1568 @param dataDict: dictionary in which to place the results 1569 @type decodeOnly: bool 1570 @param decodeOnly: Set to true to only get the code for decoding 1571 @rtype: int 1572 @return: index one past the end of where this read 1573 ''' 1574 assert type=='udecimal' 1575 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1576 if None==arraylen: arraylen=1 1577 end = startindex+int(numbits)*int(arraylen) 1578 assert arraylen == 1 # FIX... handle arrays 1579 assert numbits>=1 and numbits <= 32 1580 1581 if None == scale: scale='1' # Warning about this was in the encode section 1582 1583 if None != required: 1584 assert (Decimal(required)>=0.) # Make sure required is a number and not negative 1585 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1586 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1587 if not decodeOnly: o.write('\n') 1588 return end 1589 1590 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1591 o.write('Decimal(int('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')') 1592 if not decodeOnly: o.write('\n') 1593 1594 return end
1595 1596
1597 -def decodeBinary(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1598 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1599 ''' 1600 Build the decoder for unsigned decimal variables 1601 1602 @type o: file like obj 1603 @param o: where write the code 1604 @type name: str 1605 @param name: field name 1606 @type type: str 1607 @param type: 'udecimal' 1608 @type startindex: int 1609 @param startindex: bit that begins the int(s) 1610 @type numbits: int >= 1 1611 @param numbits: How many bits per unit datum. If -1, then read to the end of the message 1612 @type required: Decimal or None 1613 @param required: If not None, then the value must be set to this. 1614 @type arraylen: int >= 1 1615 @param arraylen: many ints will there be? FIX: handle variable 1616 @type unavailable: Decimal or None 1617 @param unavailable: the default value to use if none given (if not None) 1618 @type bv: str 1619 @param bv: BitVector containing the incoming data 1620 @type dataDict: str 1621 @param dataDict: dictionary in which to place the results 1622 @type decodeOnly: bool 1623 @param decodeOnly: Set to true to only get the code for decoding 1624 @rtype: int 1625 @return: index one past the end of where this read 1626 ''' 1627 assert type=='binary' 1628 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1629 if None==arraylen: arraylen=1 1630 end = startindex+int(numbits)*int(arraylen) 1631 assert arraylen == 1 # FIX... handle arrays 1632 assert (numbits>=1 and numbits <= 1024) or -1==numbits # What is good max? 1633 # FIX: assert not required and not an array an not unavailable 1634 1635 1636 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1637 o.write(bv+'['+str(startindex)+':') 1638 if int(numbits) != -1: o.write(str(end)) # -1 means go to the end of the message 1639 o.write(']') 1640 if not decodeOnly: o.write('\n') 1641 1642 return end
1643 1644 1645 1646 ###################################################################### 1647 # THE REST 1648 ###################################################################### 1649 1650 1651
1652 -def buildTestParamFunc(o,msgET, verbose=False, prefixName=False):
1653 '''Scrape the testvalues to make a basic param 1654 1655 @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies? 1656 ''' 1657 name = msgET.attrib['name'] 1658 1659 if prefixName: o.write('def '+name+'TestParams():\n') 1660 else: o.write('def testParams():\n') 1661 o.write("\t'''Return a params file base on the testvalue tags.\n\t@rtype: dict\n\t@return: params based on testvalue tags\n\t'''\n") 1662 o.write('\tparams = {}\n') 1663 for field in msgET.xpath('field'): 1664 name = field.attrib['name'] 1665 type = field.attrib['type'] 1666 if verbose: print 'buildTestParamFunc ...',name,type 1667 val = None 1668 if hasSubTag(field,'testvalue') and hasSubTag(field,'required'): 1669 print 'ERROR: can not have both test value and required tags in the same field' 1670 assert(False) 1671 if hasSubTag(field,'testvalue'): 1672 val = field.xpath('testvalue')[0].text 1673 else: 1674 if not hasSubTag(field,'required'): 1675 sys.exit("ERROR: missing required or testvalue for field: "+name) 1676 val = field.xpath('required')[0].text 1677 if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val 1678 o.write('\tparams[\''+name+'\'] = ') 1679 if type=='bool': 1680 if val=='1' or val.lower=='true': val = 'True' 1681 else: val = 'False' 1682 o.write(val) 1683 elif type in ('uint','int','float'): 1684 o.write(val) 1685 elif type in ('decimal','udecimal'): 1686 o.write('Decimal(\''+val+'\')') 1687 1688 elif type in ('aisstr6'): 1689 o.write('\''+val+'\'') 1690 elif type in ('binary'): 1691 o.write('BitVector(bitstring=\''+val+'\')') 1692 else: 1693 print 'ERROR: type not handled ...',type,' (found in the ',name,' field). Time to buy more coffee' 1694 suggestType(name,type) 1695 assert(False) 1696 1697 o.write('\n') 1698 1699 1700 o.write('\n\treturn params\n\n')
1701
1702 -def buildUnitTest(o,msgET, verbose=False, prefixName=False):
1703 ''' 1704 Write the unittests for a message 1705 1706 @param o: open file where resulting code will be written 1707 @param msgET: Element Tree starting at a message node 1708 ''' 1709 assert(msgET.tag=='message') 1710 name = msgET.attrib['name'] 1711 1712 buildTestParamFunc(o,msgET, prefixName=prefixName) 1713 1714 o.write('class Test'+name+'(unittest.TestCase):\n') 1715 o.write("\t'''Use testvalue tag text from each type to build test case the "+name+" message'''\n") 1716 o.write('\tdef testEncodeDecode(self):\n\n') 1717 if prefixName: 1718 o.write('\t\tparams = '+name+'TestParams()\n') 1719 o.write('\t\tbits = '+name+'Encode(params)\n') 1720 o.write('\t\tr = '+name+'Decode(bits)\n\n') 1721 else: 1722 o.write('\t\tparams = testParams()\n') 1723 o.write('\t\tbits = encode(params)\n') 1724 o.write('\t\tr = decode(bits)\n\n') 1725 1726 o.write('\t\t# Check that each parameter came through ok.\n') 1727 for field in msgET.xpath('field'): 1728 name = field.attrib['name'] 1729 type = field.attrib['type'] 1730 if type in ('bool','uint','int','aisstr6','binary'): 1731 o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n') 1732 else: 1733 # float, decimal, udecimal 1734 # FIX: look up the decimal places if decimal 1735 places = '3' 1736 if hasSubTag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text 1737 o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n')
1738 1739 1740
1741 -def buildEncode(o,msgET, verbose=False, prefixName=False):
1742 ''' 1743 Write the encoder/decoder for a message 1744 1745 http://jaynes.colorado.edu/PythonIdioms.html 1746 1747 @param o: open file where resulting code will be written 1748 @param msgET: Element Tree starting at a message node 1749 @todo: handle ais message 20 optional. very troublesome 1750 ''' 1751 assert(msgET.tag=='message') 1752 name = msgET.attrib['name'] # fix rename this variable to avoid hiding later on by field name 1753 1754 print 'Generating encoder for',name # FIX: verbose? 1755 funcName = 'encode' 1756 if prefixName: funcName = name+'Encode' 1757 o.write('def '+funcName+'(params, validate=False):\n') 1758 1759 ######################################## 1760 # doc string 1761 o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg "+name+".\n\n") 1762 o.write('\tFields in params:\n') 1763 for field in msgET.xpath('field'): 1764 if verbose: print field.tag,field.attrib['name'] 1765 desc = field[0].text.replace('\n',' ') # get ride of new lines 1766 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 1767 if len(field.xpath("required")) == 1: 1768 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 1769 1770 o.write('\n') 1771 o.write('\t@param params: Dictionary of field names/values. Throws a ValueError exception if required is missing\n') 1772 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 1773 1774 o.write("\t@rtype: BitVector\n") 1775 o.write("\t@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8\n") 1776 o.write("\t@note: The returned bits may not be 6 bit aligned. It is up to you to pad out the bits.\n") 1777 o.write("\t'''\n\n") 1778 1779 ######################################## 1780 # Actually build the code 1781 1782 o.write('\tbvList = []\n') 1783 1784 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 1785 1786 dynamicArrays = False # Set to true when positioning must be calculated 1787 1788 for field in msgET.xpath('field'): 1789 name = field.attrib['name'] 1790 type = field.attrib['type'] 1791 numbits = int(field.attrib['numberofbits']) 1792 required = None; 1793 if hasSubTag(field,'required'): 1794 required = field.xpath('required')[0].text 1795 #print 'required set for',name,'to',required 1796 unavailable=None; 1797 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1798 arraylen=1 1799 if 'arraylength' in field.attrib: 1800 arraylen=int(field.attrib['arraylength']) 1801 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1802 else: 1803 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 1804 1805 if type=='bool' : encodeBool (o,name,type,numbits,required,arraylen,unavailable) 1806 elif type=='uint' : encodeUInt (o,name,type,numbits,required,arraylen,unavailable) 1807 elif type=='int' : encodeInt (o,name,type,numbits,required,arraylen,unavailable) 1808 elif type=='float' : encodeFloat (o,name,type,numbits,required,arraylen,unavailable) 1809 elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable) 1810 elif type=='decimal': 1811 scale = None 1812 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1813 encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale) 1814 elif type=='udecimal': 1815 scale = None 1816 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1817 encodeUDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale) 1818 elif type=='binary': encodeBinary(o,name,type,numbits,required,arraylen,unavailable) 1819 else: 1820 print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type 1821 suggestType (name,type) 1822 assert False 1823 1824 # o.write('\n\tbv=binary.joinBV(bvList)\n') 1825 # o.write('\n\tbvLen=len(bv)\n') 1826 # o.write('\n\tif bvLen%6!=0:\n') 1827 # o.write('\n\t bv = bv + BitVector(size=bvLen%6) # \n') 1828 # o.write('\n\treturn bv\n\n') 1829 o.write('\n\treturn binary.joinBV(bvList)\n\n')
1830 1831 1832 ###################################################################### 1833 # DECODER ONE PART AT A TIME - if only using a small part, save some cycles! 1834 1835 1836 #, msgDict=None):
1837 -def buildDecodeParts(o,msgET, verbose=False, prefixName=False):
1838 1839 ''' 1840 Write the decoder for a message 1841 1842 @param o: open file where resulting code will be written 1843 @type msgET: elementtree 1844 @param prefixName: if True, put the name of the message on the functions. 1845 @param msgET: Element Tree starting at a message node 1846 @return: None 1847 1848 @todo: FIX: doc strings for each decode! 1849 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 1850 @todo: May want to take a dictionary of already decoded fields to speed things that need prior info 1851 for things like variable length arrays 1852 ''' 1853 1854 assert(msgET.tag=='message') 1855 name = msgET.attrib['name'] 1856 1857 print 'Generating partial decode functions for',name 1858 1859 baseName = name+'Decode' 1860 if not prefixName: baseName = 'decode' 1861 1862 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 1863 1864 for field in msgET.xpath('field'): 1865 name = field.attrib['name'] 1866 type = field.attrib['type'] 1867 1868 o.write('def '+baseName+name+'(bv, validate=False):\n') 1869 # Follow the same convention of decoding into a dict so that code is the same 1870 #o.write('\tr={};') 1871 o.write('\treturn ') 1872 1873 numbits = int(field.attrib['numberofbits']) 1874 required = None; 1875 if hasSubTag(field,'required'): 1876 required = field.xpath('required')[0].text 1877 unavailable=None; 1878 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1879 arraylen=1 1880 if 'arraylength' in field.attrib: 1881 arraylen=int(field.attrib['arraylength']) 1882 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1883 1884 assert None!=startindex 1885 if verbose: print 'startindex',startindex 1886 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1887 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1888 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1889 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1890 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1891 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1892 1893 elif type=='decimal': 1894 scale = None 1895 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1896 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True) 1897 elif type=='udecimal': 1898 scale = None 1899 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1900 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True) 1901 1902 else: 1903 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 1904 suggestType (name,type) 1905 assert False 1906 1907 #o.write('\treturn(r[\''+name+'\'])\n\n') 1908 o.write('\n\n')
1909 1910 1911 ###################################################################### 1912 # DECODER RING 1913
1914 -def buildDecode(o,msgET, verbose=False, prefixName=False):
1915 ''' 1916 Write the decoder for a message 1917 1918 @param o: open file where resulting code will be written 1919 @type msgET: elementtree 1920 @param msgET: Element Tree starting at a message node 1921 @return: None 1922 1923 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 1924 ''' 1925 1926 assert(msgET.tag=='message') 1927 name = msgET.attrib['name'] 1928 1929 print 'Generating decoder for',name 1930 funcName = 'decode' 1931 if prefixName: funcName = name+'Decode' 1932 o.write('def '+funcName+'(bv, validate=False):\n') 1933 1934 ######################################## 1935 # doc string 1936 o.write("\t'''Unpack a "+name+" message \n\n") 1937 o.write('\tFields in params:\n') 1938 for field in msgET.xpath('field'): 1939 desc = field[0].text.replace('\n',' ') # get ride of new lines 1940 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 1941 if len(field.xpath("required")) == 1: 1942 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 1943 1944 o.write('\n') 1945 o.write('\t@type bv: BitVector\n') 1946 o.write('\t@param bv: Bits defining a message\n') 1947 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 1948 1949 o.write("\t@rtype: dict\n") 1950 o.write("\t@return: params\n") 1951 o.write("\t'''\n\n") 1952 1953 1954 o.write('\t#Would be nice to check the bit count here..\n') 1955 o.write('\t#if validate:\n') 1956 o.write('\t#\tassert (len(bv)==FIX: SOME NUMBER)\n') 1957 1958 1959 ######################################## 1960 # Actually build the code 1961 1962 o.write('\tr = {}\n') 1963 1964 1965 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 1966 1967 dynamicArrays = False # Set to true when positioning must be calculated 1968 1969 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 1970 1971 for field in msgET.xpath('field'): 1972 name = field.attrib['name'] 1973 type = field.attrib['type'] 1974 numbits = int(field.attrib['numberofbits']) 1975 required = None; 1976 if hasSubTag(field,'required'): 1977 required = field.xpath('required')[0].text 1978 #print 'required set for',name,'to',required 1979 unavailable=None; 1980 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1981 arraylen=1 1982 if 'arraylength' in field.attrib: 1983 arraylen=int(field.attrib['arraylength']) 1984 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1985 else: 1986 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 1987 1988 assert None!=startindex 1989 if verbose: print 'startindex',startindex 1990 1991 if hasSubTag(field,'optional'): 1992 # NoReturn means that this field is not a point that needs a check to see if we are done 1993 # FIX: need to do something more efficient 1994 if msgET.attrib['aismsgnum']!='20': 1995 sys.exit('optional is only allow on AIS msg 20. Do NOT use it on new messages!!!') 1996 text = field.xpath('optional')[0].text 1997 if None==field.xpath('optional')[0].text: # Found a NoReturn 1998 # Message 20 is a pain! 1999 pad = 8-(startindex%8) 2000 print 'not found: noreturn. pad=',pad 2001 assert(pad<=6) 2002 o.write('\tif len(bv)<='+str(startindex+pad)+': return r; # All fields below are optional\n') 2003 elif text != 'NoReturn': 2004 sys.exit ('ERROR: optional text must be NoReturn or empty') 2005 2006 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable) 2007 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 2008 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 2009 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable) 2010 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable) 2011 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable) 2012 2013 elif type=='decimal': 2014 scale = None 2015 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2016 #print 'pre call...' 2017 #print ' required: "'+str(required)+'" scale: "'+str(scale)+'" unavail:', unavailable 2018 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale) 2019 elif type=='udecimal': 2020 scale = None 2021 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2022 #print 'pre call...' 2023 #print ' required: "'+str(required)+'" scale: "'+str(scale)+'" unavail:', unavailable 2024 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale) 2025 2026 else: 2027 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 2028 suggestType (name,type) 2029 assert False 2030 2031 2032 if None==startindex: print 'FIX: here. drat. treat me right' 2033 assert None!=startindex 2034 2035 2036 o.write('\treturn r\n\n')
2037 2038 2039 2040 #def getPythonType(aType): 2041 # ''' 2042 # Translate a 2043 # @rtype: str 2044 # @return: name of the python type 2045 # ''' 2046 2047 aisType2pythonType={ 2048 'bool':'Bool', 2049 'uint':'int', 2050 'int':'int', 2051 'udecimal':'Decimal', 2052 'decimal':'Decimal', 2053 'aisstr6':'str', 2054 'binary':'str', 2055 'float':'Float', 2056 } 2057 2058 import copy 2059 aisType2optParseType=copy.deepcopy(aisType2pythonType) 2060 aisType2optParseType['bool']='int' # FIX: make these appear as a flag variable 2061 aisType2optParseType['aisstr6']='string' 2062 aisType2optParseType['aisstr6']='string' 2063 aisType2optParseType['binary']='string' 2064 aisType2optParseType['udecimal']='string' # FIX: Is this a good choice? 2065 aisType2optParseType['decimal']='string' 2066 aisType2optParseType['float']='float' 2067 2068
2069 -def buildOptParse(o,msgET, prefixName=False):
2070 '''Create a function that adds the options to a parse object''' 2071 2072 assert None != msgET 2073 assert msgET.tag=='message' 2074 msgName = msgET.attrib['name'] 2075 2076 prefix='' 2077 if prefixName: prefix=msgName 2078 2079 funcName = 'addMsgOptions' 2080 if prefixName: funcName = msgName + 'AddMsgOptions' 2081 o.write('\ndef '+funcName+'(parser):') 2082 2083 # FIX: write out a doc string 2084 2085 o.write(''' 2086 parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true', 2087 help='decode a "'''+msgName+'''" AIS message') 2088 parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true', 2089 help='encode a "'''+msgName+'''" AIS message') 2090 ''') 2091 2092 #print 'here...', msgName, prefixName 2093 for field in msgET.xpath('field'): 2094 name = field.attrib['name'] 2095 fieldType = field.attrib['type'] 2096 if hasSubTag(field,'required'): 2097 print 'skipping required field ...',name,fieldType 2098 continue 2099 #print 'there',name,fieldType 2100 o.write('\tparser.add_option(\'--') 2101 if prefixName: o.write(msgName+'-') 2102 o.write(name+'-field\', dest=\''+name+'Field\'') 2103 if hasSubTag(field,'unavailable'): 2104 val = field.xpath('unavailable')[0].text 2105 o.write(',default=') 2106 if fieldType in ('uint','int','float'): 2107 o.write(val) 2108 elif fieldType in ('decimal','udecimal'): 2109 o.write('Decimal(\''+val+'\')') 2110 elif fieldType in ('aisstr6','bitvector'): 2111 o.write('\''+val+'\'') 2112 o.write(',metavar=\''+fieldType+'\',type=\''+aisType2optParseType[fieldType]+'\'') 2113 o.write('\n\t\t,help=\'Field parameter value [default: %default]\')\n')
2114 2115 2116
2117 -def buildMain(o, msgET, prefixName=False):
2118 assert None != msgET 2119 assert msgET.tag=='message' 2120 msgName = msgET.attrib['name'] 2121 2122 prefix='' 2123 if prefixName: prefix=msgName 2124 2125 buildOptParse(o, msgET, prefixName) 2126 2127 2128 o.write(''' 2129 ############################################################ 2130 if __name__=='__main__': 2131 2132 from optparse import OptionParser 2133 parser = OptionParser(usage="%prog [options]", 2134 version="%prog "+__version__) 2135 2136 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 2137 help='run the documentation tests') 2138 parser.add_option('--unit-test',dest='unittest',default=False,action='store_true', 2139 help='run the unit tests') 2140 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 2141 help='Make the test output verbose') 2142 2143 # FIX: remove nmea from binary messages. No way to build the whole packet? 2144 # FIX: or build the surrounding msg 8 for a broadcast? 2145 typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message? 2146 parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType' 2147 ,default='nmeapayload' 2148 ,help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]') 2149 2150 2151 outputChoices = ('std','html','csv','sql' ''') 2152 # 'xml' - FIX: need to add xml output 2153 if haveLocatableMessage(msgET): o.write(', \'kml\',\'kml-full\'') 2154 o.write(''') 2155 parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType' 2156 ,default='std' 2157 ,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]') 2158 2159 parser.add_option('-o','--output',dest='outputFileName',default=None, 2160 help='Name of the python file to write [default: stdout]') 2161 2162 parser.add_option('-f','--fields',dest='fieldList',default=None, action='append', 2163 choices=fieldList, 2164 help='Which fields to include in the output. Currently only for csv output [default: all]') 2165 2166 parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true', 2167 help='Print the field name for csv') 2168 2169 parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true', 2170 help='Print out an sql create command for the table.') 2171 2172 dbChoices = ('sqlite','postgres') 2173 parser.add_option('-D','--db-type',dest='dbType',default='postgres' 2174 ,choices=dbChoices,type='choice' 2175 ,help='What kind of database ('+', '.join(dbChoices)+') [default: %default]') 2176 2177 ''') 2178 2179 o.write('''\taddMsgOptions(parser)\n''') 2180 2181 o.write(''' 2182 (options,args) = parser.parse_args() 2183 success=True 2184 2185 if options.doctest: 2186 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 2187 sys.argv= [sys.argv[0]] 2188 if options.verbose: sys.argv.append('-v') 2189 import doctest 2190 numfail,numtests=doctest.testmod() 2191 if numfail==0: print 'ok' 2192 else: 2193 print 'FAILED' 2194 success=False 2195 2196 if not success: sys.exit('Something Failed') 2197 del success # Hide success from epydoc 2198 2199 if options.unittest: 2200 sys.argv = [sys.argv[0]] 2201 if options.verbose: sys.argv.append('-v') 2202 unittest.main() 2203 2204 outfile = sys.stdout 2205 if None!=options.outputFileName: 2206 outfile = file(options.outputFileName,'w') 2207 2208 ''') 2209 2210 2211 2212 ############################## 2213 # encode 2214 ############################## 2215 # FIX: make everything tabs. Grr. 2216 o.write('\n\tif options.doEncode:\n') 2217 o.write('\t\t# First make sure all non required options are specified\n') 2218 for field in msgET.xpath('field'): 2219 name = field.attrib['name'] 2220 fieldType = field.attrib['type'] 2221 varName = prefix+name+'Field' 2222 if not hasSubTag(field,'required'): 2223 o.write('\t\tif None==options.'+varName+': parser.error("missing value for '+varName+'")\n') 2224 2225 # Build dict 2226 o.write('\t\tmsgDict={\n') 2227 for field in msgET.xpath('field'): 2228 name = field.attrib['name'] 2229 varName = prefix+name+'Field' 2230 if hasSubTag(field,'required'): 2231 o.write('\t\t\t\''+name+'\': \''+field.xpath('required')[0].text+'\',\n') 2232 else: 2233 o.write('\t\t\t\''+name+'\': options.'+varName+',\n') 2234 o.write('\t\t}\n') 2235 2236 encodeFunction = 'encode' 2237 if prefixName: encodeFunction = msgName+'Encode' 2238 o.write(''' 2239 bits = '''+encodeFunction+'''(msgDict) 2240 if 'binary'==options.ioType: print str(bits) 2241 elif 'nmeapayload'==options.ioType: 2242 # FIX: figure out if this might be necessary at compile time 2243 print "bitLen",len(bits) 2244 bitLen=len(bits) 2245 if bitLen%6!=0: 2246 bits = bits + BitVector(size=(6 - (bitLen%6))) # Pad out to multiple of 6 2247 print "result:",binary.bitvectoais6(bits)[0] 2248 2249 2250 # FIX: Do not emit this option for the binary message payloads. Does not make sense. 2251 elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability") 2252 else: sys.exit('ERROR: unknown ioType. Help!') 2253 ''') 2254 2255 2256 ############################## 2257 # decode all 2258 ############################## 2259 decodeFunction = 'decode' 2260 printFields='printFields' 2261 if prefixName: 2262 decodeFunction = msgName+'Decode' 2263 printFields = msgName+'PrintFields' 2264 o.write(''' 2265 2266 if options.sqlCreate: 2267 sqlCreateStr(outfile,options.fieldList,dbType=options.dbType) 2268 2269 if options.printCsvfieldList: 2270 # Make a csv separated list of fields that will be displayed for csv 2271 if None == options.fieldList: options.fieldList = fieldList 2272 import StringIO 2273 buf = StringIO.StringIO() 2274 for field in options.fieldList: 2275 buf.write(field+',') 2276 result = buf.getvalue() 2277 if result[-1] == ',': print result[:-1] 2278 else: print result 2279 2280 if options.doDecode: 2281 for msg in args: 2282 bv = None 2283 2284 if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'): 2285 # Found nmea 2286 # FIX: do checksum 2287 bv = binary.ais6tobitvec(msg.split(',')[5]) 2288 else: # either binary or nmeapayload... expect mostly nmeapayloads 2289 # assumes that an all 0 and 1 string can not be a nmeapayload 2290 binaryMsg=True 2291 for c in msg: 2292 if c not in ('0','1'): 2293 binaryMsg=False 2294 break 2295 if binaryMsg: 2296 bv = BitVector(bitstring=msg) 2297 else: # nmeapayload 2298 bv = binary.ais6tobitvec(msg) 2299 2300 '''+printFields+'''('''+decodeFunction+'''(bv) 2301 ,out=outfile 2302 ,format=options.outputType 2303 ,fieldList=options.fieldList 2304 ,dbType=options.dbType 2305 ) 2306 2307 ''')
2308 2309 2310 ###################################################################### 2311 if __name__=='__main__': 2312 from optparse import OptionParser 2313 parser = OptionParser(usage="%prog [options]", 2314 version="%prog "+__version__) 2315 2316 parser.add_option('-o','--output',dest='outputFileName',default=None, 2317 help='Name of the python file to write') 2318 # help='Name of the python file to write [default: stdout]') 2319 2320 parser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None, 2321 help='XML definition file for the msg to use') 2322 # help='XML definition file for the msg to use [default: stdin]') 2323 2324 # parser.add_option('-m','--message',dest='message',default=None, 2325 # help='Which message to write code for [default: all]') 2326 2327 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 2328 help='run the documentation tests') 2329 2330 parser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true', 2331 help='put the field name in front of all function names.' 2332 +' Allows multiple messages in one file') 2333 2334 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 2335 help='run the tests run in verbose mode') 2336 2337 (options,args) = parser.parse_args() 2338 2339 success=True 2340 2341 if options.doctest: 2342 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 2343 argvOrig = sys.argv 2344 sys.argv= [sys.argv[0]] 2345 if options.verbose: sys.argv.append('-v') 2346 import doctest 2347 numfail,numtests=doctest.testmod() 2348 if numfail==0: print 'ok' 2349 else: 2350 print 'FAILED' 2351 success=False 2352 sys.argv = argvOrig # Restore the original args 2353 del argvOrig # hide from epydoc 2354 sys.exit() # FIX: Will this exit success? 2355 2356 #infile=sys.stdin 2357 #if options.xmlFileName: infile = file(options.xmlFileName,'r') 2358 2359 #outfile=sys.stdout 2360 #if options.outputFile: outfile = file(options.xmlDefinition,'w') 2361 2362 # FIX: down the road, it would be good to allow either filenames of std{in/out} 2363 2364 if None==options.xmlFileName: 2365 sys.exit('ERROR: must specify an xml definition file.') 2366 if None==options.outputFileName: 2367 sys.exit('ERROR: must specify an python file to write to.') 2368 generatePython(options.xmlFileName,options.outputFileName,prefixName=options.prefix, verbose=options.verbose) 2369 2370 print '\nrecommend running pychecker like this:' 2371 print ' pychecker -q',options.outputFileName 2372