diff options
Diffstat (limited to 'scripts/docstr2man')
| -rwxr-xr-x | scripts/docstr2man | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/scripts/docstr2man b/scripts/docstr2man new file mode 100755 index 0000000..c58c3bd --- /dev/null +++ b/scripts/docstr2man @@ -0,0 +1,241 @@ +#!/bin/python3 + +# Copyright (C) 2024 olang mantainers +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +import sys +import re +from enum import Enum + + +class Field: + def __init__(self, kind, name, type, description): + self.kind = kind + self.name = name + self.type = type + self.description = description + + +NODE_KIND = Enum('NODE_KIND', ['FUNCTION', 'TYPEDEF_STRUCT', 'TYPEDEF_UNION', 'MACRO']) + +class DocString: + def __init__(self, comment_lines, code_node): + self.comment_lines = comment_lines + self.code_node = code_node + + def get_name(self): + return self.code_node.get_name() + + def get_title(self): + return self.comment_lines[0] + + def get_description(self): + description = "" + for line in self.comment_lines[2:]: + if line.startswith('@'): + break + elif len(description) > 0 and description[-1] != '\n': + description += ' ' + description += line + '\n' + return description.strip() + + def get_fields(self): + name = "" + description = "" + + fields_map = {} + + for line in self.comment_lines[2:]: + if line.startswith('@'): + if name: + fields_map[name] = description + name = line[1:].split(':')[0] + description = ":".join(line.split(':')[1:]) + elif name: + description += ' ' + line + + if name: + fields_map[name] = description + + for field in self.code_node.get_fields(): + if field.name in fields_map: + field.description = fields_map.get(field.name) + yield field + + +class MacroNode: + def __init__(self, lines): + self.lines = lines + self.kind = NODE_KIND.MACRO + + def get_name(self): + return self.lines[0].split(' ')[1] + + def get_fields(self): + return [] + + +class TypedefNode: + def __init__(self, lines, kind): + self.lines = lines + self.kind = kind + + def get_name(self): + return self.lines[-1].split(' ')[1].replace(';', '') + + def get_fields(self): + description = None + for line in self.lines[2:-1]: + line = line.strip() + + if line.startswith('/*'): + description = line.replace('/* ', '').replace('*/', '') + continue + + # FIXME: handle anonymous struct/inline + field_name = line.split(' ')[-1].replace(';', '') + field_type = ' '.join(line.split(' ')[:-1]) + + yield Field('field', field_name, field_type, description) + description = None + return [] + + +class FunctionNode: + def __init__(self, lines): + self.lines = lines + self.kind = NODE_KIND.FUNCTION + + def get_ret_type(self): + return self.lines[0] + + def get_name(self): + return self.lines[1].split('(')[0] + + def get_fields(self): + all_lines = "".join(self.lines) + args = all_lines.split('(')[1].split(')')[0].strip() + + for arg in re.finditer("(((?P<arg_type>((struct|union|enum) *)*([\\w]+ *\\**)) *(?P<arg_name>[\\w]+))[, ]*)", args): + yield Field('arg', arg.group('arg_name'), arg.group('arg_type').strip(), None) + + yield Field('return', 'return', self.get_ret_type(), None) + + + +def get_code_node(lines): + assert(len(lines) > 0) + if lines[0].startswith("#define"): + return MacroNode(lines) + if lines[0].startswith("typedef union"): + return TypedefNode(lines, NODE_KIND.TYPEDEF_UNION) + if lines[0].startswith("typedef struct"): + return TypedefNode(lines, NODE_KIND.TYPEDEF_STRUCT) + if lines[0].startswith("typedef enum"): + return TypedefNode(lines, NODE_KIND.TYPEDEF_STRUCT) + return FunctionNode(lines) + + +def extract_comment(lines, index): + if not lines[index].startswith("/**"): + return None, index + + comment_lines = [] + + for line in lines[index + 1:]: + if line.strip().startswith("*/"): + break + + comment_lines.append(line.strip()[2:]) + index += 1 + + return comment_lines, index + + +def extract_code(lines, index): + """ + This is highly tied to olang's code style and require the curly brackets + to be open into a new line. + """ + code_lines = [] + is_block = False + + for line in lines[index + 1:]: + code_lines.append(line) + if is_block: + if len(line) == 0: + continue + + if line[0] == '}': + break; + + if len(line) == 0: + break + + if line[0] == '{': + is_block = True + index += 1 + + return code_lines, index + + +def extract_docstring(file): + lines = file.split('\n') + + index = 0 + + while index < len(lines): + comment_lines, index = extract_comment(lines, index) + if comment_lines: + index += 1 + code_lines, index = extract_code(lines, index) + yield DocString(comment_lines, get_code_node(code_lines)) + index += 1 + + +def generate_docs(file_name): + with open(file_name) as file: + for docstring in extract_docstring(file.read()): + node = docstring.code_node + if node.kind == NODE_KIND.FUNCTION: + print(f".TH {docstring.get_name()} 3 {docstring.get_name()} 1988-12-31 \"Olang Hacker's manual\"") + print(f".SH NAME") + print(f"{docstring.get_name()} \\- {docstring.get_title()}") + + print(f".SH SYNOPSIS") + + print(f".nf") + print(f".B #include <{sys.argv[1].replace('include/', '')}>") + print(f".P") + + fields = list(node.get_fields())[:-1] + paren = f"{node.get_ret_type()} {docstring.get_name()}(" + paren_len = len(paren) + post = "," + for index, field in enumerate(fields): + if index == len(fields) - 1: + post = ");" + + print(f".BI \"{paren}{field.type} \" {field.name} {post}") + + paren = " " * paren_len + + print(f".fi") + print(f".SH DESCRIPTION") + print(f"{docstring.get_description()}") + + +if __name__ == "__main__": + generate_docs(sys.argv[1]) |
