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