1f08c3bdfSopenharmony_ci#!/usr/bin/env python
2f08c3bdfSopenharmony_ci# SPDX_License-Identifier: MIT
3f08c3bdfSopenharmony_ci#
4f08c3bdfSopenharmony_ci# Copyright (C) 2018 Luc Van Oostenryck <luc.vanoostenryck@gmail.com>
5f08c3bdfSopenharmony_ci#
6f08c3bdfSopenharmony_ci
7f08c3bdfSopenharmony_ci"""
8f08c3bdfSopenharmony_ci///
9f08c3bdfSopenharmony_ci// Sparse source files may contain documentation inside block-comments
10f08c3bdfSopenharmony_ci// specifically formatted::
11f08c3bdfSopenharmony_ci//
12f08c3bdfSopenharmony_ci// 	///
13f08c3bdfSopenharmony_ci// 	// Here is some doc
14f08c3bdfSopenharmony_ci// 	// and here is some more.
15f08c3bdfSopenharmony_ci//
16f08c3bdfSopenharmony_ci// More precisely, a doc-block begins with a line containing only ``///``
17f08c3bdfSopenharmony_ci// and continues with lines beginning by ``//`` followed by either a space,
18f08c3bdfSopenharmony_ci// a tab or nothing, the first space after ``//`` is ignored.
19f08c3bdfSopenharmony_ci//
20f08c3bdfSopenharmony_ci// For functions, some additional syntax must be respected inside the
21f08c3bdfSopenharmony_ci// block-comment::
22f08c3bdfSopenharmony_ci//
23f08c3bdfSopenharmony_ci// 	///
24f08c3bdfSopenharmony_ci// 	// <mandatory short one-line description>
25f08c3bdfSopenharmony_ci// 	// <optional blank line>
26f08c3bdfSopenharmony_ci// 	// @<1st parameter's name>: <description>
27f08c3bdfSopenharmony_ci// 	// @<2nd parameter's name>: <long description
28f08c3bdfSopenharmony_ci// 	// <tab>which needs multiple lines>
29f08c3bdfSopenharmony_ci// 	// @return: <description> (absent for void functions)
30f08c3bdfSopenharmony_ci// 	// <optional blank line>
31f08c3bdfSopenharmony_ci// 	// <optional long multi-line description>
32f08c3bdfSopenharmony_ci// 	int somefunction(void *ptr, int count);
33f08c3bdfSopenharmony_ci//
34f08c3bdfSopenharmony_ci// Inside the description fields, parameter's names can be referenced
35f08c3bdfSopenharmony_ci// by using ``@<parameter name>``. A function doc-block must directly precede
36f08c3bdfSopenharmony_ci// the function it documents. This function can span multiple lines and
37f08c3bdfSopenharmony_ci// can either be a function prototype (ending with ``;``) or a
38f08c3bdfSopenharmony_ci// function definition.
39f08c3bdfSopenharmony_ci//
40f08c3bdfSopenharmony_ci// Some future versions will also allow to document structures, unions,
41f08c3bdfSopenharmony_ci// enums, typedefs and variables.
42f08c3bdfSopenharmony_ci//
43f08c3bdfSopenharmony_ci// This documentation can be extracted into a .rst document by using
44f08c3bdfSopenharmony_ci// the *autodoc* directive::
45f08c3bdfSopenharmony_ci//
46f08c3bdfSopenharmony_ci// 	.. c:autodoc:: file.c
47f08c3bdfSopenharmony_ci//
48f08c3bdfSopenharmony_ci
49f08c3bdfSopenharmony_ci"""
50f08c3bdfSopenharmony_ci
51f08c3bdfSopenharmony_ciimport re
52f08c3bdfSopenharmony_ci
53f08c3bdfSopenharmony_ciclass Lines:
54f08c3bdfSopenharmony_ci	def __init__(self, lines):
55f08c3bdfSopenharmony_ci		# type: (Iterable[str]) -> None
56f08c3bdfSopenharmony_ci		self.index = 0
57f08c3bdfSopenharmony_ci		self.lines = lines
58f08c3bdfSopenharmony_ci		self.last = None
59f08c3bdfSopenharmony_ci		self.back = False
60f08c3bdfSopenharmony_ci
61f08c3bdfSopenharmony_ci	def __iter__(self):
62f08c3bdfSopenharmony_ci		# type: () -> Lines
63f08c3bdfSopenharmony_ci		return self
64f08c3bdfSopenharmony_ci
65f08c3bdfSopenharmony_ci	def memo(self):
66f08c3bdfSopenharmony_ci		# type: () -> Tuple[int, str]
67f08c3bdfSopenharmony_ci		return (self.index, self.last)
68f08c3bdfSopenharmony_ci
69f08c3bdfSopenharmony_ci	def __next__(self):
70f08c3bdfSopenharmony_ci		# type: () -> Tuple[int, str]
71f08c3bdfSopenharmony_ci		if not self.back:
72f08c3bdfSopenharmony_ci			self.last = next(self.lines).rstrip()
73f08c3bdfSopenharmony_ci			self.index += 1
74f08c3bdfSopenharmony_ci		else:
75f08c3bdfSopenharmony_ci			self.back = False
76f08c3bdfSopenharmony_ci		return self.memo()
77f08c3bdfSopenharmony_ci	def next(self):
78f08c3bdfSopenharmony_ci		return self.__next__()
79f08c3bdfSopenharmony_ci
80f08c3bdfSopenharmony_ci	def undo(self):
81f08c3bdfSopenharmony_ci		# type: () -> None
82f08c3bdfSopenharmony_ci		self.back = True
83f08c3bdfSopenharmony_ci
84f08c3bdfSopenharmony_cidef readline_multi(lines, line):
85f08c3bdfSopenharmony_ci	# type: (Lines, str) -> str
86f08c3bdfSopenharmony_ci	try:
87f08c3bdfSopenharmony_ci		while True:
88f08c3bdfSopenharmony_ci			(n, l) = next(lines)
89f08c3bdfSopenharmony_ci			if not l.startswith('//\t'):
90f08c3bdfSopenharmony_ci				raise StopIteration
91f08c3bdfSopenharmony_ci			line += '\n' + l[3:]
92f08c3bdfSopenharmony_ci	except:
93f08c3bdfSopenharmony_ci		lines.undo()
94f08c3bdfSopenharmony_ci	return line
95f08c3bdfSopenharmony_ci
96f08c3bdfSopenharmony_cidef readline_delim(lines, delim):
97f08c3bdfSopenharmony_ci	# type: (Lines, Tuple[str, str]) -> Tuple[int, str]
98f08c3bdfSopenharmony_ci	try:
99f08c3bdfSopenharmony_ci		(lineno, line) = next(lines)
100f08c3bdfSopenharmony_ci		if line == '':
101f08c3bdfSopenharmony_ci			raise StopIteration
102f08c3bdfSopenharmony_ci		while line[-1] not in delim:
103f08c3bdfSopenharmony_ci			(n, l) = next(lines)
104f08c3bdfSopenharmony_ci			line += ' ' + l.lstrip()
105f08c3bdfSopenharmony_ci	except:
106f08c3bdfSopenharmony_ci		line = ''
107f08c3bdfSopenharmony_ci	return (lineno, line)
108f08c3bdfSopenharmony_ci
109f08c3bdfSopenharmony_ci
110f08c3bdfSopenharmony_cidef process_block(lines):
111f08c3bdfSopenharmony_ci	# type: (Lines) -> Dict[str, Any]
112f08c3bdfSopenharmony_ci	info = { }
113f08c3bdfSopenharmony_ci	tags = []
114f08c3bdfSopenharmony_ci	desc = []
115f08c3bdfSopenharmony_ci	state = 'START'
116f08c3bdfSopenharmony_ci
117f08c3bdfSopenharmony_ci	(n, l) = lines.memo()
118f08c3bdfSopenharmony_ci	#print('processing line ' + str(n) + ': ' + l)
119f08c3bdfSopenharmony_ci
120f08c3bdfSopenharmony_ci	## is it a single line comment ?
121f08c3bdfSopenharmony_ci	m = re.match(r"^///\s+(.+)$", l)	# /// ...
122f08c3bdfSopenharmony_ci	if m:
123f08c3bdfSopenharmony_ci		info['type'] = 'single'
124f08c3bdfSopenharmony_ci		info['desc'] = (n, m.group(1).rstrip())
125f08c3bdfSopenharmony_ci		return info
126f08c3bdfSopenharmony_ci
127f08c3bdfSopenharmony_ci	## read the multi line comment
128f08c3bdfSopenharmony_ci	for (n, l) in lines:
129f08c3bdfSopenharmony_ci		#print('state %d: %4d: %s' % (state, n, l))
130f08c3bdfSopenharmony_ci		if l.startswith('// '):
131f08c3bdfSopenharmony_ci			l = l[3:]					## strip leading '// '
132f08c3bdfSopenharmony_ci		elif l.startswith('//\t') or l == '//':
133f08c3bdfSopenharmony_ci			l = l[2:]					## strip leading '//'
134f08c3bdfSopenharmony_ci		else:
135f08c3bdfSopenharmony_ci			lines.undo()				## end of doc-block
136f08c3bdfSopenharmony_ci			break
137f08c3bdfSopenharmony_ci
138f08c3bdfSopenharmony_ci		if state == 'START':			## one-line short description
139f08c3bdfSopenharmony_ci			info['short'] = (n ,l)
140f08c3bdfSopenharmony_ci			state = 'PRE-TAGS'
141f08c3bdfSopenharmony_ci		elif state == 'PRE-TAGS':		## ignore empty line
142f08c3bdfSopenharmony_ci			if l != '':
143f08c3bdfSopenharmony_ci				lines.undo()
144f08c3bdfSopenharmony_ci				state = 'TAGS'
145f08c3bdfSopenharmony_ci		elif state == 'TAGS':			## match the '@tagnames'
146f08c3bdfSopenharmony_ci			m = re.match(r"^@([\w-]*)(:?\s*)(.*)", l)
147f08c3bdfSopenharmony_ci			if m:
148f08c3bdfSopenharmony_ci				tag = m.group(1)
149f08c3bdfSopenharmony_ci				sep = m.group(2)
150f08c3bdfSopenharmony_ci				## FIXME/ warn if sep != ': '
151f08c3bdfSopenharmony_ci				l = m.group(3)
152f08c3bdfSopenharmony_ci				l = readline_multi(lines, l)
153f08c3bdfSopenharmony_ci				tags.append((n, tag, l))
154f08c3bdfSopenharmony_ci			else:
155f08c3bdfSopenharmony_ci				lines.undo()
156f08c3bdfSopenharmony_ci				state = 'PRE-DESC'
157f08c3bdfSopenharmony_ci		elif state == 'PRE-DESC':		## ignore the first empty lines
158f08c3bdfSopenharmony_ci			if l != '':					## or first line of description
159f08c3bdfSopenharmony_ci				desc = [n, l]
160f08c3bdfSopenharmony_ci				state = 'DESC'
161f08c3bdfSopenharmony_ci		elif state == 'DESC':			## remaining lines -> description
162f08c3bdfSopenharmony_ci			desc.append(l)
163f08c3bdfSopenharmony_ci		else:
164f08c3bdfSopenharmony_ci			pass
165f08c3bdfSopenharmony_ci
166f08c3bdfSopenharmony_ci	## fill the info
167f08c3bdfSopenharmony_ci	if len(tags):
168f08c3bdfSopenharmony_ci		info['tags'] = tags
169f08c3bdfSopenharmony_ci	if len(desc):
170f08c3bdfSopenharmony_ci		info['desc'] = desc
171f08c3bdfSopenharmony_ci
172f08c3bdfSopenharmony_ci	## read the item (function only for now)
173f08c3bdfSopenharmony_ci	(n, line) = readline_delim(lines, (')', ';'))
174f08c3bdfSopenharmony_ci	if len(line):
175f08c3bdfSopenharmony_ci		line = line.rstrip(';')
176f08c3bdfSopenharmony_ci		#print('function: %4d: %s' % (n, line))
177f08c3bdfSopenharmony_ci		info['type'] = 'func'
178f08c3bdfSopenharmony_ci		info['func'] = (n, line)
179f08c3bdfSopenharmony_ci	else:
180f08c3bdfSopenharmony_ci		info['type'] = 'bloc'
181f08c3bdfSopenharmony_ci
182f08c3bdfSopenharmony_ci	return info
183f08c3bdfSopenharmony_ci
184f08c3bdfSopenharmony_cidef process_file(f):
185f08c3bdfSopenharmony_ci	# type: (TextIOWrapper) -> List[Dict[str, Any]]
186f08c3bdfSopenharmony_ci	docs = []
187f08c3bdfSopenharmony_ci	lines = Lines(f)
188f08c3bdfSopenharmony_ci	for (n, l) in lines:
189f08c3bdfSopenharmony_ci		#print("%4d: %s" % (n, l))
190f08c3bdfSopenharmony_ci		if l.startswith('///'):
191f08c3bdfSopenharmony_ci			info = process_block(lines)
192f08c3bdfSopenharmony_ci			docs.append(info)
193f08c3bdfSopenharmony_ci
194f08c3bdfSopenharmony_ci	return docs
195f08c3bdfSopenharmony_ci
196f08c3bdfSopenharmony_cidef decorate(l):
197f08c3bdfSopenharmony_ci	# type: (str) -> str
198f08c3bdfSopenharmony_ci	l = re.sub(r"@(\w+)", "**\\1**", l)
199f08c3bdfSopenharmony_ci	return l
200f08c3bdfSopenharmony_ci
201f08c3bdfSopenharmony_cidef convert_to_rst(info):
202f08c3bdfSopenharmony_ci	# type: (Dict[str, Any]) -> List[Tuple[int, str]]
203f08c3bdfSopenharmony_ci	lst = []
204f08c3bdfSopenharmony_ci	#print('info= ' + str(info))
205f08c3bdfSopenharmony_ci	typ = info.get('type', '???')
206f08c3bdfSopenharmony_ci	if typ == '???':
207f08c3bdfSopenharmony_ci		## uh ?
208f08c3bdfSopenharmony_ci		pass
209f08c3bdfSopenharmony_ci	elif typ == 'bloc':
210f08c3bdfSopenharmony_ci		if 'short' in info:
211f08c3bdfSopenharmony_ci			(n, l) = info['short']
212f08c3bdfSopenharmony_ci			lst.append((n, l))
213f08c3bdfSopenharmony_ci		if 'desc' in info:
214f08c3bdfSopenharmony_ci			desc = info['desc']
215f08c3bdfSopenharmony_ci			n = desc[0] - 1
216f08c3bdfSopenharmony_ci			desc.append('')
217f08c3bdfSopenharmony_ci			for i in range(1, len(desc)):
218f08c3bdfSopenharmony_ci				l = desc[i]
219f08c3bdfSopenharmony_ci				lst.append((n+i, l))
220f08c3bdfSopenharmony_ci				# auto add a blank line for a list
221f08c3bdfSopenharmony_ci				if re.search(r":$", desc[i]) and re.search(r"\S", desc[i+1]):
222f08c3bdfSopenharmony_ci					lst.append((n+i, ''))
223f08c3bdfSopenharmony_ci
224f08c3bdfSopenharmony_ci	elif typ == 'func':
225f08c3bdfSopenharmony_ci		(n, l) = info['func']
226f08c3bdfSopenharmony_ci		l = '.. c:function:: ' + l
227f08c3bdfSopenharmony_ci		lst.append((n, l + '\n'))
228f08c3bdfSopenharmony_ci		if 'short' in info:
229f08c3bdfSopenharmony_ci			(n, l) = info['short']
230f08c3bdfSopenharmony_ci			l = l[0].capitalize() + l[1:].strip('.')
231f08c3bdfSopenharmony_ci			if l[-1] != '?':
232f08c3bdfSopenharmony_ci				l = l + '.'
233f08c3bdfSopenharmony_ci			lst.append((n, '\t' + l + '\n'))
234f08c3bdfSopenharmony_ci		if 'tags' in info:
235f08c3bdfSopenharmony_ci			for (n, name, l) in info.get('tags', []):
236f08c3bdfSopenharmony_ci				if name != 'return':
237f08c3bdfSopenharmony_ci					name = 'param ' + name
238f08c3bdfSopenharmony_ci				l = decorate(l)
239f08c3bdfSopenharmony_ci				l = '\t:%s: %s' % (name, l)
240f08c3bdfSopenharmony_ci				l = '\n\t\t'.join(l.split('\n'))
241f08c3bdfSopenharmony_ci				lst.append((n, l))
242f08c3bdfSopenharmony_ci			lst.append((n+1, ''))
243f08c3bdfSopenharmony_ci		if 'desc' in info:
244f08c3bdfSopenharmony_ci			desc = info['desc']
245f08c3bdfSopenharmony_ci			n = desc[0]
246f08c3bdfSopenharmony_ci			r = ''
247f08c3bdfSopenharmony_ci			for l in desc[1:]:
248f08c3bdfSopenharmony_ci				l = decorate(l)
249f08c3bdfSopenharmony_ci				r += '\t' + l + '\n'
250f08c3bdfSopenharmony_ci			lst.append((n, r))
251f08c3bdfSopenharmony_ci	return lst
252f08c3bdfSopenharmony_ci
253f08c3bdfSopenharmony_cidef extract(f, filename):
254f08c3bdfSopenharmony_ci	# type: (TextIOWrapper, str) -> List[Tuple[int, str]]
255f08c3bdfSopenharmony_ci	res = process_file(f)
256f08c3bdfSopenharmony_ci	res = [ i for r in res for i in convert_to_rst(r) ]
257f08c3bdfSopenharmony_ci	return res
258f08c3bdfSopenharmony_ci
259f08c3bdfSopenharmony_cidef dump_doc(lst):
260f08c3bdfSopenharmony_ci	# type: (List[Tuple[int, str]]) -> None
261f08c3bdfSopenharmony_ci	for (n, lines) in lst:
262f08c3bdfSopenharmony_ci		for l in lines.split('\n'):
263f08c3bdfSopenharmony_ci			print('%4d: %s' % (n, l))
264f08c3bdfSopenharmony_ci			n += 1
265f08c3bdfSopenharmony_ci
266f08c3bdfSopenharmony_ciif __name__ == '__main__':
267f08c3bdfSopenharmony_ci	""" extract the doc from stdin """
268f08c3bdfSopenharmony_ci	import sys
269f08c3bdfSopenharmony_ci
270f08c3bdfSopenharmony_ci	dump_doc(extract(sys.stdin, '<stdin>'))
271f08c3bdfSopenharmony_ci
272f08c3bdfSopenharmony_ci
273f08c3bdfSopenharmony_cifrom sphinx.util.docutils import switch_source_input
274f08c3bdfSopenharmony_ciimport docutils
275f08c3bdfSopenharmony_ciimport os
276f08c3bdfSopenharmony_ciclass CDocDirective(docutils.parsers.rst.Directive):
277f08c3bdfSopenharmony_ci	required_argument = 1
278f08c3bdfSopenharmony_ci	optional_arguments = 1
279f08c3bdfSopenharmony_ci	has_content = False
280f08c3bdfSopenharmony_ci	option_spec = {
281f08c3bdfSopenharmony_ci	}
282f08c3bdfSopenharmony_ci
283f08c3bdfSopenharmony_ci	def run(self):
284f08c3bdfSopenharmony_ci		env = self.state.document.settings.env
285f08c3bdfSopenharmony_ci		filename = os.path.join(env.config.cdoc_srcdir, self.arguments[0])
286f08c3bdfSopenharmony_ci		env.note_dependency(os.path.abspath(filename))
287f08c3bdfSopenharmony_ci
288f08c3bdfSopenharmony_ci		## create a (view) list from the extracted doc
289f08c3bdfSopenharmony_ci		lst = docutils.statemachine.ViewList()
290f08c3bdfSopenharmony_ci		f = open(filename, 'r')
291f08c3bdfSopenharmony_ci		for (lineno, lines) in extract(f, filename):
292f08c3bdfSopenharmony_ci			for l in lines.split('\n'):
293f08c3bdfSopenharmony_ci				lst.append(l.expandtabs(8), filename, lineno)
294f08c3bdfSopenharmony_ci				lineno += 1
295f08c3bdfSopenharmony_ci
296f08c3bdfSopenharmony_ci		## let parse this new reST content
297f08c3bdfSopenharmony_ci		memo = self.state.memo
298f08c3bdfSopenharmony_ci		save = memo.title_styles, memo.section_level
299f08c3bdfSopenharmony_ci		node = docutils.nodes.section()
300f08c3bdfSopenharmony_ci		try:
301f08c3bdfSopenharmony_ci			with switch_source_input(self.state, lst):
302f08c3bdfSopenharmony_ci				self.state.nested_parse(lst, 0, node, match_titles=1)
303f08c3bdfSopenharmony_ci		finally:
304f08c3bdfSopenharmony_ci			memo.title_styles, memo.section_level = save
305f08c3bdfSopenharmony_ci		return node.children
306f08c3bdfSopenharmony_ci
307f08c3bdfSopenharmony_cidef setup(app):
308f08c3bdfSopenharmony_ci	app.add_config_value('cdoc_srcdir', None, 'env')
309f08c3bdfSopenharmony_ci	app.add_directive_to_domain('c', 'autodoc', CDocDirective)
310f08c3bdfSopenharmony_ci
311f08c3bdfSopenharmony_ci	return {
312f08c3bdfSopenharmony_ci		'version': '1.0',
313f08c3bdfSopenharmony_ci		'parallel_read_safe': True,
314f08c3bdfSopenharmony_ci	}
315f08c3bdfSopenharmony_ci
316f08c3bdfSopenharmony_ci# vim: tabstop=4
317