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,verbose=verbose)
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
751
752
753
754
755
756 o.write( (''' // /hline
757 /end{tabular}
758 /caption{AIS message number '''+msgET.attrib['aismsgnum']+': '+msgET.xpath('description')[0].text.replace('\n',' ')+'''}
759 /label{tab:'''+msgET.attrib['name']+'''}
760 /end{table}
761 \'\'\')
762 ''').replace('/','\\\\'))
763
764
765
766
767
768
769 -def buildSQL(o,msgET, verbose=False, prefixName=False):
770 '''
771 Write SQL code
772
773 @param o: open file where resulting code will be written
774 @param msgET: Element Tree starting at a message node
775 @param verbose: talk lots in the process
776 @param prefixName: set to a string to have the commands prefixed by that character.
777 '''
778 assert(msgET.tag=='message')
779 msgName = msgET.attrib['name']
780 print 'Generating SQL commands ...', msgName
781
782 createFuncName = 'sqlCreate'
783 if prefixName: createFuncName = msgName+'SqlCreate'
784 insertFuncName = 'sqlInsert'
785 if prefixName: insertFuncName = msgName+'SqlInsert'
786
787 o.write('''######################################################################
788 # SQL SUPPORT
789 ######################################################################
790
791 ''')
792
793 o.write('dbTableName=\''+msgName+'\'\n')
794 o.write('\'Database table name\'\n\n')
795
796
797 o.write('def '+createFuncName+'''Str(outfile=sys.stdout, fields=None, extraFields=None
798 ,addCoastGuardFields=True
799 ,dbType='postgres'
800 ):
801 \'\'\'
802 Return the SQL CREATE command for this message type
803 @param outfile: file like object to print to.
804 @param fields: which fields to put in the create. Defaults to all.
805 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields
806 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
807 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
808 @type addCoastGuardFields: bool
809 @return: sql create string
810 @rtype: str
811
812 @see: sqlCreate
813 \'\'\'
814 # FIX: should this sqlCreate be the same as in LaTeX (createFuncName) rather than hard coded?
815 outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields,dbType=dbType)))
816
817 ''')
818
819 o.write('def '+createFuncName+'''(fields=None, extraFields=None, addCoastGuardFields=True, dbType='postgres'):
820 \'\'\'
821 Return the sqlhelp object to create the table.
822
823 @param fields: which fields to put in the create. Defaults to all.
824 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields
825 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
826 @type addCoastGuardFields: bool
827 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
828 @return: An object that can be used to generate a return
829 @rtype: sqlhelp.create
830 \'\'\'
831 if None == fields: fields = fieldList
832 import sqlhelp
833 ''')
834
835 o.write('\tc = sqlhelp.create(\''+msgName+'\',dbType=dbType)\n')
836 o.write('\tc.addPrimaryKey()\n');
837
838 for field in msgET.xpath('field'):
839 fieldName = field.attrib['name']
840 fieldType = field.attrib['type']
841
842 postgis = False
843 if 'postgisType' in field.attrib:
844 postgis=True
845 o.write('\tif dbType != \'postgres\':\n\t')
846 o.write('\tif \''+fieldName+'\' in fields: c.add')
847
848 if fieldType in ('int','uint'): o.write('Int (\''+fieldName+'\')')
849 elif fieldType in ('float' ): o.write('Real(\''+fieldName+'\')')
850 elif fieldType == 'bool': o.write('Bool(\''+fieldName+'\')')
851 elif fieldType in ('decimal','udecimal'):
852 if not hasSubTag(field,'decimalplaces'):
853 print '\n ** ERROR: missing decimalplaces field for ',fieldName,'\n'
854 assert (False)
855 scaleSQL = int(field.xpath('decimalplaces')[0].text)
856 numBits = int(field.attrib['numberofbits'])
857 if not hasSubTag(field,'scale'):
858 print 'ERROR: missing required <scale> for',fieldName,'of type',fieldType
859 scaleVal = float(field.xpath('scale')[0].text)
860 precision = scaleSQL + len(str(int((2**numBits)/scaleVal)))
861
862 o.write('Decimal(\''+fieldName+'\','+str(precision)+','+str(scaleSQL)+')')
863 elif fieldType == 'binary':
864 numBits = int(field.attrib['numberofbits'])
865 if -1 == numBits: numBits=1024
866 o.write('BitVarying(\''+fieldName+'\','+str(numBits)+')')
867 elif fieldType == 'aisstr6':
868 arrayLength = int(field.attrib['arraylength'])
869 o.write('VarChar(\''+fieldName+'\','+str(arrayLength)+')')
870 else:
871 print '\n\n *** Unknown type in SQL code generation for field',fieldName+':',fieldType,'\n\n'
872 assert(False)
873 o.write('\n')
874
875 o.write('''
876 if addCoastGuardFields:
877 # c.addInt('cg_rssi') # Relative signal strength indicator
878 # c.addInt('cg_d') # dBm receive strength
879 # c.addInt('cg_T') # Receive timestamp from the AIS equipment
880 # c.addInt('cg_S') # Slot received in
881 # c.addVarChar('cg_x',10) # Idonno
882 c.addVarChar('cg_r',15) # Receiver station ID - should usually be an MMSI, but sometimes is a string
883 c.addInt('cg_sec') # UTC seconds since the epoch
884
885 c.addTimestamp('cg_timestamp') # UTC decoded cg_sec - not actually in the data stream
886 ''')
887
888
889
890
891
892 postgisFields = msgET.xpath('field[@postgisType]')
893 if len(postgisFields)<1:
894 if verbose: print 'No postgis fields'
895 else:
896 o.write('\n\tif dbType == \'postgres\':\n')
897 finished=[]
898 print 'processing postgis fields for create:'
899 for field in postgisFields:
900
901 pgName = field.attrib['postgisName']
902 if pgName in finished:
903 print 'already handled',pgName
904 continue
905 finished.append(pgName)
906 pgType = field.attrib['postgisType']
907
908 components = msgET.xpath('field[@postgisName=\''+pgName+'\']')
909
910
911
912 o.write('\t\t#--- EPSG 4326 : WGS 84\n')
913
914 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''')
915
916 o.write('\t\tc.addPostGIS(\''+pgName+'\',\''+pgType+'\','+str(len(components))+',SRID=4326);\n')
917
918 o.write('\n\treturn c\n\n')
919
920
921
922 o.write('''def '''+insertFuncName+'''Str(params, outfile=sys.stdout, extraParams=None, dbType='postgres'):
923 \'\'\'
924 Return the SQL INSERT command for this message type
925 @param params: dictionary of values keyed by field name
926 @param outfile: file like object to print to.
927 @param extraParams: A sequence of tuples containing (name,sql type) for additional fields
928 @return: sql create string
929 @rtype: str
930
931 @see: sqlCreate
932 \'\'\'
933 outfile.write(str(sqlInsert(params,extraParams,dbType=dbType)))
934
935
936 ''')
937
938
939 o.write('''def '''+insertFuncName+'''(params,extraParams=None,dbType='postgres'):
940 \'\'\'
941 Give the SQL INSERT statement
942 @param params: dict keyed by field name of values
943 @param extraParams: any extra fields that you have created beyond the normal ais message fields
944 @rtype: sqlhelp.insert
945 @return: insert class instance
946 @todo: allow optional type checking of params?
947 @warning: this will take invalid keys happily and do what???
948 \'\'\'
949 import sqlhelp
950 i = sqlhelp.insert(\'''' + msgName + '''\',dbType=dbType)
951
952 if dbType=='postgres':
953 finished = []
954 for key in params:
955 if key in finished:
956 continue
957
958 if key not in toPgFields and key not in fromPgFields:
959 if type(params[key])==Decimal: i.add(key,float(params[key]))
960 else: i.add(key,params[key])
961 else:
962 if key in fromPgFields:
963 val = params[key]
964 # Had better be a WKT type like POINT(-88.1 30.321)
965 i.addPostGIS(key,val)
966 finished.append(key)
967 else:
968 # Need to construct the type.
969 pgName = toPgFields[key]
970 #valStr='GeomFromText(\\''+pgTypes[pgName]+'('
971 valStr=pgTypes[pgName]+'('
972 vals = []
973 for nonPgKey in fromPgFields[pgName]:
974 vals.append(str(params[nonPgKey]))
975 finished.append(nonPgKey)
976 valStr+=' '.join(vals)+')'
977 i.addPostGIS(pgName,valStr)
978 else:
979 for key in params:
980 if type(params[key])==Decimal: i.add(key,float(params[key]))
981 else: i.add(key,params[key])
982
983 if None != extraParams:
984 for key in extraParams:
985 i.add(key,extraParams[key])
986
987 return i
988 ''')
989
990
991
992
993
994
995
996
997 -def buildLUT(o,msgET, verbose=False, prefixName=False):
998 '''
999 Write lookup tables for enumerated types (uint or int, maybe bool too).
1000
1001 @todo: FIX: what to do about multiple entries with the same text? Need to ban that kind of thing
1002 @todo: Make doc strings for each LUT.
1003
1004 @param o: open file where resulting code will be written
1005 @param msgET: Element Tree starting at a message node
1006 '''
1007 assert(msgET.tag=='message')
1008 msgname = msgET.attrib['name']
1009
1010 print 'Generating lookup tables ...',msgname
1011
1012 for field in msgET.xpath('field'):
1013 name = field.attrib['name']
1014 if not hasSubTag(field,'lookuptable'): continue
1015 lut = field.xpath('lookuptable')[0]
1016
1017 lutName = name
1018 if prefixName: lutName = msgname+name.capitalize()
1019
1020 o.write(lutName+'EncodeLut = {\n')
1021 for entry in lut.xpath('entry'):
1022 o.write('\t\''+entry.text+'\':\''+entry.attrib['key']+'\',\n')
1023 o.write('\t} #'+lutName+'EncodeLut\n')
1024 o.write('\n')
1025
1026
1027
1028 o.write(lutName+'DecodeLut = {\n')
1029 for entry in lut.xpath('entry'):
1030 o.write('\t\''+entry.attrib['key']+'\':\''+entry.text+'\',\n')
1031 o.write('\t} # '+lutName+'EncodeLut\n')
1032 o.write('\n')
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1043 '''
1044 Build the encoder for boolean variables
1045 @type o: file like obj
1046 @param o: where write the code
1047 @type name: str
1048 @param name: field name
1049 @type type: str
1050 @param type: bool, etc.
1051 @type numbits: int = 1
1052 @param numbits: How many bits per unit datum (must be 1 for bools)
1053 @type required: bool or None
1054 @param required: If not None, then the value must be set to this.
1055 @type arraylen: int >= 1
1056 @param arraylen: many bools will there be? FIX: handle variable
1057 @type unavailable: bool or None
1058 @param unavailable: the default value to use if none given (if not None)
1059 @return: None
1060 '''
1061
1062 if verbose: print 'bool encode',name,': unvail=',unavailable
1063
1064 assert type.lower()=='bool'
1065 assert numbits==1
1066 if arraylen != 1: assert False
1067 if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n')
1068 if None != required:
1069 assert type(required)==bool
1070 if required: o.write('\t\tbvList.append(TrueBV)\n')
1071 else: o.write('\t\tbvList.append(FalseBV)\n')
1072 if verbose: o.write('\n')
1073 return
1074
1075 if None==unavailable:
1076 o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n')
1077 o.write('\telse: bvList.append(FalseBV)\n')
1078 else:
1079 assert type(unavailable)==bool
1080 o.write("\tif '"+name+"' in params:\n")
1081 o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n')
1082 o.write('\t\telse: bvList.append(FalseBV)\n')
1083 o.write('\telse:\n')
1084 if unavailable: o.write('\t\tbvList.append(TrueBV)\n')
1085 else: o.write('\t\tbvList.append(FalseBV)\n')
1086 if verbose: o.write('\n')
1087
1088 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1089 '''
1090 Build the encoder for unsigned integer variables
1091
1092 @type o: file like obj
1093 @param o: where write the code
1094 @type name: str
1095 @param name: field name
1096 @type type: str
1097 @param type: uint, bool, etc.
1098 @type numbits: int >= 1
1099 @param numbits: How many bits per unit datum (must be 1..32)
1100 @type required: bool or None
1101 @param required: If not None, then the value must be set to this.
1102 @type arraylen: int >= 1
1103 @param arraylen: many unsigned ints will there be? FIX: handle variable
1104 @type unavailable: bool or None
1105 @param unavailable: the default value to use if none given (if not None)
1106 @return: None
1107 '''
1108 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1109
1110 assert type=='uint'
1111 assert numbits>=1 and numbits<=32
1112 if arraylen != 1: assert False
1113 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1114
1115 if None != required:
1116 if verbose: print ' required:',required
1117 required=int(required)
1118 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n')
1119 if verbose: o.write('\n')
1120 return
1121
1122 if None==unavailable:
1123 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n')
1124 else:
1125
1126 int(unavailable)
1127 o.write("\tif '"+name+"' in params:\n")
1128 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n')
1129 o.write('\telse:\n')
1130 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n')
1131
1132 if verbose: o.write('\n')
1133
1134
1135 -def encodeFloat(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1136 '''
1137 Build the encoder for IEEE float variables
1138
1139 @type o: file like obj
1140 @param o: where write the code
1141 @type name: str
1142 @param name: field name
1143 @type fieldType: str
1144 @param fieldType: uint, bool, etc.
1145 @type numbits: int >= 1
1146 @param numbits: How many bits per unit datum (must be 1..32)
1147 @type required: bool or None
1148 @param required: If not None, then the value must be set to this.
1149 @type arraylen: int >= 1
1150 @param arraylen: many unsigned ints will there be? FIX: handle variable
1151 @type unavailable: bool or None
1152 @param unavailable: the default value to use if none given (if not None)
1153 @return: None
1154 '''
1155 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable
1156
1157 assert numbits==32
1158 if arraylen != 1: assert False
1159 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n')
1160
1161 if None != required:
1162 if verbose: print ' required:',required
1163 required=int(required)
1164 o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n')
1165 if verbose: o.write('\n')
1166 return
1167
1168 if None==unavailable:
1169 o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n')
1170 else:
1171 int(unavailable)
1172 o.write("\tif '"+name+"' in params:\n")
1173 o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n')
1174 o.write('\telse:\n')
1175 o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n')
1176
1177 if verbose: o.write('\n')
1178
1179
1180 -def encodeAisstr6(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1181 '''
1182 Build the encoder for aisstr6 variables. Generally are arrays.
1183 @bug: do we need to optionally check for a valid string?
1184
1185 @type o: file like obj
1186 @param o: where write the code
1187 @type name: str
1188 @param name: field name
1189 @type fieldType: str
1190 @param fieldType: uint, bool, etc.
1191 @type numbits: int >= 1
1192 @param numbits: How many bits per unit datum (must be 1..32)
1193 @type required: bool or None
1194 @param required: If not None, then the value must be set to this.
1195 @type arraylen: int >= 1
1196 @param arraylen: many unsigned ints will there be? FIX: handle variable
1197 @type unavailable: bool or None
1198 @param unavailable: the default value to use if none given (if not None)
1199 @return: None
1200 '''
1201 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable
1202 totLen = str(numbits*arraylen)
1203 assert numbits==6
1204 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n')
1205
1206 if None != required:
1207 if verbose: print ' required:',required
1208 required=int(required)
1209 o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+totLen+'))\n')
1210 if verbose: o.write('\n')
1211 return
1212
1213 if None==unavailable:
1214 o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n')
1215 else:
1216 o.write("\tif '"+name+"' in params:\n")
1217 o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n')
1218 o.write('\telse:\n')
1219 o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+totLen+'))\n')
1220
1221 if verbose: o.write('\n')
1222
1223
1224 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1225 '''
1226 Build the encoder for signed integer variables
1227
1228 @type o: file like obj
1229 @param o: where write the code
1230 @type name: str
1231 @param name: field name
1232 @type type: str
1233 @param type: uint, bool, etc.
1234 @type numbits: int >= 1
1235 @param numbits: How many bits per unit datum (must be 1..32)
1236 @type required: bool or None
1237 @param required: If not None, then the value must be set to this.
1238 @type arraylen: int >= 1
1239 @param arraylen: many signed ints will there be? FIX: handle variable
1240 @type unavailable: number or None
1241 @param unavailable: the default value to use if none given (if not None)
1242 @return: None
1243 '''
1244 if verbose: print ' encodeInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1245
1246 assert numbits>=1 and numbits<=32
1247 if arraylen != 1: assert False
1248 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1249
1250 if None != required:
1251 if verbose: print ' required:',required
1252 required=int(required)
1253 o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n')
1254 if verbose: o.write('\n')
1255 return
1256
1257
1258 if None==unavailable:
1259 o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n')
1260 else:
1261
1262 int(unavailable)
1263 o.write("\tif '"+name+"' in params:\n")
1264 o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n')
1265 o.write('\telse:\n')
1266 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n')
1267
1268 if verbose: o.write('\n')
1269
1270
1271
1272
1273 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None, offset=None):
1274 '''
1275 Build the encoder for signed decimal variables
1276
1277 @type o: file like obj
1278 @param o: where write the code
1279 @type name: str
1280 @param name: field name
1281 @type type: str
1282 @param type: decimal
1283 @type numbits: int >= 1
1284 @param numbits: How many bits per unit datum (must be 1..32)
1285 @type required: bool or None
1286 @param required: If not None, then the value must be set to this.
1287 @type arraylen: int >= 1
1288 @param arraylen: many decimals will there be? FIX: handle variable
1289 @type unavailable: Decimal or None
1290 @param unavailable: the default value to use if none given (if not None)
1291 @return: None
1292 '''
1293 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1294
1295 assert numbits>=1 and numbits<=32
1296 if arraylen != 1: assert False
1297 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1298
1299
1300 if None == scale:
1301 print 'WARNING: if you are not scaling, then you probably want to use an int instead!'
1302 print 'Beware canadians bearing travel videos'
1303 scale='1'
1304
1305 if None != required:
1306 if verbose: print ' required:',required
1307 required=int(required)
1308 o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n')
1309 if verbose: o.write('\n')
1310 return
1311
1312 offsetStr=''
1313 if None != offset:
1314 offsetStr='-('+offset+')'
1315
1316
1317 if None==unavailable:
1318 o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')),'+str(numbits)+'))\n')
1319 else:
1320 o.write("\tif '"+name+"' in params:\n")
1321 o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')),'+str(numbits)+'))\n')
1322 o.write('\telse:\n')
1323 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n')
1324
1325 if verbose: o.write('\n')
1326
1327
1328
1329 -def encodeUDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None, offset=None):
1330 '''
1331 Build the encoder for signed decimal variables
1332
1333 @type o: file like obj
1334 @param o: where write the code
1335 @type name: str
1336 @param name: field name
1337 @type type: str
1338 @param type: decimal
1339 @type numbits: int >= 1
1340 @param numbits: How many bits per unit datum (must be 1..32)
1341 @type required: bool or None
1342 @param required: If not None, then the value must be set to this.
1343 @type arraylen: int >= 1
1344 @param arraylen: many decimals will there be? FIX: handle variable
1345 @type unavailable: Decimal or None
1346 @param unavailable: the default value to use if none given (if not None)
1347 @return: None
1348 '''
1349 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1350 assert type=='udecimal'
1351 assert numbits>=1 and numbits<=32
1352 if arraylen != 1: assert False
1353 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1354
1355
1356 if None == scale:
1357 print 'WARNING: if you are not scaling, then you probably want to use an int instead!'
1358 print 'Beware canadians bearing travel videos'
1359 scale='1'
1360
1361 if None != required:
1362 if verbose: print ' required:',required
1363 required=int(required)
1364 assert(0<=required)
1365 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(int(Decimal(required)*Decimal(scale)))+'),'+str(numbits)+'))\n')
1366 if verbose: o.write('\n')
1367 return
1368
1369 offsetStr=''
1370 if None != offset:
1371 offsetStr='-('+offset+')'
1372
1373
1374 if None==unavailable:
1375 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n')
1376 else:
1377 o.write("\tif '"+name+"' in params:\n")
1378 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n')
1379 o.write('\telse:\n')
1380 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int('+str(int(Decimal(unavailable)*Decimal(scale)))+')),'+str(numbits)+'))\n')
1381
1382 if verbose: o.write('\n')
1383
1384
1385
1386 -def encodeBinary(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1387 '''
1388 Build the encoder for binary variables. This is just a pass through.
1389 This is used for the ais binary message wrapper (e.g. msg 8). Do
1390 not use it within binary messages.
1391
1392 @type o: file like obj
1393 @param o: where write the code
1394 @type name: str
1395 @param name: field name
1396 @type type: str
1397 @param type: binary
1398 @type numbits: int >= 1
1399 @param numbits: How many bits per unit datum (must be 1..1024 or so)
1400 @type required: bool or None
1401 @param required: If not None, then the value must be set to this.
1402 @type arraylen: int >= 1
1403 @param arraylen: many decimals will there be? FIX: handle variable
1404 @type unavailable: Decimal or None
1405 @param unavailable: the default value to use if none given (if not None)
1406 @return: None
1407 '''
1408 if verbose: print ' encode'+name+':',type,numbits,'Req:',required,'alen:',arraylen,unavailable
1409 assert type=='binary'
1410 assert (numbits>=1 and numbits<=1024) or numbits==-1
1411 assert (None == required)
1412 assert (None == unavailable)
1413
1414 if arraylen != 1: assert False
1415 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1416
1417
1418 o.write('\tbvList.append(params[\''+name+'\'])\n')
1419
1420 if verbose: o.write('\n')
1421
1422
1423
1424
1425
1426
1427 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1428 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1429 '''
1430 Build the decoder for boolean variables
1431
1432 @type o: file like obj
1433 @param o: where write the code
1434 @type name: str
1435 @param name: field name
1436 @type type: str
1437 @param type: uint, bool, etc.
1438 @type startindex: int
1439 @param startindex: bit that begins the bool(s)
1440 @type numbits: int = 1
1441 @param numbits: How many bits per unit datum (must be 1 for bools)
1442 @type required: bool or None
1443 @param required: If not None, then the value must be set to this.
1444 @type arraylen: int >= 1
1445 @param arraylen: many bools will there be? FIX: handle variable
1446 @type unavailable: bool or None
1447 @param unavailable: the default value to use if none given (if not None)
1448 @type bv: str
1449 @param bv: BitVector containing the incoming data
1450 @type dataDict: str
1451 @param dataDict: dictionary in which to place the results
1452 @type decodeOnly: bool
1453 @param decodeOnly: Set to true to only get the code for decoding
1454 @rtype: int
1455 @return: index one past the end of where this read
1456 '''
1457 assert(type=='bool')
1458 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1459
1460 assert numbits==1
1461 assert arraylen == 1
1462
1463 if None != required:
1464 assert type(required)==bool
1465 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1466 if required: o.write('True\n')
1467 else: o.write('False\n')
1468 if not decodeOnly: o.write('\n')
1469 return int(startindex)+int(numbits)
1470
1471 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1472 o.write('bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))')
1473 if not decodeOnly: o.write('\n')
1474
1475 return int(startindex)+int(numbits)
1476
1477
1478 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1479 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1480 '''
1481 Build the decoder for unsigned integer variables
1482
1483 @type o: file like obj
1484 @param o: where write the code
1485 @type name: str
1486 @param name: field name
1487 @type type: str
1488 @param type: uint, etc.
1489 @type startindex: int
1490 @param startindex: bit that begins the uint(s)
1491 @type numbits: int >= 1
1492 @param numbits: How many bits per unit datum
1493 @type required: int or None
1494 @param required: If not None, then the value must be set to this.
1495 @type arraylen: int >= 1
1496 @param arraylen: many ints will there be? FIX: handle variable
1497 @type unavailable: int or None
1498 @param unavailable: the default value to use if none given (if not None)
1499 @type bv: str
1500 @param bv: BitVector containing the incoming data
1501 @type dataDict: str
1502 @param dataDict: dictionary in which to place the results
1503 @type decodeOnly: bool
1504 @param decodeOnly: Set to true to only get the code for decoding
1505 @rtype: int
1506 @return: index one past the end of where this read
1507 '''
1508 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1509 if None==arraylen: arraylen=1
1510 assert arraylen == 1
1511 assert numbits>=1
1512 if not decodeOnly: verbose=False
1513
1514 if None != required:
1515 int(required)
1516 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1517 o.write(str(required))
1518 if not decodeOnly: o.write('\n')
1519 return startindex+numbits
1520
1521 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1522 o.write('int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])')
1523 if not decodeOnly: o.write('\n')
1524 if verbose: o.write('\n')
1525
1526 return startindex+numbits
1527
1528
1529 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1530 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1531 '''
1532 Build the decoder for unsigned integer variables
1533
1534 @type o: file like obj
1535 @param o: where write the code
1536 @type name: str
1537 @param name: field name
1538 @type type: str
1539 @param type: int
1540 @type startindex: int
1541 @param startindex: bit that begins the int(s)
1542 @type numbits: int >= 1
1543 @param numbits: How many bits per unit datum
1544 @type required: int or None
1545 @param required: If not None, then the value must be set to this.
1546 @type arraylen: int >= 1
1547 @param arraylen: many ints will there be? FIX: handle variable
1548 @type unavailable: int or None
1549 @param unavailable: the default value to use if none given (if not None)
1550 @type bv: str
1551 @param bv: BitVector containing the incoming data
1552 @type dataDict: str
1553 @param dataDict: dictionary in which to place the results
1554 @type decodeOnly: bool
1555 @param decodeOnly: Set to true to only get the code for decoding
1556 @rtype: int
1557 @return: index one past the end of where this read
1558 '''
1559 assert type=='int'
1560 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1561 if None==arraylen: arraylen=1
1562 end = startindex+int(numbits)*int(arraylen)
1563 assert arraylen == 1
1564 assert numbits>=1
1565
1566 if None != required:
1567 int(required)
1568 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1569 o.write(str(required))
1570 if not decodeOnly: o.write('\n')
1571 return end
1572
1573 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1574 o.write('binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])')
1575 if not decodeOnly: o.write('\n')
1576 if verbose: o.write('\n')
1577
1578 return end
1579
1580 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1581 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1582 '''
1583 Build the decoder for IEEE float variables
1584
1585 @type o: file like obj
1586 @param o: where write the code
1587 @type name: str
1588 @param name: field name
1589 @type type: str
1590 @param type: int
1591 @type startindex: int
1592 @param startindex: bit that begins the int(s)
1593 @type numbits: int >= 1
1594 @param numbits: How many bits per unit datum
1595 @type required: float or None
1596 @param required: If not None, then the value must be set to this.
1597 @type arraylen: int >= 1
1598 @param arraylen: many ints will there be? FIX: handle variable
1599 @type unavailable: float or None
1600 @param unavailable: the default value to use if none given (if not None)
1601 @type bv: str
1602 @param bv: BitVector containing the incoming data
1603 @type dataDict: str
1604 @param dataDict: dictionary in which to place the results
1605 @type decodeOnly: bool
1606 @param decodeOnly: Set to true to only get the code for decoding
1607 @rtype: int
1608 @return: index one past the end of where this read
1609 '''
1610 assert type=='float'
1611 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1612 if None==arraylen: arraylen=1
1613 end = startindex+int(numbits)*int(arraylen)
1614 assert arraylen == 1
1615 assert numbits>=1
1616
1617 if None != required:
1618 float(required)
1619 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1620 o.write(str(required))
1621 if not decodeOnly: o.write('\n')
1622 if verbose: o.write('\n')
1623 return end
1624
1625 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1626 o.write('binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])')
1627 if not decodeOnly: o.write('\n')
1628
1629 return end
1630
1631
1632 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1633 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1634 '''
1635 Build the decoder for aisstr6 variables. Generally arrays.
1636 @bug: FIX: validate strings??
1637 @type o: file like obj
1638 @param o: where write the code
1639 @type name: str
1640 @param name: field name
1641 @type type: str
1642 @param type: 'aisstr6'
1643 @type startindex: int
1644 @param startindex: bit that begins the int(s)
1645 @type numbits: int >= 1
1646 @param numbits: How many bits per unit datum
1647 @type required: restricted str or None
1648 @param required: If not None, then the value must be set to this.
1649 @type arraylen: int >= 1
1650 @param arraylen: many ints will there be? FIX: handle variable
1651 @type unavailable: restricted str or None
1652 @param unavailable: the default value to use if none given (if not None)
1653 @type bv: str
1654 @param bv: BitVector containing the incoming data
1655 @type dataDict: str
1656 @param dataDict: dictionary in which to place the results
1657 @type decodeOnly: bool
1658 @param decodeOnly: Set to true to only get the code for decoding
1659 @rtype: int
1660 @return: index one past the end of where this read
1661 '''
1662 assert type=='aisstr6'
1663 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1664 if None==arraylen: arraylen=1
1665 end = startindex+int(numbits)*int(arraylen)
1666 assert arraylen >= 1
1667 assert numbits>=1
1668
1669 if None != required:
1670 float(required)
1671 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1672 o.write(required)
1673 if not decodeOnly: o.write('\n')
1674 return end
1675
1676 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1677 o.write('aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])')
1678 if not decodeOnly: o.write('\n')
1679
1680 return end
1681
1682
1683 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1684 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False,offset=None):
1685 '''
1686 Build the decoder for signed decimal variables
1687
1688 @type o: file like obj
1689 @param o: where write the code
1690 @type name: str
1691 @param name: field name
1692 @type type: str
1693 @param type: 'decimal'
1694 @type startindex: int
1695 @param startindex: bit that begins the int(s)
1696 @type numbits: int >= 1
1697 @param numbits: How many bits per unit datum
1698 @type required: Decimal or None
1699 @param required: If not None, then the value must be set to this.
1700 @type arraylen: int >= 1
1701 @param arraylen: many ints will there be? FIX: handle variable
1702 @type unavailable: Decimal or None
1703 @param unavailable: the default value to use if none given (if not None)
1704 @type bv: str
1705 @param bv: BitVector containing the incoming data
1706 @type dataDict: str
1707 @param dataDict: dictionary in which to place the results
1708 @type decodeOnly: bool
1709 @param decodeOnly: Set to true to only get the code for decoding
1710 @rtype: int
1711 @return: index one past the end of where this read
1712 '''
1713 assert type=='decimal'
1714 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1715 if None==arraylen: arraylen=1
1716 end = startindex+int(numbits)*int(arraylen)
1717 assert arraylen == 1
1718 assert numbits>=1 and numbits <= 32
1719
1720 if None == scale: scale='1'
1721
1722 if None != required:
1723 Decimal(required)
1724 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1725 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')')
1726 if not decodeOnly: o.write('\n')
1727 return end
1728
1729 offsetStr=''
1730 if offset != None:
1731 offsetStr += '+Decimal(\''+offset+'\')'
1732
1733 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1734 o.write('Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')'+offsetStr+'')
1735 if not decodeOnly: o.write('\n')
1736
1737 return end
1738
1739
1740
1741
1742 -def decodeUDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1743 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False,offset=None):
1744 '''
1745 Build the decoder for unsigned decimal variables
1746
1747 @type o: file like obj
1748 @param o: where write the code
1749 @type name: str
1750 @param name: field name
1751 @type type: str
1752 @param type: 'udecimal'
1753 @type startindex: int
1754 @param startindex: bit that begins the int(s)
1755 @type numbits: int >= 1
1756 @param numbits: How many bits per unit datum
1757 @type required: Decimal or None
1758 @param required: If not None, then the value must be set to this.
1759 @type arraylen: int >= 1
1760 @param arraylen: many ints will there be? FIX: handle variable
1761 @type unavailable: Decimal or None
1762 @param unavailable: the default value to use if none given (if not None)
1763 @type bv: str
1764 @param bv: BitVector containing the incoming data
1765 @type dataDict: str
1766 @param dataDict: dictionary in which to place the results
1767 @type decodeOnly: bool
1768 @param decodeOnly: Set to true to only get the code for decoding
1769 @rtype: int
1770 @return: index one past the end of where this read
1771 '''
1772 assert type=='udecimal'
1773 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1774 if None==arraylen: arraylen=1
1775 end = startindex+int(numbits)*int(arraylen)
1776 assert arraylen == 1
1777 assert numbits>=1 and numbits <= 32
1778
1779 if None == scale: scale='1'
1780
1781 if None != required:
1782 assert (Decimal(required)>=0.)
1783 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1784 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')')
1785 if not decodeOnly: o.write('\n')
1786 return end
1787
1788 offsetStr=''
1789 if offset != None:
1790 offsetStr += '+Decimal(\''+offset+'\')'
1791
1792 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1793 o.write('Decimal(int('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')'+offsetStr+'')
1794 if not decodeOnly: o.write('\n')
1795
1796 return end
1797
1798
1799 -def decodeBinary(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1800 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1801 '''
1802 Build the decoder for unsigned decimal variables
1803
1804 @type o: file like obj
1805 @param o: where write the code
1806 @type name: str
1807 @param name: field name
1808 @type type: str
1809 @param type: 'udecimal'
1810 @type startindex: int
1811 @param startindex: bit that begins the int(s)
1812 @type numbits: int >= 1
1813 @param numbits: How many bits per unit datum. If -1, then read to the end of the message
1814 @type required: Decimal or None
1815 @param required: If not None, then the value must be set to this.
1816 @type arraylen: int >= 1
1817 @param arraylen: many ints will there be? FIX: handle variable
1818 @type unavailable: Decimal or None
1819 @param unavailable: the default value to use if none given (if not None)
1820 @type bv: str
1821 @param bv: BitVector containing the incoming data
1822 @type dataDict: str
1823 @param dataDict: dictionary in which to place the results
1824 @type decodeOnly: bool
1825 @param decodeOnly: Set to true to only get the code for decoding
1826 @rtype: int
1827 @return: index one past the end of where this read
1828 '''
1829 assert type=='binary'
1830 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1831 if None==arraylen: arraylen=1
1832 end = startindex+int(numbits)*int(arraylen)
1833 assert arraylen == 1
1834 assert (numbits>=1 and numbits <= 1024) or -1==numbits
1835
1836
1837
1838 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1839 o.write(bv+'['+str(startindex)+':')
1840 if int(numbits) != -1: o.write(str(end))
1841 o.write(']')
1842 if not decodeOnly: o.write('\n')
1843
1844 return end
1845
1846
1847
1848
1849
1850
1851
1852
1853
1855 '''Scrape the testvalues to make a basic param
1856
1857 @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies?
1858 '''
1859 name = msgET.attrib['name']
1860
1861 if prefixName: o.write('def '+name+'TestParams():\n')
1862 else: o.write('def testParams():\n')
1863 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")
1864 o.write('\tparams = {}\n')
1865 for field in msgET.xpath('field'):
1866 name = field.attrib['name']
1867 type = field.attrib['type']
1868 if verbose: print 'buildTestParamFunc ...',name,type
1869 val = None
1870 if hasSubTag(field,'testvalue') and hasSubTag(field,'required'):
1871 print 'ERROR: can not have both test value and required tags in the same field'
1872 assert(False)
1873 if hasSubTag(field,'testvalue'):
1874 val = field.xpath('testvalue')[0].text
1875 else:
1876 if not hasSubTag(field,'required'):
1877 sys.exit("ERROR: missing required or testvalue for field: "+name)
1878 val = field.xpath('required')[0].text
1879 if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val
1880 o.write('\tparams[\''+name+'\'] = ')
1881 if type=='bool':
1882 if val=='1' or val.lower=='true': val = 'True'
1883 else: val = 'False'
1884 o.write(val)
1885 elif type in ('uint','int','float'):
1886 o.write(val)
1887 elif type in ('decimal','udecimal'):
1888 o.write('Decimal(\''+val+'\')')
1889
1890 elif type in ('aisstr6'):
1891 o.write('\''+val+'\'')
1892 elif type in ('binary'):
1893 o.write('BitVector(bitstring=\''+val+'\')')
1894 else:
1895 print 'ERROR: type not handled ...',type,' (found in the ',name,' field). Time to buy more coffee'
1896 suggestType(name,type)
1897 assert(False)
1898
1899 o.write('\n')
1900
1901
1902 o.write('\n\treturn params\n\n')
1903
1905 '''
1906 Write the unittests for a message
1907
1908 @param o: open file where resulting code will be written
1909 @param msgET: Element Tree starting at a message node
1910 '''
1911 assert(msgET.tag=='message')
1912 name = msgET.attrib['name']
1913
1914 buildTestParamFunc(o,msgET, prefixName=prefixName)
1915
1916 o.write('class Test'+name+'(unittest.TestCase):\n')
1917 o.write("\t'''Use testvalue tag text from each type to build test case the "+name+" message'''\n")
1918 o.write('\tdef testEncodeDecode(self):\n\n')
1919 if prefixName:
1920 o.write('\t\tparams = '+name+'TestParams()\n')
1921 o.write('\t\tbits = '+name+'Encode(params)\n')
1922 o.write('\t\tr = '+name+'Decode(bits)\n\n')
1923 else:
1924 o.write('\t\tparams = testParams()\n')
1925 o.write('\t\tbits = encode(params)\n')
1926 o.write('\t\tr = decode(bits)\n\n')
1927
1928 o.write('\t\t# Check that each parameter came through ok.\n')
1929 for field in msgET.xpath('field'):
1930 name = field.attrib['name']
1931 type = field.attrib['type']
1932 if type in ('bool','uint','int','aisstr6','binary'):
1933 o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n')
1934 else:
1935
1936
1937 places = '3'
1938 if hasSubTag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text
1939 o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n')
1940
1941
1942
1943 -def buildEncode(o,msgET, verbose=False, prefixName=False):
1944 '''
1945 Write the encoder/decoder for a message
1946
1947 http://jaynes.colorado.edu/PythonIdioms.html
1948
1949 @param o: open file where resulting code will be written
1950 @param msgET: Element Tree starting at a message node
1951 @todo: handle ais message 20 optional. very troublesome
1952 '''
1953 assert(msgET.tag=='message')
1954 name = msgET.attrib['name']
1955
1956 print 'Generating encoder ...',name
1957 funcName = 'encode'
1958 if prefixName: funcName = name+'Encode'
1959 o.write('def '+funcName+'(params, validate=False):\n')
1960
1961
1962
1963 o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg "+name+".\n\n")
1964 o.write('\tFields in params:\n')
1965 for field in msgET.xpath('field'):
1966 if verbose:
1967 print field.tag,field.attrib['name']
1968 print field.attrib
1969 desc = field[0].text.replace('\n',' ')
1970 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc)
1971 if len(field.xpath("required")) == 1:
1972 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")')
1973
1974 o.write('\n')
1975 o.write('\t@param params: Dictionary of field names/values. Throws a ValueError exception if required is missing\n')
1976 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n')
1977
1978 o.write("\t@rtype: BitVector\n")
1979 o.write("\t@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8\n")
1980 o.write("\t@note: The returned bits may not be 6 bit aligned. It is up to you to pad out the bits.\n")
1981 o.write("\t'''\n\n")
1982
1983
1984
1985
1986 o.write('\tbvList = []\n')
1987
1988 if verbose: print 'number of fields = ', len(msgET.xpath('field'))
1989
1990 dynamicArrays = False
1991
1992 for field in msgET.xpath('field'):
1993 name = field.attrib['name']
1994 type = field.attrib['type']
1995 numbits = int(field.attrib['numberofbits'])
1996 required = None;
1997 if hasSubTag(field,'required'):
1998 required = field.xpath('required')[0].text
1999 unavailable=None;
2000 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
2001 arraylen=1
2002 if 'arraylength' in field.attrib:
2003 arraylen=int(field.attrib['arraylength'])
2004 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )'
2005 else:
2006 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')'
2007
2008 if type=='bool' : encodeBool (o,name,type,numbits,required,arraylen,unavailable)
2009 elif type=='uint' : encodeUInt (o,name,type,numbits,required,arraylen,unavailable)
2010 elif type=='int' : encodeInt (o,name,type,numbits,required,arraylen,unavailable)
2011 elif type=='float' : encodeFloat (o,name,type,numbits,required,arraylen,unavailable)
2012 elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable)
2013 elif type=='decimal':
2014 scale = None
2015 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
2016 offset = None
2017 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text
2018 encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale, offset=offset)
2019 elif type=='udecimal':
2020 scale = None
2021 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
2022 offset = None
2023 if hasSubTag(field,'offset'):
2024 offset = field.xpath('offset')[0].text
2025 encodeUDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale,offset=offset)
2026 elif type=='binary': encodeBinary(o,name,type,numbits,required,arraylen,unavailable)
2027 else:
2028 print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type
2029 suggestType (name,type)
2030 assert False
2031
2032
2033
2034
2035
2036
2037 o.write('\n\treturn binary.joinBV(bvList)\n\n')
2038
2039
2040
2041
2042
2043
2044
2046
2047 '''
2048 Write the decoder for a message
2049
2050 @param o: open file where resulting code will be written
2051 @type msgET: elementtree
2052 @param prefixName: if True, put the name of the message on the functions.
2053 @param msgET: Element Tree starting at a message node
2054 @return: None
2055
2056 @todo: FIX: doc strings for each decode!
2057 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload
2058 @todo: May want to take a dictionary of already decoded fields to speed things that need prior info
2059 for things like variable length arrays
2060 '''
2061
2062 assert(msgET.tag=='message')
2063 name = msgET.attrib['name']
2064
2065 print 'Generating partial decode functions ...',name
2066
2067 baseName = name+'Decode'
2068 if not prefixName: baseName = 'decode'
2069
2070 startindex = 0
2071
2072 for field in msgET.xpath('field'):
2073 name = field.attrib['name']
2074 type = field.attrib['type']
2075
2076 o.write('def '+baseName+name+'(bv, validate=False):\n')
2077
2078
2079 o.write('\treturn ')
2080
2081 numbits = int(field.attrib['numberofbits'])
2082 required = None;
2083 if hasSubTag(field,'required'):
2084 required = field.xpath('required')[0].text
2085 unavailable=None;
2086 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
2087 arraylen=1
2088 if 'arraylength' in field.attrib:
2089 arraylen=int(field.attrib['arraylength'])
2090 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )'
2091
2092 assert None!=startindex
2093 if verbose: print 'startindex',startindex
2094 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
2095 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
2096 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
2097 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
2098 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
2099 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
2100
2101 elif type=='decimal':
2102 scale = None
2103 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
2104 offset = None
2105 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text
2106 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True,offset=offset)
2107 elif type=='udecimal':
2108 scale = None
2109 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
2110 offset = None
2111 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text
2112 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True,offset=offset)
2113
2114 else:
2115 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type
2116 suggestType (name,type)
2117 assert False
2118
2119
2120 o.write('\n\n')
2121
2122
2123
2124
2125
2126 -def buildDecode(o,msgET, verbose=False, prefixName=False):
2127 '''
2128 Write the decoder for a message
2129
2130 @param o: open file where resulting code will be written
2131 @type msgET: elementtree
2132 @param msgET: Element Tree starting at a message node
2133 @return: None
2134
2135 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload
2136 '''
2137
2138 assert(msgET.tag=='message')
2139 name = msgET.attrib['name']
2140
2141 print 'Generating decoder ...',name
2142 funcName = 'decode'
2143 if prefixName: funcName = name+'Decode'
2144 o.write('def '+funcName+'(bv, validate=False):\n')
2145
2146
2147
2148 o.write("\t'''Unpack a "+name+" message \n\n")
2149 o.write('\tFields in params:\n')
2150 for field in msgET.xpath('field'):
2151 desc = field[0].text.replace('\n',' ')
2152 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc)
2153 if len(field.xpath("required")) == 1:
2154 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")')
2155
2156 o.write('\n')
2157 o.write('\t@type bv: BitVector\n')
2158 o.write('\t@param bv: Bits defining a message\n')
2159 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n')
2160
2161 o.write("\t@rtype: dict\n")
2162 o.write("\t@return: params\n")
2163 o.write("\t'''\n\n")
2164
2165
2166 o.write('\t#Would be nice to check the bit count here..\n')
2167 o.write('\t#if validate:\n')
2168 o.write('\t#\tassert (len(bv)==FIX: SOME NUMBER)\n')
2169
2170
2171
2172
2173
2174 o.write('\tr = {}\n')
2175
2176
2177 if verbose: print 'number of fields = ', len(msgET.xpath('field'))
2178
2179 dynamicArrays = False
2180
2181 startindex = 0
2182
2183 for field in msgET.xpath('field'):
2184 name = field.attrib['name']
2185 type = field.attrib['type']
2186 numbits = int(field.attrib['numberofbits'])
2187 required = None;
2188 if hasSubTag(field,'required'):
2189 required = field.xpath('required')[0].text
2190 unavailable=None;
2191 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
2192 arraylen=1
2193 if 'arraylength' in field.attrib:
2194 arraylen=int(field.attrib['arraylength'])
2195 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )'
2196 else:
2197 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')'
2198
2199 assert None!=startindex
2200 if verbose: print 'startindex',startindex
2201
2202 if hasSubTag(field,'optional'):
2203
2204
2205 if msgET.attrib['aismsgnum']!='20':
2206 sys.exit('optional is only allow on AIS msg 20. Do NOT use it on new messages!!!')
2207 text = field.xpath('optional')[0].text
2208 if None==field.xpath('optional')[0].text:
2209
2210 pad = 8-(startindex%8)
2211 print 'not found: noreturn. pad=',pad
2212 assert(pad<=6)
2213 o.write('\tif len(bv)<='+str(startindex+pad)+': return r; # All fields below are optional\n')
2214 elif text != 'NoReturn':
2215 sys.exit ('ERROR: optional text must be NoReturn or empty')
2216
2217 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable)
2218 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable)
2219 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable)
2220 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable)
2221 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable)
2222 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable)
2223
2224 elif type=='decimal':
2225 scale = None
2226 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
2227 offset = None
2228 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text
2229 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,offset=offset)
2230
2231 elif type=='udecimal':
2232 scale = None
2233 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
2234 offset = None
2235 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text
2236 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,offset=offset)
2237
2238 else:
2239 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type
2240 suggestType (name,type)
2241 assert False
2242
2243
2244 if None==startindex: print 'FIX: here. drat. treat me right'
2245 assert None!=startindex
2246
2247
2248 o.write('\treturn r\n\n')
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259 aisType2pythonType={
2260 'bool':'Bool',
2261 'uint':'int',
2262 'int':'int',
2263 'udecimal':'Decimal',
2264 'decimal':'Decimal',
2265 'aisstr6':'str',
2266 'binary':'str',
2267 'float':'Float',
2268 }
2269
2270 import copy
2271 aisType2optParseType=copy.deepcopy(aisType2pythonType)
2272 aisType2optParseType['bool']='int'
2273 aisType2optParseType['aisstr6']='string'
2274 aisType2optParseType['aisstr6']='string'
2275 aisType2optParseType['binary']='string'
2276 aisType2optParseType['udecimal']='string'
2277 aisType2optParseType['decimal']='string'
2278 aisType2optParseType['float']='float'
2279
2280
2282 '''Create a function that adds the options to a parse object'''
2283
2284 assert None != msgET
2285 assert msgET.tag=='message'
2286 msgName = msgET.attrib['name']
2287
2288 prefix=''
2289 if prefixName: prefix=msgName
2290
2291 funcName = 'addMsgOptions'
2292 if prefixName: funcName = msgName + 'AddMsgOptions'
2293 o.write('\ndef '+funcName+'(parser):')
2294
2295
2296
2297 o.write('''
2298 parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true',
2299 help='decode a "'''+msgName+'''" AIS message')
2300 parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true',
2301 help='encode a "'''+msgName+'''" AIS message')
2302 ''')
2303
2304
2305 for field in msgET.xpath('field'):
2306 name = field.attrib['name']
2307 fieldType = field.attrib['type']
2308 if hasSubTag(field,'required'):
2309 print 'skipping required field ...',name,fieldType
2310 continue
2311
2312 o.write('\tparser.add_option(\'--')
2313 if prefixName: o.write(msgName+'-')
2314 o.write(name+'-field\', dest=\''+name+'Field\'')
2315 if hasSubTag(field,'unavailable'):
2316 val = field.xpath('unavailable')[0].text
2317 o.write(',default=')
2318 if fieldType in ('uint','int','float'):
2319 o.write(val)
2320 elif fieldType in ('decimal','udecimal'):
2321 o.write('Decimal(\''+val+'\')')
2322 elif fieldType in ('aisstr6','bitvector'):
2323 o.write('\''+val+'\'')
2324 o.write(',metavar=\''+fieldType+'\',type=\''+aisType2optParseType[fieldType]+'\'')
2325 o.write('\n\t\t,help=\'Field parameter value [default: %default]\')\n')
2326
2327
2328
2329 -def buildMain(o, msgET, prefixName=False):
2330 assert None != msgET
2331 assert msgET.tag=='message'
2332 msgName = msgET.attrib['name']
2333
2334 prefix=''
2335 if prefixName: prefix=msgName
2336
2337 buildOptParse(o, msgET, prefixName)
2338
2339
2340 o.write('''
2341 ############################################################
2342 if __name__=='__main__':
2343
2344 from optparse import OptionParser
2345 parser = OptionParser(usage="%prog [options]",
2346 version="%prog "+__version__)
2347
2348 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true',
2349 help='run the documentation tests')
2350 parser.add_option('--unit-test',dest='unittest',default=False,action='store_true',
2351 help='run the unit tests')
2352 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
2353 help='Make the test output verbose')
2354
2355 # FIX: remove nmea from binary messages. No way to build the whole packet?
2356 # FIX: or build the surrounding msg 8 for a broadcast?
2357 typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message?
2358 parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType'
2359 ,default='nmeapayload'
2360 ,help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]')
2361
2362
2363 outputChoices = ('std','html','csv','sql' ''')
2364
2365 if haveLocatableMessage(msgET): o.write(', \'kml\',\'kml-full\'')
2366 o.write(''')
2367 parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType'
2368 ,default='std'
2369 ,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]')
2370
2371 parser.add_option('-o','--output',dest='outputFileName',default=None,
2372 help='Name of the python file to write [default: stdout]')
2373
2374 parser.add_option('-f','--fields',dest='fieldList',default=None, action='append',
2375 choices=fieldList,
2376 help='Which fields to include in the output. Currently only for csv output [default: all]')
2377
2378 parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true',
2379 help='Print the field name for csv')
2380
2381 parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true',
2382 help='Print out an sql create command for the table.')
2383
2384 parser.add_option('--latex-table',dest='latexDefinitionTable',default=False,action='store_true',
2385 help='Print a LaTeX table of the type')
2386
2387 parser.add_option('--text-table',dest='textDefinitionTable',default=False,action='store_true',
2388 help='Print delimited table of the type (for Word table importing)')
2389 parser.add_option('--delimt-text-table',dest='delimTextDefinitionTable',default='\\t'
2390 ,help='Delimiter for text table [default: \\\'%default\\\'](for Word table importing)')
2391
2392
2393 dbChoices = ('sqlite','postgres')
2394 parser.add_option('-D','--db-type',dest='dbType',default='postgres'
2395 ,choices=dbChoices,type='choice'
2396 ,help='What kind of database ('+', '.join(dbChoices)+') [default: %default]')
2397
2398 ''')
2399
2400 o.write('''\taddMsgOptions(parser)\n''')
2401
2402 o.write('''
2403 (options,args) = parser.parse_args()
2404 success=True
2405
2406 if options.doctest:
2407 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
2408 sys.argv= [sys.argv[0]]
2409 if options.verbose: sys.argv.append('-v')
2410 import doctest
2411 numfail,numtests=doctest.testmod()
2412 if numfail==0: print 'ok'
2413 else:
2414 print 'FAILED'
2415 success=False
2416
2417 if not success: sys.exit('Something Failed')
2418 del success # Hide success from epydoc
2419
2420 if options.unittest:
2421 sys.argv = [sys.argv[0]]
2422 if options.verbose: sys.argv.append('-v')
2423 unittest.main()
2424
2425 outfile = sys.stdout
2426 if None!=options.outputFileName:
2427 outfile = file(options.outputFileName,'w')
2428
2429 ''')
2430
2431
2432
2433
2434
2435
2436
2437 o.write('\n\tif options.doEncode:\n')
2438 o.write('\t\t# First make sure all non required options are specified\n')
2439 for field in msgET.xpath('field'):
2440 name = field.attrib['name']
2441 fieldType = field.attrib['type']
2442 varName = prefix+name+'Field'
2443 if not hasSubTag(field,'required'):
2444 o.write('\t\tif None==options.'+varName+': parser.error("missing value for '+varName+'")\n')
2445
2446
2447 o.write('\t\tmsgDict={\n')
2448 for field in msgET.xpath('field'):
2449 name = field.attrib['name']
2450 varName = prefix+name+'Field'
2451 if hasSubTag(field,'required'):
2452 o.write('\t\t\t\''+name+'\': \''+field.xpath('required')[0].text+'\',\n')
2453 else:
2454 o.write('\t\t\t\''+name+'\': options.'+varName+',\n')
2455 o.write('\t\t}\n')
2456
2457 encodeFunction = 'encode'
2458 if prefixName: encodeFunction = msgName+'Encode'
2459 o.write('''
2460 bits = '''+encodeFunction+'''(msgDict)
2461 if 'binary'==options.ioType: print str(bits)
2462 elif 'nmeapayload'==options.ioType:
2463 # FIX: figure out if this might be necessary at compile time
2464 print "bitLen",len(bits)
2465 bitLen=len(bits)
2466 if bitLen%6!=0:
2467 bits = bits + BitVector(size=(6 - (bitLen%6))) # Pad out to multiple of 6
2468 print "result:",binary.bitvectoais6(bits)[0]
2469
2470
2471 # FIX: Do not emit this option for the binary message payloads. Does not make sense.
2472 elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability")
2473 else: sys.exit('ERROR: unknown ioType. Help!')
2474 ''')
2475
2476
2477
2478
2479
2480 decodeFunction = 'decode'
2481 printFields='printFields'
2482 if prefixName:
2483 decodeFunction = msgName+'Decode'
2484 printFields = msgName+'PrintFields'
2485 o.write('''
2486
2487 if options.sqlCreate:
2488 sqlCreateStr(outfile,options.fieldList,dbType=options.dbType)
2489
2490 if options.latexDefinitionTable:
2491 latexDefinitionTable(outfile)
2492
2493 # For conversion to word tables
2494 if options.textDefinitionTable:
2495 textDefinitionTable(outfile,options.delimTextDefinitionTable)
2496
2497 if options.printCsvfieldList:
2498 # Make a csv separated list of fields that will be displayed for csv
2499 if None == options.fieldList: options.fieldList = fieldList
2500 import StringIO
2501 buf = StringIO.StringIO()
2502 for field in options.fieldList:
2503 buf.write(field+',')
2504 result = buf.getvalue()
2505 if result[-1] == ',': print result[:-1]
2506 else: print result
2507
2508 if options.doDecode:
2509 if len(args)==0: args = sys.stdin
2510 for msg in args:
2511 bv = None
2512
2513 if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'):
2514 # Found nmea
2515 # FIX: do checksum
2516 bv = binary.ais6tobitvec(msg.split(',')[5])
2517 else: # either binary or nmeapayload... expect mostly nmeapayloads
2518 # assumes that an all 0 and 1 string can not be a nmeapayload
2519 binaryMsg=True
2520 for c in msg:
2521 if c not in ('0','1'):
2522 binaryMsg=False
2523 break
2524 if binaryMsg:
2525 bv = BitVector(bitstring=msg)
2526 else: # nmeapayload
2527 bv = binary.ais6tobitvec(msg)
2528
2529 '''+printFields+'''('''+decodeFunction+'''(bv)
2530 ,out=outfile
2531 ,format=options.outputType
2532 ,fieldList=options.fieldList
2533 ,dbType=options.dbType
2534 )
2535
2536 ''')
2537
2538
2539
2540 if __name__=='__main__':
2541 from optparse import OptionParser
2542 parser = OptionParser(usage="%prog [options]",
2543 version="%prog "+__version__)
2544
2545 parser.add_option('-o','--output',dest='outputFileName',default=None,
2546 help='Name of the python file to write')
2547
2548
2549 parser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None,
2550 help='XML definition file for the msg to use')
2551
2552
2553
2554
2555
2556 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true',
2557 help='run the documentation tests')
2558
2559 parser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true',
2560 help='put the field name in front of all function names.'
2561 +' Allows multiple messages in one file')
2562
2563 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
2564 help='run the tests run in verbose mode')
2565
2566 (options,args) = parser.parse_args()
2567
2568 success=True
2569
2570 if options.doctest:
2571 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
2572 argvOrig = sys.argv
2573 sys.argv= [sys.argv[0]]
2574 if options.verbose: sys.argv.append('-v')
2575 import doctest
2576 numfail,numtests=doctest.testmod()
2577 if numfail==0: print 'ok'
2578 else:
2579 print 'FAILED'
2580 success=False
2581 sys.argv = argvOrig
2582 del argvOrig
2583 sys.exit()
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593 if None==options.xmlFileName:
2594 sys.exit('ERROR: must specify an xml definition file.')
2595 if None==options.outputFileName:
2596 sys.exit('ERROR: must specify an python file to write to.')
2597 generatePython(options.xmlFileName,options.outputFileName,prefixName=options.prefix, verbose=options.verbose)
2598
2599 print '\nrecommend running pychecker like this:'
2600 print ' pychecker -q',options.outputFileName
2601