blob: 0779e80b7cba41d92f74f7f6d2f7fa2e9a139a48 [file] [log] [blame]
Ole Troan9d420872017-10-12 13:06:35 +02001#!/usr/bin/env python
2
3from __future__ import print_function
4import ply.lex as lex
5import ply.yacc as yacc
6import sys
7import argparse
8import logging
9import binascii
10import os
11
Ole Troand6743b12018-03-07 08:40:58 +010012# Ensure we don't leave temporary files around
13sys.dont_write_bytecode = True
14
Ole Troan9d420872017-10-12 13:06:35 +020015#
16# VPP API language
17#
18
19# Global dictionary of new types (including enums)
20global_types = {}
21
22
23def global_type_add(name):
24 '''Add new type to the dictionary of types '''
25 type_name = 'vl_api_' + name + '_t'
26 if type_name in global_types:
27 raise KeyError('Type is already defined: {}'.format(name))
28 global_types[type_name] = True
29
30
31# All your trace are belong to us!
32def exception_handler(exception_type, exception, traceback):
33 print ("%s: %s" % (exception_type.__name__, exception))
34
35
36#
37# Lexer
38#
39class VPPAPILexer(object):
40 def __init__(self, filename):
41 self.filename = filename
42
43 reserved = {
44 'service': 'SERVICE',
45 'rpc': 'RPC',
46 'returns': 'RETURNS',
Marek Gradzki51e59682018-03-06 10:05:44 +010047 'null': 'NULL',
Ole Troan9d420872017-10-12 13:06:35 +020048 'stream': 'STREAM',
49 'events': 'EVENTS',
50 'define': 'DEFINE',
51 'typedef': 'TYPEDEF',
52 'enum': 'ENUM',
53 'typeonly': 'TYPEONLY',
54 'manual_print': 'MANUAL_PRINT',
55 'manual_endian': 'MANUAL_ENDIAN',
56 'dont_trace': 'DONT_TRACE',
57 'autoreply': 'AUTOREPLY',
58 'option': 'OPTION',
59 'u8': 'U8',
60 'u16': 'U16',
61 'u32': 'U32',
62 'u64': 'U64',
63 'i8': 'I8',
64 'i16': 'I16',
65 'i32': 'I32',
66 'i64': 'I64',
67 'f64': 'F64',
68 'bool': 'BOOL',
69 'string': 'STRING',
70 'import': 'IMPORT',
71 'true': 'TRUE',
72 'false': 'FALSE',
73 }
74
75 tokens = ['STRING_LITERAL',
76 'ID', 'NUM'] + list(reserved.values())
77
78 t_ignore_LINE_COMMENT = '//.*'
79
80 def t_NUM(self, t):
81 r'0[xX][0-9a-fA-F]+|\d+'
82 base = 16 if t.value.startswith('0x') else 10
83 t.value = int(t.value, base)
84 return t
85
86 def t_ID(self, t):
87 r'[a-zA-Z_][a-zA-Z_0-9]*'
88 # Check for reserved words
89 t.type = VPPAPILexer.reserved.get(t.value, 'ID')
90 return t
91
92 # C string
93 def t_STRING_LITERAL(self, t):
94 r'\"([^\\\n]|(\\.))*?\"'
95 t.value = str(t.value).replace("\"", "")
96 return t
97
98 # C or C++ comment (ignore)
99 def t_comment(self, t):
100 r'(/\*(.|\n)*?\*/)|(//.*)'
101 t.lexer.lineno += t.value.count('\n')
102
103 # Error handling rule
104 def t_error(self, t):
105 raise ParseError("Illegal character '{}' ({})"
106 "in {}: line {}".format(t.value[0],
107 hex(ord(t.value[0])),
108 self.filename,
109 t.lexer.lineno))
110 t.lexer.skip(1)
111
112 # Define a rule so we can track line numbers
113 def t_newline(self, t):
114 r'\n+'
115 t.lexer.lineno += len(t.value)
116
117 literals = ":{}[];=.,"
118
119 # A string containing ignored characters (spaces and tabs)
120 t_ignore = ' \t'
121
Ole Troan9d420872017-10-12 13:06:35 +0200122class Service():
123 def __init__(self, caller, reply, events=[], stream=False):
124 self.caller = caller
125 self.reply = reply
126 self.stream = stream
127 self.events = events
128
129
130class Typedef():
131 def __init__(self, name, flags, block):
132 self.name = name
133 self.flags = flags
134 self.block = block
135 self.crc = binascii.crc32(str(block)) & 0xffffffff
136 global_type_add(name)
137
138 def __repr__(self):
139 return self.name + str(self.flags) + str(self.block)
140
141
142class Define():
143 def __init__(self, name, flags, block):
144 self.name = name
145 self.flags = flags
146 self.block = block
147 self.crc = binascii.crc32(str(block)) & 0xffffffff
148 self.typeonly = False
149 self.dont_trace = False
150 self.manual_print = False
151 self.manual_endian = False
152 self.autoreply = False
153 self.singular = False
154 for f in flags:
155 if f == 'typeonly':
156 self.typeonly = True
157 global_type_add(name)
158 elif f == 'dont_trace':
159 self.dont_trace = True
160 elif f == 'manual_print':
161 self.manual_print = True
162 elif f == 'manual_endian':
163 self.manual_endian = True
164 elif f == 'autoreply':
165 self.autoreply = True
166
167 for b in block:
168 if isinstance(b, Option):
169 if b[1] == 'singular' and b[2] == 'true':
170 self.singular = True
171 block.remove(b)
172
173 def __repr__(self):
174 return self.name + str(self.flags) + str(self.block)
175
176
177class Enum():
178 def __init__(self, name, block, enumtype='u32'):
179 self.name = name
180 self.enumtype = enumtype
181 count = 0
182 for i, b in enumerate(block):
183 if type(b) is list:
184 count = b[1]
185 else:
186 count += 1
187 block[i] = [b, count]
188
189 self.block = block
190 self.crc = binascii.crc32(str(block)) & 0xffffffff
191 global_type_add(name)
192
193 def __repr__(self):
194 return self.name + str(self.block)
195
196
197class Import():
198 def __init__(self, filename):
199 self.filename = filename
200
201 # Deal with imports
202 parser = VPPAPI(filename=filename)
203 dirlist = dirlist_get()
204 f = filename
205 for dir in dirlist:
206 f = os.path.join(dir, filename)
207 if os.path.exists(f):
208 break
209 with open(f) as fd:
210 self.result = parser.parse_file(fd, None)
211
212 def __repr__(self):
213 return self.filename
214
215
216class Option():
217 def __init__(self, option):
218 self.option = option
219 self.crc = binascii.crc32(str(option)) & 0xffffffff
220
221 def __repr__(self):
222 return str(self.option)
223
224 def __getitem__(self, index):
225 return self.option[index]
226
227
228class Array():
229 def __init__(self, fieldtype, name, length):
230 self.type = 'Array'
231 self.fieldtype = fieldtype
232 self.fieldname = name
233 if type(length) is str:
234 self.lengthfield = length
235 self.length = 0
236 else:
237 self.length = length
238 self.lengthfield = None
239
240 def __repr__(self):
241 return str([self.fieldtype, self.fieldname, self.length,
242 self.lengthfield])
243
244
245class Field():
246 def __init__(self, fieldtype, name):
247 self.type = 'Field'
248 self.fieldtype = fieldtype
249 self.fieldname = name
250
251 def __repr__(self):
252 return str([self.fieldtype, self.fieldname])
253
254
255class Coord(object):
256 """ Coordinates of a syntactic element. Consists of:
257 - File name
258 - Line number
259 - (optional) column number, for the Lexer
260 """
261 __slots__ = ('file', 'line', 'column', '__weakref__')
262
263 def __init__(self, file, line, column=None):
264 self.file = file
265 self.line = line
266 self.column = column
267
268 def __str__(self):
269 str = "%s:%s" % (self.file, self.line)
270 if self.column:
271 str += ":%s" % self.column
272 return str
273
274
275class ParseError(Exception):
276 pass
277
278
279#
280# Grammar rules
281#
282class VPPAPIParser(object):
283 tokens = VPPAPILexer.tokens
284
285 def __init__(self, filename, logger):
286 self.filename = filename
287 self.logger = logger
288 self.fields = []
289
290 def _parse_error(self, msg, coord):
291 raise ParseError("%s: %s" % (coord, msg))
292
293 def _parse_warning(self, msg, coord):
294 if self.logger:
295 self.logger.warning("%s: %s" % (coord, msg))
296
297 def _coord(self, lineno, column=None):
298 return Coord(
299 file=self.filename,
300 line=lineno, column=column)
301
302 def _token_coord(self, p, token_idx):
303 """ Returns the coordinates for the YaccProduction object 'p' indexed
304 with 'token_idx'. The coordinate includes the 'lineno' and
305 'column'. Both follow the lex semantic, starting from 1.
306 """
307 last_cr = p.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx))
308 if last_cr < 0:
309 last_cr = -1
310 column = (p.lexpos(token_idx) - (last_cr))
311 return self._coord(p.lineno(token_idx), column)
312
313 def p_slist(self, p):
314 '''slist : stmt
315 | slist stmt'''
316 if len(p) == 2:
317 p[0] = [p[1]]
318 else:
319 p[0] = p[1] + [p[2]]
320
321 def p_stmt(self, p):
322 '''stmt : define
323 | typedef
324 | option
325 | import
326 | enum
327 | service'''
328 p[0] = p[1]
329
330 def p_import(self, p):
331 '''import : IMPORT STRING_LITERAL ';' '''
332 p[0] = Import(p[2])
333
334 def p_service(self, p):
335 '''service : SERVICE '{' service_statements '}' ';' '''
336 p[0] = p[3]
337
338 def p_service_statements(self, p):
339 '''service_statements : service_statement
340 | service_statements service_statement'''
341 if len(p) == 2:
342 p[0] = [p[1]]
343 else:
344 p[0] = p[1] + [p[2]]
345
346 def p_service_statement(self, p):
Marek Gradzki51e59682018-03-06 10:05:44 +0100347 '''service_statement : RPC ID RETURNS NULL ';'
348 | RPC ID RETURNS ID ';'
Ole Troan9d420872017-10-12 13:06:35 +0200349 | RPC ID RETURNS STREAM ID ';'
350 | RPC ID RETURNS ID EVENTS event_list ';' '''
Marek Gradzkifc70e3a2018-03-06 10:56:26 +0100351 if p[2] == p[4]:
352 # Verify that caller and reply differ
353 self._parse_error('Reply ID ({}) should not be equal to Caller ID'.format(p[2]),
354 self._token_coord(p, 1))
Ole Troan9d420872017-10-12 13:06:35 +0200355 if len(p) == 8:
356 p[0] = Service(p[2], p[4], p[6])
357 elif len(p) == 7:
358 p[0] = Service(p[2], p[5], stream=True)
359 else:
360 p[0] = Service(p[2], p[4])
361
362 def p_event_list(self, p):
363 '''event_list : events
364 | event_list events '''
365 if len(p) == 2:
366 p[0] = [p[1]]
367 else:
368 p[0] = p[1] + [p[2]]
369
370 def p_event(self, p):
371 '''events : ID
372 | ID ',' '''
373 p[0] = p[1]
374
375 def p_enum(self, p):
376 '''enum : ENUM ID '{' enum_statements '}' ';' '''
377 p[0] = Enum(p[2], p[4])
378
379 def p_enum_type(self, p):
380 ''' enum : ENUM ID ':' enum_size '{' enum_statements '}' ';' '''
381 if len(p) == 9:
382 p[0] = Enum(p[2], p[6], enumtype=p[4])
383 else:
384 p[0] = Enum(p[2], p[4])
385
386 def p_enum_size(self, p):
387 ''' enum_size : U8
388 | U16
389 | U32 '''
390 p[0] = p[1]
391
392 def p_define(self, p):
393 '''define : DEFINE ID '{' block_statements_opt '}' ';' '''
394 self.fields = []
395 p[0] = Define(p[2], [], p[4])
396
397 def p_define_flist(self, p):
398 '''define : flist DEFINE ID '{' block_statements_opt '}' ';' '''
399 p[0] = Define(p[3], p[1], p[5])
400
401 def p_flist(self, p):
402 '''flist : flag
403 | flist flag'''
404 if len(p) == 2:
405 p[0] = [p[1]]
406 else:
407 p[0] = p[1] + [p[2]]
408
409 def p_flag(self, p):
410 '''flag : MANUAL_PRINT
411 | MANUAL_ENDIAN
412 | DONT_TRACE
413 | TYPEONLY
414 | AUTOREPLY'''
415 if len(p) == 1:
416 return
417 p[0] = p[1]
418
419 def p_typedef(self, p):
420 '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' '''
421 p[0] = Typedef(p[2], [], p[4])
422
423 def p_block_statements_opt(self, p):
424 '''block_statements_opt : block_statements'''
425 p[0] = p[1]
426
427 def p_block_statements(self, p):
428 '''block_statements : block_statement
429 | block_statements block_statement'''
430 if len(p) == 2:
431 p[0] = [p[1]]
432 else:
433 p[0] = p[1] + [p[2]]
434
435 def p_block_statement(self, p):
436 '''block_statement : declaration
437 | option '''
438 p[0] = p[1]
439
440 def p_enum_statements(self, p):
441 '''enum_statements : enum_statement
442 | enum_statements enum_statement'''
443 if len(p) == 2:
444 p[0] = [p[1]]
445 else:
446 p[0] = p[1] + [p[2]]
447
448 def p_enum_statement(self, p):
449 '''enum_statement : ID '=' NUM ','
450 | ID ',' '''
451 if len(p) == 5:
452 p[0] = [p[1], p[3]]
453 else:
454 p[0] = p[1]
455
456 def p_declaration(self, p):
457 '''declaration : type_specifier ID ';' '''
458 if len(p) != 4:
459 self._parse_error('ERROR')
460 self.fields.append(p[2])
461 p[0] = Field(p[1], p[2])
462
463 def p_declaration_array(self, p):
464 '''declaration : type_specifier ID '[' NUM ']' ';'
465 | type_specifier ID '[' ID ']' ';' '''
466 if len(p) != 7:
467 return self._parse_error(
468 'array: %s' % p.value,
469 self._coord(lineno=p.lineno))
470
471 # Make this error later
472 if type(p[4]) is int and p[4] == 0:
473 # XXX: Line number is wrong
474 self._parse_warning('Old Style VLA: {} {}[{}];'
475 .format(p[1], p[2], p[4]),
476 self._token_coord(p, 1))
477
478 if type(p[4]) is str and p[4] not in self.fields:
479 # Verify that length field exists
480 self._parse_error('Missing length field: {} {}[{}];'
481 .format(p[1], p[2], p[4]),
482 self._token_coord(p, 1))
483 p[0] = Array(p[1], p[2], p[4])
484
485 def p_option(self, p):
486 '''option : OPTION ID '=' assignee ';' '''
487 p[0] = Option([p[1], p[2], p[4]])
488
489 def p_assignee(self, p):
490 '''assignee : NUM
491 | TRUE
492 | FALSE
493 | STRING_LITERAL '''
494 p[0] = p[1]
495
496 def p_type_specifier(self, p):
497 '''type_specifier : U8
498 | U16
499 | U32
500 | U64
501 | I8
502 | I16
503 | I32
504 | I64
505 | F64
506 | BOOL
507 | STRING'''
508 p[0] = p[1]
509
510 # Do a second pass later to verify that user defined types are defined
511 def p_typedef_specifier(self, p):
512 '''type_specifier : ID '''
513 if p[1] not in global_types:
514 self._parse_error('Undefined type: {}'.format(p[1]),
515 self._token_coord(p, 1))
516 p[0] = p[1]
517
518 # Error rule for syntax errors
519 def p_error(self, p):
520 if p:
521 self._parse_error(
522 'before: %s' % p.value,
523 self._coord(lineno=p.lineno))
524 else:
525 self._parse_error('At end of input', self.filename)
526
527
528class VPPAPI(object):
529
530 def __init__(self, debug=False, filename='', logger=None):
531 self.lexer = lex.lex(module=VPPAPILexer(filename), debug=debug)
532 self.parser = yacc.yacc(module=VPPAPIParser(filename, logger),
Ole Troand6743b12018-03-07 08:40:58 +0100533 write_tables=False, debug=debug)
Ole Troan9d420872017-10-12 13:06:35 +0200534 self.logger = logger
535
536 def parse_string(self, code, debug=0, lineno=1):
537 self.lexer.lineno = lineno
538 return self.parser.parse(code, lexer=self.lexer, debug=debug)
539
540 def parse_file(self, fd, debug=0):
541 data = fd.read()
542 return self.parse_string(data, debug=debug)
543
544 def autoreply_block(self, name):
545 block = [Field('u32', 'context'),
546 Field('i32', 'retval')]
547 return Define(name + '_reply', [], block)
548
549 def process(self, objs):
550 s = {}
551 s['defines'] = []
552 s['typedefs'] = []
553 s['imports'] = []
554 s['options'] = {}
555 s['enums'] = []
556 s['services'] = []
557
558 for o in objs:
559 if isinstance(o, Define):
560 if o.typeonly:
561 s['typedefs'].append(o)
562 else:
563 s['defines'].append(o)
564 if o.autoreply:
565 s['defines'].append(self.autoreply_block(o.name))
566 elif isinstance(o, Option):
567 s['options'][o[1]] = o[2]
568 elif isinstance(o, Enum):
569 s['enums'].append(o)
570 elif isinstance(o, Typedef):
571 s['typedefs'].append(o)
572 elif type(o) is list:
573 for o2 in o:
574 if isinstance(o2, Service):
575 s['services'].append(o2)
576
Marek Gradzki51e59682018-03-06 10:05:44 +0100577
Ole Troan9d420872017-10-12 13:06:35 +0200578 msgs = {d.name: d for d in s['defines']}
579 svcs = {s.caller: s for s in s['services']}
Marek Gradzkib533f3f2018-03-06 11:10:56 +0100580 replies = {s.reply: s for s in s['services']}
Marek Gradzki51e59682018-03-06 10:05:44 +0100581 seen_services = {}
Ole Troan9d420872017-10-12 13:06:35 +0200582
583 for service in svcs:
584 if service not in msgs:
585 raise ValueError('Service definition refers to unknown message'
586 ' definition: {}'.format(service))
Marek Gradzki51e59682018-03-06 10:05:44 +0100587 if svcs[service].reply != 'null' and svcs[service].reply not in msgs:
Ole Troan9d420872017-10-12 13:06:35 +0200588 raise ValueError('Service definition refers to unknown message'
589 ' definition in reply: {}'
590 .format(svcs[service].reply))
Marek Gradzkib533f3f2018-03-06 11:10:56 +0100591 if service in replies:
592 raise ValueError('Service definition refers to message'
593 ' marked as reply: {}'.format(service))
Ole Troan9d420872017-10-12 13:06:35 +0200594 for event in svcs[service].events:
595 if event not in msgs:
596 raise ValueError('Service definition refers to unknown '
597 'event: {} in message: {}'
598 .format(event, service))
Marek Gradzki51e59682018-03-06 10:05:44 +0100599 seen_services[event] = True
Ole Troan9d420872017-10-12 13:06:35 +0200600
Marek Gradzki51e59682018-03-06 10:05:44 +0100601 # Create services implicitly
Ole Troan9d420872017-10-12 13:06:35 +0200602 for d in msgs:
Marek Gradzki51e59682018-03-06 10:05:44 +0100603 if d in seen_services:
604 continue
Ole Troan9d420872017-10-12 13:06:35 +0200605 if msgs[d].singular is True:
606 continue
Ole Troan9d420872017-10-12 13:06:35 +0200607 if d.endswith('_reply'):
608 if d[:-6] in svcs:
609 continue
610 if d[:-6] not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100611 raise ValueError('{} missing calling message'
612 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200613 continue
614 if d.endswith('_dump'):
615 if d in svcs:
616 continue
617 if d[:-5]+'_details' in msgs:
618 s['services'].append(Service(d, d[:-5]+'_details',
619 stream=True))
620 else:
Marek Gradzkicc134712018-03-06 12:25:02 +0100621 raise ValueError('{} missing details message'
622 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200623 continue
624
625 if d.endswith('_details'):
626 if d[:-8]+'_dump' not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100627 raise ValueError('{} missing dump message'
628 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200629 continue
630
631 if d in svcs:
632 continue
633 if d+'_reply' in msgs:
634 s['services'].append(Service(d, d+'_reply'))
635 else:
Marek Gradzki07dce1e2018-03-06 11:42:36 +0100636 raise ValueError('{} missing reply message ({}) or service definition'
637 .format(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200638
639 return s
640
Marek Gradzki51e59682018-03-06 10:05:44 +0100641 def process_imports(self, objs, in_import):
642 imported_objs = []
Ole Troan9d420872017-10-12 13:06:35 +0200643 for o in objs:
644 if isinstance(o, Import):
Marek Gradzki51e59682018-03-06 10:05:44 +0100645 return objs + self.process_imports(o.result, True)
646 if in_import:
647 if isinstance(o, Define) and o.typeonly:
648 imported_objs.append(o)
649 if in_import:
650 return imported_objs
Ole Troan9d420872017-10-12 13:06:35 +0200651 return objs
652
653
654# Add message ids to each message.
655def add_msg_id(s):
656 for o in s:
657 o.block.insert(0, Field('u16', '_vl_msg_id'))
658 return s
659
660
661def getcrc(s):
662 return binascii.crc32(str(s)) & 0xffffffff
663
664
665dirlist = []
666
667
668def dirlist_add(dirs):
669 global dirlist
670 if dirs:
671 dirlist = dirlist + dirs
672
673
674def dirlist_get():
675 return dirlist
676
677
678#
679# Main
680#
681def main():
Ole Troan9d420872017-10-12 13:06:35 +0200682 cliparser = argparse.ArgumentParser(description='VPP API generator')
683 cliparser.add_argument('--pluginpath', default=""),
684 cliparser.add_argument('--includedir', action='append'),
685 cliparser.add_argument('--input', type=argparse.FileType('r'),
686 default=sys.stdin)
687 cliparser.add_argument('--output', nargs='?', type=argparse.FileType('w'),
688 default=sys.stdout)
689
690 cliparser.add_argument('output_module', nargs='?', default='C')
691 cliparser.add_argument('--debug', action='store_true')
692 cliparser.add_argument('--show-name', nargs=1)
693 args = cliparser.parse_args()
694
695 dirlist_add(args.includedir)
696 if not args.debug:
697 sys.excepthook = exception_handler
698
699 # Filename
700 if args.show_name:
701 filename = args.show_name[0]
702 elif args.input != sys.stdin:
703 filename = args.input.name
704 else:
705 filename = ''
706
Marek Gradzki51e59682018-03-06 10:05:44 +0100707 if args.debug:
708 logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
709 else:
710 logging.basicConfig()
711 log = logging.getLogger('vppapigen')
712
713
Ole Troan9d420872017-10-12 13:06:35 +0200714 parser = VPPAPI(debug=args.debug, filename=filename, logger=log)
715 result = parser.parse_file(args.input, log)
716
717 # Build a list of objects. Hash of lists.
Marek Gradzki51e59682018-03-06 10:05:44 +0100718 result = parser.process_imports(result, False)
Ole Troan9d420872017-10-12 13:06:35 +0200719 s = parser.process(result)
720
721 # Add msg_id field
722 s['defines'] = add_msg_id(s['defines'])
723
724 file_crc = getcrc(s)
725
726 #
727 # Debug
728 if args.debug:
729 import pprint
730 pp = pprint.PrettyPrinter(indent=4)
731 for t in s['defines']:
732 pp.pprint([t.name, t.flags, t.block])
733 for t in s['typedefs']:
734 pp.pprint([t.name, t.flags, t.block])
735
736 #
737 # Generate representation
738 #
739 import imp
740
741 # Default path
Ole Troan30787372018-03-01 13:33:39 +0100742 pluginpath = ''
Ole Troan9d420872017-10-12 13:06:35 +0200743 if not args.pluginpath:
Ole Troan30787372018-03-01 13:33:39 +0100744 cand = []
745 cand.append(os.path.dirname(os.path.realpath(__file__)))
746 cand.append(os.path.dirname(os.path.realpath(__file__)) + \
747 '/../share/vpp/')
748 for c in cand:
749 c += '/'
750 if os.path.isfile(c + args.output_module + '.py'):
751 pluginpath = c
752 break
Ole Troan9d420872017-10-12 13:06:35 +0200753 else:
754 pluginpath = args.pluginpath + '/'
Ole Troan30787372018-03-01 13:33:39 +0100755 if pluginpath == '':
756 raise Exception('Output plugin not found')
Ole Troan9d420872017-10-12 13:06:35 +0200757 module_path = pluginpath + args.output_module + '.py'
758
759 try:
760 plugin = imp.load_source(args.output_module, module_path)
761 except Exception, err:
762 raise Exception('Error importing output plugin: {}, {}'
763 .format(module_path, err))
764
765 result = plugin.run(filename, s, file_crc)
766 if result:
767 print (result, file=args.output)
768 else:
769 raise Exception('Running plugin failed: {} {}'
770 .format(filename, result))
771
772
773if __name__ == '__main__':
774 main()