db/fixup_kernel.sh: fix clear_user() handling
[smatch.git] / Documentation / sphinx / cdoc.py
blobcca5ad2822de8bc95f9d5d2bbd51375a1c45d383
1 #!/usr/bin/env python
2 # SPDX_License-Identifier: MIT
4 # Copyright (C) 2018 Luc Van Oostenryck <luc.vanoostenryck@gmail.com>
7 """
8 ///
9 // Sparse source files may contain documentation inside block-comments
10 // specifically formatted::
12 // ///
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
21 // block-comment::
23 // ///
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
49 """
51 import re
53 class Lines:
54 def __init__(self, lines):
55 # type: (Iterable[str]) -> None
56 self.index = 0
57 self.lines = lines
58 self.last = None
59 self.back = False
61 def __iter__(self):
62 # type: () -> Lines
63 return self
65 def memo(self):
66 # type: () -> Tuple[int, str]
67 return (self.index, self.last)
69 def __next__(self):
70 # type: () -> Tuple[int, str]
71 if not self.back:
72 self.last = next(self.lines).rstrip()
73 self.index += 1
74 else:
75 self.back = False
76 return self.memo()
77 def next(self):
78 return self.__next__()
80 def undo(self):
81 # type: () -> None
82 self.back = True
84 def readline_multi(lines, line):
85 # type: (Lines, str) -> str
86 try:
87 while True:
88 (n, l) = next(lines)
89 if not l.startswith('//\t'):
90 raise StopIteration
91 line += '\n' + l[3:]
92 except:
93 lines.undo()
94 return line
96 def readline_delim(lines, delim):
97 # type: (Lines, Tuple[str, str]) -> Tuple[int, str]
98 try:
99 (lineno, line) = next(lines)
100 if line == '':
101 raise StopIteration
102 while line[-1] not in delim:
103 (n, l) = next(lines)
104 line += ' ' + l.lstrip()
105 except:
106 line = ''
107 return (lineno, line)
110 def process_block(lines):
111 # type: (Lines) -> Dict[str, Any]
112 info = { }
113 tags = []
114 desc = []
115 state = 'START'
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) # /// ...
122 if m:
123 info['type'] = 'single'
124 info['desc'] = (n, m.group(1).rstrip())
125 return info
127 ## read the multi line comment
128 for (n, l) in lines:
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 '//'
134 else:
135 lines.undo() ## end of doc-block
136 break
138 if state == 'START': ## one-line short description
139 info['short'] = (n ,l)
140 state = 'PRE-TAGS'
141 elif state == 'PRE-TAGS': ## ignore empty line
142 if l != '':
143 lines.undo()
144 state = 'TAGS'
145 elif state == 'TAGS': ## match the '@tagnames'
146 m = re.match(r"^@([\w-]*)(:?\s*)(.*)", l)
147 if m:
148 tag = m.group(1)
149 sep = m.group(2)
150 ## FIXME/ warn if sep != ': '
151 l = m.group(3)
152 l = readline_multi(lines, l)
153 tags.append((n, tag, l))
154 else:
155 lines.undo()
156 state = 'PRE-DESC'
157 elif state == 'PRE-DESC': ## ignore the first empty lines
158 if l != '': ## or first line of description
159 desc = [n, l]
160 state = 'DESC'
161 elif state == 'DESC': ## remaining lines -> description
162 desc.append(l)
163 else:
164 pass
166 ## fill the info
167 if len(tags):
168 info['tags'] = tags
169 if len(desc):
170 info['desc'] = desc
172 ## read the item (function only for now)
173 (n, line) = readline_delim(lines, (')', ';'))
174 if len(line):
175 line = line.rstrip(';')
176 #print('function: %4d: %s' % (n, line))
177 info['type'] = 'func'
178 info['func'] = (n, line)
179 else:
180 info['type'] = 'bloc'
182 return info
184 def process_file(f):
185 # type: (TextIOWrapper) -> List[Dict[str, Any]]
186 docs = []
187 lines = Lines(f)
188 for (n, l) in lines:
189 #print("%4d: %s" % (n, l))
190 if l.startswith('///'):
191 info = process_block(lines)
192 docs.append(info)
194 return docs
196 def decorate(l):
197 # type: (str) -> str
198 l = re.sub(r"@(\w+)", "**\\1**", l)
199 return l
201 def convert_to_rst(info):
202 # type: (Dict[str, Any]) -> List[Tuple[int, str]]
203 lst = []
204 #print('info= ' + str(info))
205 typ = info.get('type', '???')
206 if typ == '???':
207 ## uh ?
208 pass
209 elif typ == 'bloc':
210 if 'short' in info:
211 (n, l) = info['short']
212 lst.append((n, l))
213 if 'desc' in info:
214 desc = info['desc']
215 n = desc[0] - 1
216 desc.append('')
217 for i in range(1, len(desc)):
218 l = desc[i]
219 lst.append((n+i, l))
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, ''))
224 elif typ == 'func':
225 (n, l) = info['func']
226 l = '.. c:function:: ' + l
227 lst.append((n, l + '\n'))
228 if 'short' in info:
229 (n, l) = info['short']
230 l = l[0].capitalize() + l[1:].strip('.')
231 if l[-1] != '?':
232 l = l + '.'
233 lst.append((n, '\t' + l + '\n'))
234 if 'tags' in info:
235 for (n, name, l) in info.get('tags', []):
236 if name != 'return':
237 name = 'param ' + name
238 l = decorate(l)
239 l = '\t:%s: %s' % (name, l)
240 l = '\n\t\t'.join(l.split('\n'))
241 lst.append((n, l))
242 lst.append((n+1, ''))
243 if 'desc' in info:
244 desc = info['desc']
245 n = desc[0]
246 r = ''
247 for l in desc[1:]:
248 l = decorate(l)
249 r += '\t' + l + '\n'
250 lst.append((n, r))
251 return lst
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) ]
257 return res
259 def dump_doc(lst):
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))
264 n += 1
266 if __name__ == '__main__':
267 """ extract the doc from stdin """
268 import sys
270 dump_doc(extract(sys.stdin, '<stdin>'))
273 from sphinx.util.docutils import switch_source_input
274 import docutils
275 import os
276 class CDocDirective(docutils.parsers.rst.Directive):
277 required_argument = 1
278 optional_arguments = 1
279 has_content = False
280 option_spec = {
283 def run(self):
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)
294 lineno += 1
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()
300 try:
301 with switch_source_input(self.state, lst):
302 self.state.nested_parse(lst, 0, node, match_titles=1)
303 finally:
304 memo.title_styles, memo.section_level = save
305 return node.children
307 def setup(app):
308 app.add_config_value('cdoc_srcdir', None, 'env')
309 app.add_directive_to_domain('c', 'autodoc', CDocDirective)
311 return {
312 'version': '1.0',
313 'parallel_read_safe': True,
316 # vim: tabstop=4