2 # SPDX_License-Identifier: MIT
4 # Copyright (C) 2018 Luc Van Oostenryck <luc.vanoostenryck@gmail.com>
9 // Sparse source files may contain documentation inside block-comments
10 // specifically formatted::
13 // // Here is some doc
14 // // and here is some more.
16 // More precisely, a doc-block begins with a line containing only ``///``
17 // and continues with lines beginning by ``//`` followed by either a space,
18 // a tab or nothing, the first space after ``//`` is ignored.
20 // For functions, some additional syntax must be respected inside the
24 // // <mandatory short one-line description>
25 // // <optional blank line>
26 // // @<1st parameter's name>: <description>
27 // // @<2nd parameter's name>: <long description
28 // // <tab>which needs multiple lines>
29 // // @return: <description> (absent for void functions)
30 // // <optional blank line>
31 // // <optional long multi-line description>
32 // int somefunction(void *ptr, int count);
34 // Inside the description fields, parameter's names can be referenced
35 // by using ``@<parameter name>``. A function doc-block must directly precede
36 // the function it documents. This function can span multiple lines and
37 // can either be a function prototype (ending with ``;``) or a
38 // function definition.
40 // Some future versions will also allow to document structures, unions,
41 // enums, typedefs and variables.
43 // This documentation can be extracted into a .rst document by using
44 // the *autodoc* directive::
46 // .. c:autodoc:: file.c
54 def __init__(self
, lines
):
55 # type: (Iterable[str]) -> None
66 # type: () -> Tuple[int, str]
67 return (self
.index
, self
.last
)
70 # type: () -> Tuple[int, str]
72 self
.last
= next(self
.lines
).rstrip()
78 return self
.__next
__()
84 def readline_multi(lines
, line
):
85 # type: (Lines, str) -> str
89 if not l
.startswith('//\t'):
96 def readline_delim(lines
, delim
):
97 # type: (Lines, Tuple[str, str]) -> Tuple[int, str]
99 (lineno
, line
) = next(lines
)
102 while line
[-1] not in delim
:
104 line
+= ' ' + l
.lstrip()
107 return (lineno
, line
)
110 def process_block(lines
):
111 # type: (Lines) -> Dict[str, Any]
117 (n
, l
) = lines
.memo()
118 #print('processing line ' + str(n) + ': ' + l)
120 ## is it a single line comment ?
121 m
= re
.match(r
"^///\s+(.+)$", l
) # /// ...
123 info
['type'] = 'single'
124 info
['desc'] = (n
, m
.group(1).rstrip())
127 ## read the multi line comment
129 #print('state %d: %4d: %s' % (state, n, l))
130 if l
.startswith('// '):
131 l
= l
[3:] ## strip leading '// '
132 elif l
.startswith('//\t') or l
== '//':
133 l
= l
[2:] ## strip leading '//'
135 lines
.undo() ## end of doc-block
138 if state
== 'START': ## one-line short description
139 info
['short'] = (n
,l
)
141 elif state
== 'PRE-TAGS': ## ignore empty line
145 elif state
== 'TAGS': ## match the '@tagnames'
146 m
= re
.match(r
"^@([\w-]*)(:?\s*)(.*)", l
)
150 ## FIXME/ warn if sep != ': '
152 l
= readline_multi(lines
, l
)
153 tags
.append((n
, tag
, l
))
157 elif state
== 'PRE-DESC': ## ignore the first empty lines
158 if l
!= '': ## or first line of description
161 elif state
== 'DESC': ## remaining lines -> description
172 ## read the item (function only for now)
173 (n
, line
) = readline_delim(lines
, (')', ';'))
175 line
= line
.rstrip(';')
176 #print('function: %4d: %s' % (n, line))
177 info
['type'] = 'func'
178 info
['func'] = (n
, line
)
180 info
['type'] = 'bloc'
185 # type: (TextIOWrapper) -> List[Dict[str, Any]]
189 #print("%4d: %s" % (n, l))
190 if l
.startswith('///'):
191 info
= process_block(lines
)
198 l
= re
.sub(r
"@(\w+)", "**\\1**", l
)
201 def convert_to_rst(info
):
202 # type: (Dict[str, Any]) -> List[Tuple[int, str]]
204 #print('info= ' + str(info))
205 typ
= info
.get('type', '???')
211 (n
, l
) = info
['short']
217 for i
in range(1, len(desc
)):
220 # auto add a blank line for a list
221 if re
.search(r
":$", desc
[i
]) and re
.search(r
"\S", desc
[i
+1]):
222 lst
.append((n
+i
, ''))
225 (n
, l
) = info
['func']
226 l
= '.. c:function:: ' + l
227 lst
.append((n
, l
+ '\n'))
229 (n
, l
) = info
['short']
230 l
= l
[0].capitalize() + l
[1:].strip('.')
233 lst
.append((n
, '\t' + l
+ '\n'))
235 for (n
, name
, l
) in info
.get('tags', []):
237 name
= 'param ' + name
239 l
= '\t:%s: %s' % (name
, l
)
240 l
= '\n\t\t'.join(l
.split('\n'))
242 lst
.append((n
+1, ''))
253 def extract(f
, filename
):
254 # type: (TextIOWrapper, str) -> List[Tuple[int, str]]
255 res
= process_file(f
)
256 res
= [ i
for r
in res
for i
in convert_to_rst(r
) ]
260 # type: (List[Tuple[int, str]]) -> None
261 for (n
, lines
) in lst
:
262 for l
in lines
.split('\n'):
263 print('%4d: %s' % (n
, l
))
266 if __name__
== '__main__':
267 """ extract the doc from stdin """
270 dump_doc(extract(sys
.stdin
, '<stdin>'))
273 from sphinx
.util
.docutils
import switch_source_input
276 class CDocDirective(docutils
.parsers
.rst
.Directive
):
277 required_argument
= 1
278 optional_arguments
= 1
284 env
= self
.state
.document
.settings
.env
285 filename
= os
.path
.join(env
.config
.cdoc_srcdir
, self
.arguments
[0])
286 env
.note_dependency(os
.path
.abspath(filename
))
288 ## create a (view) list from the extracted doc
289 lst
= docutils
.statemachine
.ViewList()
290 f
= open(filename
, 'r')
291 for (lineno
, lines
) in extract(f
, filename
):
292 for l
in lines
.split('\n'):
293 lst
.append(l
.expandtabs(8), filename
, lineno
)
296 ## let parse this new reST content
297 memo
= self
.state
.memo
298 save
= memo
.title_styles
, memo
.section_level
299 node
= docutils
.nodes
.section()
301 with
switch_source_input(self
.state
, lst
):
302 self
.state
.nested_parse(lst
, 0, node
, match_titles
=1)
304 memo
.title_styles
, memo
.section_level
= save
308 app
.add_config_value('cdoc_srcdir', None, 'env')
309 app
.add_directive_to_domain('c', 'autodoc', CDocDirective
)
313 'parallel_read_safe': True,