Loading asn2md.py +49 −27 Original line number Diff line number Diff line Loading @@ -16,15 +16,12 @@ RE_MODULE = re.compile( r'^\s*([A-Z][\w-]*)\s*({.*?})?\s*DEFINITIONS.*?::=\s*? RE_SPACES = re.compile(r'\s+') RE_COMMENTS = re.compile(r'^\s*--.*?\n|--.*?(?:--|$)|/\*.*?\*/[\t ]*\n?', re.MULTILINE|re.DOTALL) RE_COMMENTS = re.compile(r'^\s*--.*?\n|(?:^\s*)?--.*?(?:--[\t ]*|$)|(?:^\s*)?/\*.*?\*/[\t ]*\n?', re.MULTILINE|re.DOTALL) RE_BASIC_TYPES = re.compile(r'^OCTET\s+STRING|BIT\s+STRING|BOOLEAN|INTEGER|FLOAT|SEQUENCE|SET|NULL') #RE_TYPE_BODY_1 = re.compile(r'.*?{(.*)}\s*WITH', re.MULTILINE|re.DOTALL) #RE_TYPE_BODY_2 = re.compile(r'.*?{(.*)}\s*(?:WITH.*|\(.*?\)|\s*$)', re.MULTILINE|re.DOTALL) RE_TYPE_BODY = re.compile(r'.*?{(.*)}\s*(?:WITH.*|\(.*?\)|\s*$)', re.MULTILINE|re.DOTALL) #RE_FIELDS = re.compile(r'^\s*(?:/\*\*.*?\*/)|^\s*([\w-]+?)\s+(OCTET\s+STRING|BIT\s+STRING|[A-Z][.\w-]+)?(.*?)(?:,((?:\s*--!?<.*?\n)*)|((?:--!?<.*?\n)*)$)', re.MULTILINE | re.DOTALL| re.VERBOSE) RE_FIELDS = re.compile(r'^\s*/\*.*?\*/|^\s*--\!.*?\n|^[\s&]*([\w-]+)\s+(OCTET\s+STRING|BIT\s+STRING|[A-Z][\w-]+)?((?:{[^}]*}|\([^)]*\)|.)*?)(?:,|(--)|$)', re.MULTILINE | re.DOTALL) RE_EXPORTS = re.compile(r'^\s*EXPORTS.*?;', re.DOTALL | re.MULTILINE) Loading @@ -40,8 +37,8 @@ RE_DOXY_ASN_COMMENTS = re.compile(r'^\s*--[-!#](:?$|\s(.*))', re.MULTILINE) RE_DOXY_C_COMMENTS = re.compile(r'^\s*/\*\*\s(.*?)\*/', re.MULTILINE | re.DOTALL) RE_DOXY_C_COMMENTS_I = re.compile(r'\s*\*+') RE_STRIPSTAR = re.compile(r'^\s*\*', re.MULTILINE) RE_STRIPSTAR = re.compile(r'^\s*\*?', re.MULTILINE) RE_DOXY_REF = re.compile(r'@ref\s+([\w-]+)') RE_DOXY_CLASS = re.compile(r'@(?:class|struct|details):?\s+([\w-]+)') Loading @@ -59,6 +56,7 @@ RE_DOXY_SECTION = re.compile(r"^\s*@(brief|note|(class|struct|param|field|detail # RE_TYPE = re.compile(r'(([A-Z][\w-]*)\s*::=[\w \t]+(?:{+(.*?)}+)?.*?)\n\s*\n', re.MULTILINE | re.DOTALL) RE_TYPE = re.compile(r'^\s*([A-Z][\w-]*)?\s*([{} \t:\w-]*?)?::=([\w \t]+.*?)\n\s*\n', re.MULTILINE | re.DOTALL) RE_OPTIONS = re.compile(r'^\s*@options[\s:]+(.+)', re.MULTILINE) RE_DOXY_OTHER = re.compile(r'\^(\w+)', re.MULTILINE) extTypes = {} cpos = 0 Loading Loading @@ -87,6 +85,8 @@ def parseText(content, indent=None): content = RE_DOXY_STRIP_SINGLE_TAG.sub('', content) content = re.sub('\^(\w+)', '<sup>\\1</sup>', content) return indentLines(content, indent) def parseInlineComments(content:str, indent=None): Loading Loading @@ -139,14 +139,16 @@ def parseModule(mname, content): # parse types def repl_type (m, doc): title = t = m.group(1) # type name title = typeName = m.group(1) # type name f_params = {} s_unit = '' s_category = '' s_note = '' s_revision = '' options = copy.copy(o_args) if doc : # doc is the prepending comment. Check if not None and not Empty # doc is the prepending comment. Check if not None and not Empty if doc : doc = parseDoxyComments(doc) # parse options Loading @@ -154,41 +156,51 @@ def parseModule(mname, content): nonlocal options if m.group(1) is not None: for o in m.group(1).split(','): setattr(options, o.strip(), True) setattr(options, o.strip().replace('-', '_'), True) return '' doc=RE_OPTIONS.sub(repl_options, doc) # parse @param, @details, @brief, etc... def repl_section (m): nonlocal title nonlocal t nonlocal typeName nonlocal f_params nonlocal s_note nonlocal options ret = '' l = m.group(4).lstrip(":, \t").lstrip('\n') if m.group(2) is not None: # this can be class|struct|details|param|field if m.group(3) == t: if m.group(3) == typeName: ret = parseText(l) else: if len(l): f_params[m.group(3)] = parseText(l, 2) elif m.group(1) == 'brief': if o_args.brief_as_title: if options.brief_as_title: title = parseText(l) else: ret = parseText(l) elif m.group(1) == 'note': s_note = '\n>>>\n' + 'NOTE: ' + parseText(l).rstrip() + '\n>>>\n' else: ret = m.string[m.start():m.end()] return ret doc = RE_DOXY_SECTION.sub(repl_section, doc) # parse @category XXX [, YYY, ZZZ] def repl_category(m): nonlocal s_category s_category = '\n **Categories**: ' if options.category_links: for l in m.group(1).split(','): s_category += '[{0}](#{1}) '.format(l.strip(), urlquote(l.strip())) else: for l in m.group(1).split(','): # s_category += '[{0}](#{1}) '.format(l.strip(), urlquote(l.strip())) s_category += l.strip() + ' ' s_category += '\n' return '' Loading @@ -209,9 +221,9 @@ def parseModule(mname, content): doc = '' ret = '' if t is not None: if typeName is not None: fields = '' ret = '\n### <a name="{0}"></a>{1}\n'.format(t, title) + parseText(doc) ret = '\n### <a name="{0}"></a>{1}\n'.format(typeName, title) + parseText(doc) # parse fields and get out fields descriptions if m.group(3) is not None: Loading @@ -234,29 +246,30 @@ def parseModule(mname, content): if f in f_params: field = f_params.pop(f) + '\n\n' if fm.group(2) is not None: if len(field) or not options.no_auto_fields: fTitle = 'Fields:\n' if len(field) or not o_args.no_auto_fields: t = fm.group(2).strip() if RE_BASIC_TYPES.match(t) is not None: field = '* {0} **{1}** {2}<br>\n'.format(f, t, ext) + field fTypeName = fm.group(2).strip() if RE_BASIC_TYPES.match(fTypeName) is not None: field = '* {0} **{1}** {2}<br>\n'.format(f, fTypeName, ext) + field else: field = '* {0} [**{1}**]({2}#{1}) {3}<br>\n'.format(f, t, extTypes.get(t,''), ext) + field field = '* {0} [**{1}**]({2}#{1}) {3}<br>\n'.format(f, fTypeName, extTypes.get(fTypeName,''), ext) + field else: fTitle = 'Values:\n' if len(field) or not o_args.no_auto_values: if len(field) or not options.no_auto_values: field = '* **{0}** {1}<br>\n'.format(f, ext) + field if len(field): field += parseText(fm.string[pos:fm.start()], 3) field += parseText(parseDoxyComments(fm.string[pos:fm.start()]).strip(), 3) pos = fm.end() if fm.group(4) is not None: # keep '--' for the next round pos -= 2 if len(field): fields += field field = '' if len(field): fields += parseInlineComments(typeBody[pos:], 3) # add all other fields defined as @params if 'force-all-fields' in options or 'force-all-fields' in o_args: if 'force_all_fields' in options or 'force_all_fields' in o_args: for f in f_params: fields += '* {}<br>\n{}\n\n'.format(f, f_params[f]) if len(fields): Loading Loading @@ -290,7 +303,15 @@ def parseAsn(outDir, content) : if m.group(3) is not None: ret += parseModule(m.group(1), m.group(3)) ret += '\n\n' open(outDir + '/' + m.group(1) + '.md', "w",encoding='utf-8').write(ret) try: f = open(outDir + '/' + m.group(1) + '.md', mode='w',encoding='utf-8') f.write(ret) except OSError as err: print("OS error: {0}".format(err)) except BaseException as err: print(f"Unexpected {err=}, {type(err)=}") finally: f.close() pos = m.end() cnt += 1 return cnt Loading @@ -303,6 +324,7 @@ def main(): ap.add_argument('--force-all-fields', '-f', default=False,action='store_true', help='Add all fields in the list even if empty') ap.add_argument('--no-auto-fields', '-F', default=False,action='store_true', help='Add fields only if @param or @field is defined') ap.add_argument('--no-auto-values', '-V', default=False,action='store_true', help='Do not add named values or enums') ap.add_argument('--category-links', '-c', default=False,action='store_true', help='Create links for categories') ap.add_argument('modules', action='store', nargs='+', help='ASN.1 files') o_args = ap.parse_args() Loading Loading
asn2md.py +49 −27 Original line number Diff line number Diff line Loading @@ -16,15 +16,12 @@ RE_MODULE = re.compile( r'^\s*([A-Z][\w-]*)\s*({.*?})?\s*DEFINITIONS.*?::=\s*? RE_SPACES = re.compile(r'\s+') RE_COMMENTS = re.compile(r'^\s*--.*?\n|--.*?(?:--|$)|/\*.*?\*/[\t ]*\n?', re.MULTILINE|re.DOTALL) RE_COMMENTS = re.compile(r'^\s*--.*?\n|(?:^\s*)?--.*?(?:--[\t ]*|$)|(?:^\s*)?/\*.*?\*/[\t ]*\n?', re.MULTILINE|re.DOTALL) RE_BASIC_TYPES = re.compile(r'^OCTET\s+STRING|BIT\s+STRING|BOOLEAN|INTEGER|FLOAT|SEQUENCE|SET|NULL') #RE_TYPE_BODY_1 = re.compile(r'.*?{(.*)}\s*WITH', re.MULTILINE|re.DOTALL) #RE_TYPE_BODY_2 = re.compile(r'.*?{(.*)}\s*(?:WITH.*|\(.*?\)|\s*$)', re.MULTILINE|re.DOTALL) RE_TYPE_BODY = re.compile(r'.*?{(.*)}\s*(?:WITH.*|\(.*?\)|\s*$)', re.MULTILINE|re.DOTALL) #RE_FIELDS = re.compile(r'^\s*(?:/\*\*.*?\*/)|^\s*([\w-]+?)\s+(OCTET\s+STRING|BIT\s+STRING|[A-Z][.\w-]+)?(.*?)(?:,((?:\s*--!?<.*?\n)*)|((?:--!?<.*?\n)*)$)', re.MULTILINE | re.DOTALL| re.VERBOSE) RE_FIELDS = re.compile(r'^\s*/\*.*?\*/|^\s*--\!.*?\n|^[\s&]*([\w-]+)\s+(OCTET\s+STRING|BIT\s+STRING|[A-Z][\w-]+)?((?:{[^}]*}|\([^)]*\)|.)*?)(?:,|(--)|$)', re.MULTILINE | re.DOTALL) RE_EXPORTS = re.compile(r'^\s*EXPORTS.*?;', re.DOTALL | re.MULTILINE) Loading @@ -40,8 +37,8 @@ RE_DOXY_ASN_COMMENTS = re.compile(r'^\s*--[-!#](:?$|\s(.*))', re.MULTILINE) RE_DOXY_C_COMMENTS = re.compile(r'^\s*/\*\*\s(.*?)\*/', re.MULTILINE | re.DOTALL) RE_DOXY_C_COMMENTS_I = re.compile(r'\s*\*+') RE_STRIPSTAR = re.compile(r'^\s*\*', re.MULTILINE) RE_STRIPSTAR = re.compile(r'^\s*\*?', re.MULTILINE) RE_DOXY_REF = re.compile(r'@ref\s+([\w-]+)') RE_DOXY_CLASS = re.compile(r'@(?:class|struct|details):?\s+([\w-]+)') Loading @@ -59,6 +56,7 @@ RE_DOXY_SECTION = re.compile(r"^\s*@(brief|note|(class|struct|param|field|detail # RE_TYPE = re.compile(r'(([A-Z][\w-]*)\s*::=[\w \t]+(?:{+(.*?)}+)?.*?)\n\s*\n', re.MULTILINE | re.DOTALL) RE_TYPE = re.compile(r'^\s*([A-Z][\w-]*)?\s*([{} \t:\w-]*?)?::=([\w \t]+.*?)\n\s*\n', re.MULTILINE | re.DOTALL) RE_OPTIONS = re.compile(r'^\s*@options[\s:]+(.+)', re.MULTILINE) RE_DOXY_OTHER = re.compile(r'\^(\w+)', re.MULTILINE) extTypes = {} cpos = 0 Loading Loading @@ -87,6 +85,8 @@ def parseText(content, indent=None): content = RE_DOXY_STRIP_SINGLE_TAG.sub('', content) content = re.sub('\^(\w+)', '<sup>\\1</sup>', content) return indentLines(content, indent) def parseInlineComments(content:str, indent=None): Loading Loading @@ -139,14 +139,16 @@ def parseModule(mname, content): # parse types def repl_type (m, doc): title = t = m.group(1) # type name title = typeName = m.group(1) # type name f_params = {} s_unit = '' s_category = '' s_note = '' s_revision = '' options = copy.copy(o_args) if doc : # doc is the prepending comment. Check if not None and not Empty # doc is the prepending comment. Check if not None and not Empty if doc : doc = parseDoxyComments(doc) # parse options Loading @@ -154,41 +156,51 @@ def parseModule(mname, content): nonlocal options if m.group(1) is not None: for o in m.group(1).split(','): setattr(options, o.strip(), True) setattr(options, o.strip().replace('-', '_'), True) return '' doc=RE_OPTIONS.sub(repl_options, doc) # parse @param, @details, @brief, etc... def repl_section (m): nonlocal title nonlocal t nonlocal typeName nonlocal f_params nonlocal s_note nonlocal options ret = '' l = m.group(4).lstrip(":, \t").lstrip('\n') if m.group(2) is not None: # this can be class|struct|details|param|field if m.group(3) == t: if m.group(3) == typeName: ret = parseText(l) else: if len(l): f_params[m.group(3)] = parseText(l, 2) elif m.group(1) == 'brief': if o_args.brief_as_title: if options.brief_as_title: title = parseText(l) else: ret = parseText(l) elif m.group(1) == 'note': s_note = '\n>>>\n' + 'NOTE: ' + parseText(l).rstrip() + '\n>>>\n' else: ret = m.string[m.start():m.end()] return ret doc = RE_DOXY_SECTION.sub(repl_section, doc) # parse @category XXX [, YYY, ZZZ] def repl_category(m): nonlocal s_category s_category = '\n **Categories**: ' if options.category_links: for l in m.group(1).split(','): s_category += '[{0}](#{1}) '.format(l.strip(), urlquote(l.strip())) else: for l in m.group(1).split(','): # s_category += '[{0}](#{1}) '.format(l.strip(), urlquote(l.strip())) s_category += l.strip() + ' ' s_category += '\n' return '' Loading @@ -209,9 +221,9 @@ def parseModule(mname, content): doc = '' ret = '' if t is not None: if typeName is not None: fields = '' ret = '\n### <a name="{0}"></a>{1}\n'.format(t, title) + parseText(doc) ret = '\n### <a name="{0}"></a>{1}\n'.format(typeName, title) + parseText(doc) # parse fields and get out fields descriptions if m.group(3) is not None: Loading @@ -234,29 +246,30 @@ def parseModule(mname, content): if f in f_params: field = f_params.pop(f) + '\n\n' if fm.group(2) is not None: if len(field) or not options.no_auto_fields: fTitle = 'Fields:\n' if len(field) or not o_args.no_auto_fields: t = fm.group(2).strip() if RE_BASIC_TYPES.match(t) is not None: field = '* {0} **{1}** {2}<br>\n'.format(f, t, ext) + field fTypeName = fm.group(2).strip() if RE_BASIC_TYPES.match(fTypeName) is not None: field = '* {0} **{1}** {2}<br>\n'.format(f, fTypeName, ext) + field else: field = '* {0} [**{1}**]({2}#{1}) {3}<br>\n'.format(f, t, extTypes.get(t,''), ext) + field field = '* {0} [**{1}**]({2}#{1}) {3}<br>\n'.format(f, fTypeName, extTypes.get(fTypeName,''), ext) + field else: fTitle = 'Values:\n' if len(field) or not o_args.no_auto_values: if len(field) or not options.no_auto_values: field = '* **{0}** {1}<br>\n'.format(f, ext) + field if len(field): field += parseText(fm.string[pos:fm.start()], 3) field += parseText(parseDoxyComments(fm.string[pos:fm.start()]).strip(), 3) pos = fm.end() if fm.group(4) is not None: # keep '--' for the next round pos -= 2 if len(field): fields += field field = '' if len(field): fields += parseInlineComments(typeBody[pos:], 3) # add all other fields defined as @params if 'force-all-fields' in options or 'force-all-fields' in o_args: if 'force_all_fields' in options or 'force_all_fields' in o_args: for f in f_params: fields += '* {}<br>\n{}\n\n'.format(f, f_params[f]) if len(fields): Loading Loading @@ -290,7 +303,15 @@ def parseAsn(outDir, content) : if m.group(3) is not None: ret += parseModule(m.group(1), m.group(3)) ret += '\n\n' open(outDir + '/' + m.group(1) + '.md', "w",encoding='utf-8').write(ret) try: f = open(outDir + '/' + m.group(1) + '.md', mode='w',encoding='utf-8') f.write(ret) except OSError as err: print("OS error: {0}".format(err)) except BaseException as err: print(f"Unexpected {err=}, {type(err)=}") finally: f.close() pos = m.end() cnt += 1 return cnt Loading @@ -303,6 +324,7 @@ def main(): ap.add_argument('--force-all-fields', '-f', default=False,action='store_true', help='Add all fields in the list even if empty') ap.add_argument('--no-auto-fields', '-F', default=False,action='store_true', help='Add fields only if @param or @field is defined') ap.add_argument('--no-auto-values', '-V', default=False,action='store_true', help='Do not add named values or enums') ap.add_argument('--category-links', '-c', default=False,action='store_true', help='Create links for categories') ap.add_argument('modules', action='store', nargs='+', help='ASN.1 files') o_args = ap.parse_args() Loading