Mercurial > hg > freeDiameter
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 |