comparison contrib/tools/csv_to_fd @ 1498:01efba83bf60

csv_to_fd: fdc generate complete file fdc: Generate a file with a function to create the DICT_AVP entries, instead of a function snippet. Processor: provide parameter() method for implementations. Rename next_file() to filename().
author Luke Mewburn <luke@mewburn.net>
date Fri, 03 Apr 2020 11:37:26 +1100
parents ae76ea63ee12
children 2ab9d941bad5
comparison
equal deleted inserted replaced
1497:d9615342e116 1498:01efba83bf60
202 if cls.__doc__ is None: 202 if cls.__doc__ is None:
203 return "" 203 return ""
204 return cls.__doc__.split('\n')[0] 204 return cls.__doc__.split('\n')[0]
205 205
206 @abc.abstractmethod 206 @abc.abstractmethod
207 def next_file(self, filename): 207 def filename(self, filename):
208 """Called when a file is opened.""" 208 """Called when a file is opened."""
209 pass 209 pass
210 210
211 @abc.abstractmethod 211 @abc.abstractmethod
212 def avp(self, avp): 212 def avp(self, avp):
223 @abc.abstractmethod 223 @abc.abstractmethod
224 def generate(self): 224 def generate(self):
225 """Invoked after all rows processed.""" 225 """Invoked after all rows processed."""
226 pass 226 pass
227 227
228 @abc.abstractmethod
229 def parameter(self, name, value):
230 """Process a parameter row:
231 @name,value.
232 """
233 pass
234
228 235
229 class DebugProcessor(Processor): 236 class DebugProcessor(Processor):
230 """Display the CSV parsing.""" 237 """Display the CSV parsing."""
231 238
232 def next_file(self, filename): 239 def filename(self, filename):
233 print('File: {}'.format(filename)) 240 print('File: {}'.format(filename))
234 241
235 def avp(self, avp): 242 def avp(self, avp):
236 avpdict = vars(avp) 243 avpdict = vars(avp)
237 print('AVP: {name}, {code}, {datatype}'.format(**avpdict)) 244 print('AVP: {name}, {code}, {datatype}'.format(**avpdict))
240 print('Comment: {}'.format(comment)) 247 print('Comment: {}'.format(comment))
241 248
242 def generate(self): 249 def generate(self):
243 print('Generate') 250 print('Generate')
244 251
252 def parameter(self, name, value):
253 print('Parameter: {} {}'.format(name, value))
254
245 255
246 class NoopProcessor(Processor): 256 class NoopProcessor(Processor):
247 """Validate the CSV; no other output.""" 257 """Validate the CSV; no other output."""
248 258
249 def next_file(self, filename): 259 def filename(self, filename):
250 pass 260 pass
251 261
252 def avp(self, avp): 262 def avp(self, avp):
253 pass 263 pass
254 264
255 def comment(self, comment, filename, line_num): 265 def comment(self, comment, filename, line_num):
256 pass 266 pass
257 267
258 def generate(self): 268 def generate(self):
269 pass
270
271 def parameter(self, name, value):
259 pass 272 pass
260 273
261 274
262 class FdcProcessor(Processor): 275 class FdcProcessor(Processor):
263 """Generate freeDiameter C code. 276 """Generate freeDiameter C code.
268 # [blank line] 281 # [blank line]
269 """ 282 """
270 283
271 COMMENT_WIDTH = 64 284 COMMENT_WIDTH = 64
272 285
286 class AvpFunction(object):
287 """Maintain per-function state to create DICT_AVP entries.
288 """
289
290 def __init__(self, name):
291 self.__name = name
292 self.__lines = []
293 self.__derived = set()
294
295 @property
296 def name(self):
297 """Return name."""
298 return self.__name
299
300 @property
301 def lines(self):
302 """Return all lines."""
303 return self.__lines
304
305 @lines.setter
306 def lines(self, value):
307 """Set to append a line."""
308 self.__lines.append(value)
309
310 @property
311 def derived(self):
312 """Return list of all derived values."""
313 return list(self.__derived)
314
315 @derived.setter
316 def derived(self, value):
317 """Set to store a derived type."""
318 self.__derived.add(value)
319
273 def __init__(self): 320 def __init__(self):
274 self.filenames = [] 321 self._filenames = []
275 self.lines = [] 322 self._functions = collections.OrderedDict()
276 323
277 def next_file(self, filename): 324 def filename(self, filename):
278 self.filenames.append(os.path.basename(filename)) 325 self._filenames.append(os.path.basename(filename))
279 326
280 def avp(self, avp): 327 def avp(self, avp):
281 comment = '{name}, {datatype}, code {code}'.format(**vars(avp)) 328 comment = '{name}, {datatype}, code {code}'.format(**vars(avp))
282 if avp.section != '': 329 if avp.section != '':
283 comment += ', section {}'.format(avp.section) 330 comment += ', section {}'.format(avp.section)
309 # XXX: add enumerated values 356 # XXX: add enumerated values
310 self.add('\t\tCHECK_dict_new(DICT_TYPE, &tdata, NULL, &type);') 357 self.add('\t\tCHECK_dict_new(DICT_TYPE, &tdata, NULL, &type);')
311 avp_type = "type" 358 avp_type = "type"
312 elif avp.datatype in DERIVED_TO_BASE: 359 elif avp.datatype in DERIVED_TO_BASE:
313 avp_type = '{}_type'.format(avp.datatype) 360 avp_type = '{}_type'.format(avp.datatype)
361 self.derived(avp.datatype)
314 self.add('\t\tCHECK_dict_new(DICT_AVP, &data, {}, NULL);'.format( 362 self.add('\t\tCHECK_dict_new(DICT_AVP, &data, {}, NULL);'.format(
315 avp_type)) 363 avp_type))
316 # TODO: remove ; on scope brace 364 # TODO: remove ; on scope brace
317 self.add('\t};') 365 self.add('\t};')
318 self.add('') 366 self.add('')
326 self.add_comment(comment[1:]) 374 self.add_comment(comment[1:])
327 else: 375 else:
328 raise ValueError('Unsupported comment "{}"'.format(comment)) 376 raise ValueError('Unsupported comment "{}"'.format(comment))
329 377
330 def generate(self): 378 def generate(self):
331 self.print_header() 379 fp = sys.stdout
332 self.print_comment('Start of generated data.') 380 self.write_introduction(fp)
333 self.print_comment('') 381 for func in self._functions:
334 self.print_comment('The following is created automatically with:') 382 self.write_function(fp, self._functions[func])
335 self.print_comment(' csv_to_fd -p {} {}'.format( 383
336 self.cls_name(), ' '.join(self.filenames))) 384 def parameter(self, name, value):
337 self.print_comment('Changes will be lost during the next update.') 385 pass
338 self.print_comment('Do not modify;' 386
339 ' modify the source .csv file instead.') 387 # internal methods
340 self.print_header() 388
341 print('') 389 def current_avpfunction(self):
342 print('\n'.join(self.lines)) 390 """Return current AvpFunction to update.
343 self.print_header() 391
344 self.print_comment('End of generated data.') 392 Note: allows for easier future enhancement to generate separate
345 self.print_header() 393 C functions per AVP groups such as: by csv file, standard, or vendor.
394 """
395 name = 'add_avps'
396 if name not in self._functions:
397 self._functions[name] = self.AvpFunction(name)
398 return self._functions[name]
399
400 def add(self, line):
401 self.current_avpfunction().lines = line
402
403 def derived(self, value):
404 self.current_avpfunction().derived = value
405
406 def build_c_token(self, value):
407 """Convert a string into a valid C token."""
408 return re.sub(r'[^\w]', '_', value)
346 409
347 def build_flags(self, flags): 410 def build_flags(self, flags):
348 result = [] 411 result = []
349 if 'V' in flags: 412 if 'V' in flags:
350 result.append('AVP_FLAG_VENDOR') 413 result.append('AVP_FLAG_VENDOR')
351 if 'M' in flags: 414 if 'M' in flags:
352 result.append('AVP_FLAG_MANDATORY') 415 result.append('AVP_FLAG_MANDATORY')
353 return ' |'.join(result) 416 return ' |'.join(result)
354 417
355 def add(self, line):
356 self.lines.append(line)
357
358 def add_comment(self, comment): 418 def add_comment(self, comment):
359 self.lines.append(self.format_comment(comment)) 419 self.add(self.format_comment(comment))
360 420
361 def add_header(self): 421 def add_header(self):
362 self.lines.append(self.format_header()) 422 self.add(self.format_header())
363 423
364 def format_comment(self, comment): 424 def format_comment(self, comment):
365 return '\t/* {:<{width}} */'.format(comment, width=self.COMMENT_WIDTH) 425 return '\t/* {:<{width}} */'.format(comment, width=self.COMMENT_WIDTH)
366 426
367 def format_header(self): 427 def format_header(self):
368 return '\t/*={:=<{width}}=*/'.format('', width=self.COMMENT_WIDTH) 428 return '\t/*={:=<{width}}=*/'.format('', width=self.COMMENT_WIDTH)
369 429
370 def print_comment(self, comment): 430 def write_introduction(self, fp):
371 print(self.format_comment(comment)) 431 """Write the introduction to the generated file."""
372 432 fp.write('''\
373 def print_header(self): 433 /*
374 print(self.format_header()) 434 Generated by:
435 \tcsv_to_fd -p {processor} {files}
436
437 Do not modify; modify the source .csv files instead.
438 */
439
440 #include <freeDiameter/extension.h>
441
442 #define CHECK_dict_new( _type, _data, _parent, _ref ) \\
443 \tCHECK_FCT( fd_dict_new( fd_g_config->cnf_dict, \
444 (_type), (_data), (_parent), (_ref)) );
445
446 #define CHECK_dict_search( _type, _criteria, _what, _result ) \\
447 \tCHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, \
448 (_type), (_criteria), (_what), (_result), ENOENT) );
449 '''.format(
450 processor=self.cls_name(),
451 files=' '.join(self._filenames)))
452
453 def write_function(self, fp, avpfunction):
454 """Generate a function from AvpFunction."""
455 function = self.build_c_token(avpfunction.name)
456 # Function start
457 fp.write('''\
458
459 int {}()
460 {{
461 '''.format(function))
462
463 # Create variables used by derived type validation
464 for derived in avpfunction.derived:
465 fp.write('''\
466 \tstruct dict_object * {name}_type = NULL;
467 \tCHECK_dict_search(DICT_TYPE, TYPE_BY_NAME, "{name}", &{name}_type);
468
469 '''.format(name=derived))
470
471 # Write generated DICT_AVP creation
472 fp.write('\n'.join(avpfunction.lines))
473
474 # Write function end
475 fp.write('''\
476
477 \treturn 0;
478 }} /* {}() */
479 '''.format(function))
375 480
376 481
377 class JsonProcessor(Processor): 482 class JsonProcessor(Processor):
378 """Generate freeDiameter JSON object. 483 """Generate freeDiameter JSON object.
379 """ 484 """
380 485
381 def __init__(self): 486 def __init__(self):
382 self.avps = [] 487 self.avps = []
383 488
384 def next_file(self, filename): 489 def filename(self, filename):
385 pass 490 pass
386 491
387 def avp(self, avp): 492 def avp(self, avp):
388 flags = collections.OrderedDict([ 493 flags = collections.OrderedDict([
389 ('Must', self.build_flags(avp.must)), 494 ('Must', self.build_flags(avp.must)),
402 pass 507 pass
403 508
404 def generate(self): 509 def generate(self):
405 doc = {"AVPs": self.avps} 510 doc = {"AVPs": self.avps}
406 print(json.dumps(doc, indent=2)) 511 print(json.dumps(doc, indent=2))
512
513 def parameter(self, name, value):
514 pass
407 515
408 def build_flags(self, flags): 516 def build_flags(self, flags):
409 result = [] 517 result = []
410 if 'V' in flags: 518 if 'V' in flags:
411 result.append('V') 519 result.append('V')
464 usage='%prog [-h] [-p PROCESSOR] FILE ...', 572 usage='%prog [-h] [-p PROCESSOR] FILE ...',
465 description="""\ 573 description="""\
466 Convert CSV files FILE ... containing RADIUS or Diameter AVP tables 574 Convert CSV files FILE ... containing RADIUS or Diameter AVP tables
467 into various formats using the specified processor PROCESSOR. 575 into various formats using the specified processor PROCESSOR.
468 """) 576 """)
469
470 parser.add_option( 577 parser.add_option(
471 '-p', '--processor', 578 '-p', '--processor',
472 default='noop', 579 default='noop',
473 help='AVP processor. One of: {}. [%default]'.format( 580 help='AVP processor. One of: {}. [%default]'.format(
474 ', '.join(processors.keys()))) 581 ', '.join(processors.keys())))
488 # dict of [vendor][name] : Avp 595 # dict of [vendor][name] : Avp
489 avp_names = collections.defaultdict(dict) 596 avp_names = collections.defaultdict(dict)
490 597
491 # Process files 598 # Process files
492 for filename in args: 599 for filename in args:
493 avpproc.next_file(filename) 600 avpproc.filename(filename)
494 with open(filename, 'r') as csvfile: 601 with open(filename, 'r') as csvfile:
495 csvdata = csv.DictReader(csvfile, CSV_COLUMN_NAMES, 602 csvdata = csv.DictReader(csvfile, CSV_COLUMN_NAMES,
496 restkey='extra_cells') 603 restkey='extra_cells')
497 standard = '' 604 standard = ''
498 vendor = 0 605 vendor = 0
514 elif parameter == 'vendor': 621 elif parameter == 'vendor':
515 vendor = int(value) 622 vendor = int(value)
516 else: 623 else:
517 raise ValueError('Unknown parameter "{}"'.format( 624 raise ValueError('Unknown parameter "{}"'.format(
518 parameter)) 625 parameter))
626 avpproc.parameter(parameter, value)
519 else: 627 else:
520 avp = Avp(filename=filename, line_num=csvdata.line_num, 628 avp = Avp(filename=filename, line_num=csvdata.line_num,
521 standard=standard, vendor=vendor, 629 standard=standard, vendor=vendor,
522 **row) 630 **row)
523 # Ensure AVP vendor/code not already defined 631 # Ensure AVP vendor/code not already defined
"Welcome to our mercurial repository"