asn2md.py 4.82 KB
Newer Older
ASN.1 Documenter's avatar
ASN.1 Documenter committed
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import argparse # parse arguments
import os.path  # getting extension from file
import sys      # output and stuff
import re       # for regular expressions

## extract doxygen-tag namespace
RE_MODULE = re.compile(
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	r'^(\w[\w-]*)[\s{](?:.|\n)*DEFINITIONS(?:.|\n)*::=\s*BEGIN'
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	, re.IGNORECASE | re.VERBOSE | re.DOTALL | re.MULTILINE)
RE_EXPORTS = re.compile(
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	r'^\s*EXPORTS .*?;' 
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	, re.VERBOSE | re.DOTALL | re.MULTILINE)

RE_IMPORTS = re.compile(
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	r'^\s*IMPORTS .*?;' 
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	, re.VERBOSE | re.DOTALL | re.MULTILINE)

RE_END = re.compile(
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	r'^\s*END\s*$' 
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	, re.VERBOSE | re.MULTILINE)

RE_DOXY_COMMENTS = re.compile(
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	r'^\s*--[-#!]\s'
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	, re.VERBOSE | re.MULTILINE)

RE_DOXY_C_COMMENTS = re.compile(
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	r'^\s*/\*\*\s(.*?)\*/'
	, re.VERBOSE | re.MULTILINE | re.DOTALL)
ASN.1 Documenter's avatar
ASN.1 Documenter committed

RE_COMMENT = re.compile(
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	r'--.*?(?:--|\n)'
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	, re.VERBOSE | re.MULTILINE)

ASN.1 Documenter's avatar
ASN.1 Documenter committed
RE_TYPE = re.compile(r'(([A-Z][\w-]*)\s*::=[\w \t]+(?:{(.*?)})?.*?)\n\s*\n'
	, re.VERBOSE | re.MULTILINE | re.DOTALL)
ASN.1 Documenter's avatar
ASN.1 Documenter committed

extTypes = {}

def indentString(s, indent):
	lines = s.splitlines()
	s = ''
	for c in lines:
		s += ''.ljust(indent, ' ') + c + '\n'
	return s

def parseText(content, indent=None):
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	# we need to keep only documenting text under the /** */ and --!
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	c = re.sub(r'^\s*--[-!#](:?$|(.*))', '/** \g<1>*/', content, 0, re.MULTILINE)
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	if c is not None:
		content = c
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	ret = ''
	for m in RE_DOXY_C_COMMENTS.finditer(content):
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		lines = m.group(1).splitlines()
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		for l in lines:
			l = re.sub(r'\s*\*', '', l, 1).rstrip()
ASN.1 Documenter's avatar
ASN.1 Documenter committed
			ret += ''.ljust(indent or 0) + l + '\n'
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	def repl_ref(m):
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		return '[**{0}**]({1}#{0})'.format(m.group(1), extTypes.get(m.group(1),''))
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	c = re.sub(r'@ref\s+([\w-]+)', repl_ref, ret, 0, re.VERBOSE | re.MULTILINE)
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	if c is not None:
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		ret = c
	c = re.sub(r'@class\s+([\w-]+)', '', ret, 0, re.VERBOSE | re.MULTILINE)
	if c is not None:
		ret = c
	c = re.sub(r'@brief\s+', '', ret, 0, re.VERBOSE | re.MULTILINE)
	if c is not None:
		ret = c
	return ret
ASN.1 Documenter's avatar
ASN.1 Documenter committed
cpos=0
def parseModule(mname, content):
	global cpos
	cpos = 0
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	ret = ''
	m = re.search(r'^\s*IMPORTS\s*(.*?);', content, re.VERBOSE | re.DOTALL | re.MULTILINE)
	if m is not None:
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		pos = 0
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		if m.group(1) is not None:
ASN.1 Documenter's avatar
ASN.1 Documenter committed
			ret += '## Imports:\n'
ASN.1 Documenter's avatar
ASN.1 Documenter committed
			s = m.group(1)
ASN.1 Documenter's avatar
ASN.1 Documenter committed
			for fm in re.finditer(r'^([,\s\w-]*?)FROM\s*([\w-]+)\s*({[^}]*}(?:\s+WITH\s+SUCCESSORS)?)?', s, re.VERBOSE | re.MULTILINE):
ASN.1 Documenter's avatar
ASN.1 Documenter committed
				imName = fm.group(2)
				for im in re.finditer(r'[^,\s]+', fm.group(1), 0):
ASN.1 Documenter's avatar
ASN.1 Documenter committed
					extTypes[im.group(0)] = imName+'.md'
ASN.1 Documenter's avatar
ASN.1 Documenter committed
				ret += ' * **{}** *{}*<br/>\n'.format(imName, re.sub(r'\s+', ' ', fm.group(3) or '', 0, 0))
ASN.1 Documenter's avatar
ASN.1 Documenter committed
				ret += parseText(s[pos:fm.start()], 3)+'\n'
				pos = fm.end()
			ret += parseText(s[pos:])
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		cpos = m.end()
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	m = re.search(r'^\s*EXPORTS.*?;', content, re.VERBOSE | re.DOTALL | re.MULTILINE)
	if m is not None:
		if cpos < m.end():
			cpos = m.end()
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	
	# parse types
	def repl_type (m, doc):
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		ret = '## <a name="{0}"></a>{0}\n\n'.format(m.group(2))
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		if doc is not None:
			ret += parseText(doc) + '\n\n'
		# parse fields and get out fields descriptions
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		if m.group(3) is not None:
ASN.1 Documenter's avatar
ASN.1 Documenter committed
			ret += 'Fields:\n'
			pos = 0
ASN.1 Documenter's avatar
ASN.1 Documenter committed
			for fm in re.finditer('^\s*([\w-]+?)\s+(OCTET\s+STRING|BIT\s+STRING|[A-Z][.\w-]+)?(.*?)(?:,|$)', m.group(3), re.VERBOSE | re.MULTILINE | re.DOTALL):
				if fm.group(2) is not None:
					f = fm.group(1).strip()
					t = fm.group(2).strip()
ASN.1 Documenter's avatar
ASN.1 Documenter committed
					if re.match('^OCTET\s+STRING|BIT\s+STRING|BOOLEAN|INTEGER|FLOAT', t, re.VERBOSE|re.MULTILINE) is not None:
ASN.1 Documenter's avatar
ASN.1 Documenter committed
						ret += '* {0} **{1}** {2}<br>\n'.format(f, t, fm.group(3) or '')
ASN.1 Documenter's avatar
ASN.1 Documenter committed
					else:
ASN.1 Documenter's avatar
ASN.1 Documenter committed
						ret += '* {0} [**{1}**]({2}#{1}) {3}<br>\n'.format(f, t, extTypes.get(t,''), fm.group(3) or '')
ASN.1 Documenter's avatar
ASN.1 Documenter committed
				ret += parseText(fm.string[pos:fm.start()], 3)
				pos = fm.end()
		
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		return ret + '```asn1\n' + re.sub(r'^\s*--.*\n', '', m.group(1).strip(), 0, re.MULTILINE) +'\n```\n\n'
ASN.1 Documenter's avatar
ASN.1 Documenter committed

ASN.1 Documenter's avatar
ASN.1 Documenter committed
	pos = 0
	for m in RE_TYPE.finditer(content[cpos:]):
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		ss = repl_type (m, m.string[pos:m.start()])
		ret += ss
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		pos = m.end()
	return ret


def parseAsn(outDir, content) :
	# iterate modules in the file
	pos= 0
	RE_MODULE=re.compile(r'^\s*([A-Z][\w-]*)\s*({.*?})?\s*DEFINITIONS.*?::=\s*?BEGIN(.*)END', re.VERBOSE | re.MULTILINE | re.DOTALL)
	cnt = 0
	for m in RE_MODULE.finditer(content):
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		ret = '# ASN.1 module {}\n OID: _{}_\n'.format(m.group(1), re.sub(r'\s+', ' ', m.group(2)))
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		ret += parseText(content[pos:m.start()]) + '\n'
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		if m.group(3) is not None:
			ret += parseModule(m.group(1), m.group(3))
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		ret += '\n\n'
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		open(outDir + '/' + m.group(1) + '.md', "w").write(ret)
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		pos = m.end()
ASN.1 Documenter's avatar
ASN.1 Documenter committed
		cnt += 1
ASN.1 Documenter's avatar
ASN.1 Documenter committed
	return cnt

def main():
	argc = len(sys.argv)
	if argc < 3:
		sys.stderr.write("Usage: {} <out dir> [ASN.1 files]\n".format(sys.argv[0]))
		exit(1)
	outDir = sys.argv[1]
	cnt = 0
	for a in sys.argv[2:]:
		try:
			content = open(a).read()
			cnt += parseAsn(outDir, content)
		except IOError as e:
			sys.stderr.write(e[1]+"\n")
	print("{} modules porcessed\n".format(cnt))

if __name__ == '__main__':
	main()