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