Mercurial > hg > freeDiameter
comparison contrib/tools/csv_to_fd @ 1469:48fa8d70e6ad
csv_to_fd: validate flags and other improvements
Validate the flags.
Allow more columns; ignore any past column 8 "Must Not".
Add basename of filenames to the regen command.
author | Luke Mewburn <luke@mewburn.net> |
---|---|
date | Wed, 18 Mar 2020 11:40:01 +1100 |
parents | 1404de313b85 |
children | c0aa1e66c12e |
comparison
equal
deleted
inserted
replaced
1468:e947c6dbc096 | 1469:48fa8d70e6ad |
---|---|
4 Convert CSV files containing RADIUS or Diameter AVP tables | 4 Convert CSV files containing RADIUS or Diameter AVP tables |
5 into various formats. | 5 into various formats. |
6 | 6 |
7 Format of the CSV files is one of: | 7 Format of the CSV files is one of: |
8 - Row per 3GPP AVP tables: | 8 - Row per 3GPP AVP tables: |
9 Name, Code, Section, DataType, Must, May, ShouldNot, MustNot [, ...] | 9 Name, Code, Section, DataType, Must, May, ShouldNot, MustNot [, extra] |
10 - Name: | 10 - Name: |
11 AVP Name. String, validated as ALPHA *(ALPHA / DIGIT / "-") | 11 AVP Name. String, validated as ALPHA *(ALPHA / DIGIT / "-") |
12 per RFC 6733 section 3.2. | 12 per RFC 6733 section 3.2. |
13 - Code: | 13 - Code: |
14 AVP Code. Integer, 0..4294967295. | 14 AVP Code. Integer, 0..4294967295. |
42 import csv | 42 import csv |
43 import collections | 43 import collections |
44 import json | 44 import json |
45 import re | 45 import re |
46 import optparse | 46 import optparse |
47 import os | |
47 import sys | 48 import sys |
48 | 49 |
49 CSV_COLUMN_NAMES = [ | 50 CSV_COLUMN_NAMES = [ |
50 'name', | 51 'name', |
51 'code', | 52 'code', |
53 'datatype', | 54 'datatype', |
54 'must', | 55 'must', |
55 'may', | 56 'may', |
56 'shouldnot', | 57 'shouldnot', |
57 'mustnot', | 58 'mustnot', |
58 'encrypt', | |
59 ] | 59 ] |
60 | 60 |
61 DERIVED_TO_BASE = { | 61 DERIVED_TO_BASE = { |
62 'Address': 'OctetString', # RFC 6733 section 4.3.1 | 62 'Address': 'OctetString', # RFC 6733 section 4.3.1 |
63 'Time': 'OctetString', # RFC 6733 section 4.3.1 | 63 'Time': 'OctetString', # RFC 6733 section 4.3.1 |
86 # - Allow avp-name to start with numbers (for 3GPP) | 86 # - Allow avp-name to start with numbers (for 3GPP) |
87 # - Allow '.' in avp-name, for existing dict_dcca_3gpp usage. | 87 # - Allow '.' in avp-name, for existing dict_dcca_3gpp usage. |
88 # TODO: if starts with digit, ensure contains a letter somewhere? | 88 # TODO: if starts with digit, ensure contains a letter somewhere? |
89 _name_re = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9-\.]*$') | 89 _name_re = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9-\.]*$') |
90 | 90 |
91 # Regex to validate flags: M, P, V, comma, space | |
92 _flags_re = re.compile(r'^[MPV, ]*$') | |
93 | |
91 __slots__ = CSV_COLUMN_NAMES + [ | 94 __slots__ = CSV_COLUMN_NAMES + [ |
92 'filename', 'linenum', 'standard', 'vendor', ] | 95 'filename', 'linenum', 'standard', 'vendor', ] |
93 | 96 |
94 def __init__(self, name, code, section, datatype, | 97 def __init__(self, name, code, section, datatype, |
95 must, may, shouldnot, mustnot, encrypt, | 98 must, may, shouldnot, mustnot, extra_cells, |
96 filename='', linenum=0, standard='', vendor=0): | 99 filename='', linenum=0, standard='', vendor=0): |
97 # Members from CSV row | 100 # Members from CSV row |
98 self.name = name | 101 self.name = name |
99 self.code = int(code) | 102 self.code = int(code) |
100 self.section = section | 103 self.section = section |
101 self.datatype = datatype | 104 self.datatype = datatype |
102 self.must = must | 105 self.must = must |
103 self.may = may | 106 self.may = may |
104 self.shouldnot = shouldnot | 107 self.shouldnot = shouldnot |
105 self.mustnot = mustnot | 108 self.mustnot = mustnot |
106 self.encrypt = encrypt | |
107 # Members from file state | 109 # Members from file state |
108 self.filename = filename | 110 self.filename = filename |
109 self.linenum = linenum | 111 self.linenum = linenum |
110 self.standard = standard | 112 self.standard = standard |
111 self.vendor = vendor | 113 self.vendor = vendor |
118 'OctetString', 'Integer32', 'Integer64', 'Unsigned32', | 120 'OctetString', 'Integer32', 'Integer64', 'Unsigned32', |
119 'Unsigned64', 'Float32', 'Float64', 'Grouped') | 121 'Unsigned64', 'Float32', 'Float64', 'Grouped') |
120 and self.datatype not in DERIVED_TO_BASE): | 122 and self.datatype not in DERIVED_TO_BASE): |
121 raise ValueError('Invalid AVP data type "{}"'.format( | 123 raise ValueError('Invalid AVP data type "{}"'.format( |
122 self.datatype)) | 124 self.datatype)) |
123 # TODO: validate must, may, shouldnot, mustnot | 125 for val, desc in [ |
126 (self.must, 'Must'), | |
127 (self.may, 'May'), | |
128 (self.shouldnot, 'Should Not'), | |
129 (self.mustnot, 'Must Not'), | |
130 ]: | |
131 if not self._flags_re.match(val): | |
132 raise ValueError('Invalid AVP Flags {} "{}"'.format(desc, val)) | |
124 | 133 |
125 @property | 134 @property |
126 def __dict__(self): | 135 def __dict__(self): |
127 return {s: getattr(self, s) for s in self.__slots__} | 136 return {s: getattr(self, s) for s in self.__slots__} |
128 | 137 |
214 """ | 223 """ |
215 | 224 |
216 COMMENT_WIDTH = 64 | 225 COMMENT_WIDTH = 64 |
217 | 226 |
218 def __init__(self): | 227 def __init__(self): |
228 self.filenames = [] | |
219 self.lines = [] | 229 self.lines = [] |
220 | 230 |
221 def next_file(self, filename): | 231 def next_file(self, filename): |
222 print('/* CSV file: {} */'.format(filename)) | 232 self.filenames.append(os.path.basename(filename)) |
223 | 233 |
224 def avp(self, avp): | 234 def avp(self, avp): |
225 comment = '{name}, {datatype}, code {code}'.format(**vars(avp)) | 235 comment = '{name}, {datatype}, code {code}'.format(**vars(avp)) |
226 if avp.section != '': | 236 if avp.section != '': |
227 comment += ', section {}'.format(avp.section) | 237 comment += ', section {}'.format(avp.section) |
274 def generate(self): | 284 def generate(self): |
275 self.print_header() | 285 self.print_header() |
276 self.print_comment('Start of generated data.') | 286 self.print_comment('Start of generated data.') |
277 self.print_comment('') | 287 self.print_comment('') |
278 self.print_comment('The following is created automatically with:') | 288 self.print_comment('The following is created automatically with:') |
279 self.print_comment(' csv_to_fd -p {}'.format(self.cls_name())) | 289 self.print_comment(' csv_to_fd -p {} {}'.format( |
290 self.cls_name(), ' '.join(self.filenames))) | |
280 self.print_comment('Changes will be lost during the next update.') | 291 self.print_comment('Changes will be lost during the next update.') |
281 self.print_comment('Do not modify;' | 292 self.print_comment('Do not modify;' |
282 ' modify the source .csv file instead.') | 293 ' modify the source .csv file instead.') |
283 self.print_header() | 294 self.print_header() |
284 print('') | 295 print('') |
409 | 420 |
410 # Process files | 421 # Process files |
411 for filename in args: | 422 for filename in args: |
412 avpproc.next_file(filename) | 423 avpproc.next_file(filename) |
413 with open(filename, 'r') as csvfile: | 424 with open(filename, 'r') as csvfile: |
414 csvdata = csv.DictReader(csvfile, CSV_COLUMN_NAMES) | 425 csvdata = csv.DictReader(csvfile, CSV_COLUMN_NAMES, |
426 restkey='extra_cells') | |
415 linenum = 0 | 427 linenum = 0 |
416 standard = '' | 428 standard = '' |
417 vendor = 0 | 429 vendor = 0 |
418 for row in csvdata: | 430 for row in csvdata: |
419 linenum += 1 | 431 linenum += 1 |