summaryrefslogtreecommitdiff
path: root/scripts/docstr2man
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/docstr2man')
-rwxr-xr-xscripts/docstr2man241
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])