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