tp: Generate stdlib documentation
Result: https://screenshot.googleplex.com/4kNXxsoX66rVMwB
Bug:255535171
Change-Id: Ia4f0dcbdcaffc07158f40c3a1b024ce5f36824bd
diff --git a/tools/gen_stdlib_docs_json.py b/tools/gen_stdlib_docs_json.py
new file mode 100755
index 0000000..914720f
--- /dev/null
+++ b/tools/gen_stdlib_docs_json.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python3
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import os
+import sys
+import json
+import re
+from collections import defaultdict
+from sql_modules_utils import *
+
+# Creates a JSON file with documentation for stdlib files.
+
+REPLACEMENT_HEADER = '''/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ *******************************************************************************
+ * AUTOGENERATED BY tools/gen_stdlib_docs - DO NOT EDIT
+ *******************************************************************************
+ */
+
+ #include <string.h>
+'''
+
+
+def parse_columns(col_lines):
+ cols = {}
+ last_col_name = ""
+ for line in col_lines:
+ m = re.match(r'^-- @column[ \t]+(\w+)[ \t]+(.*)', line)
+ if m:
+ cols[m.group(1)] = m.group(2)
+ last_col_name = m.group(1)
+ else:
+ line = line.strip('--').lstrip()
+ if line:
+ cols[last_col_name] += " " + line
+ return cols
+
+
+def parse_comment(comment_lines):
+ text_lines = []
+ for i, line in enumerate(comment_lines):
+ # Break if columns definition started.
+ m = re.match(r'^-- @column[ \t]+(\w+)[ \t]+(.*)', line)
+ if m is not None:
+ return dict(
+ cols=parse_columns(comment_lines[i:]),
+ desc=" ".join(text_lines).strip('\n').strip())
+
+ line = line.strip('--').lstrip()
+ if line:
+ text_lines.append(line)
+ if not line:
+ text_lines.append('\n')
+ raise ValueError('No columns found')
+
+
+def parse_function_comment(comment):
+ text = []
+ next_block = 0
+
+ # Fetch function description
+ for i, line in enumerate(comment):
+ if line.startswith('-- @arg'):
+ next_block = i
+ break
+
+ line = line.strip('--').lstrip()
+ if line:
+ text.append(line)
+ if not line:
+ text.append('\n')
+
+ # Fetch function args
+ args = {}
+ last_arg_name = ""
+ for i, line in enumerate(comment[next_block:]):
+ if line.startswith('-- @ret'):
+ next_block = i
+ break
+
+ m = re.match(args_pattern(), line)
+ if m:
+ args[m.group(1)] = dict(type=m.group(2), desc=m.group(3))
+ last_arg_name = m.group(1)
+ else:
+ line = line.strip('--').lstrip()
+ if line:
+ args[last_arg_name]['desc'] += " " + line
+
+ # Fetch function ret
+ ret_type, ret_desc = "", ""
+ for line in comment[next_block:]:
+ m = re.match(function_return_pattern(), line)
+ if m:
+ ret_type, ret_desc = m.group(1), m.group(2)
+ else:
+ line = line.strip('--').lstrip()
+ if line:
+ ret_desc += " " + line
+
+ return dict(
+ args=args,
+ desc=" ".join(text).strip('\n').strip(),
+ return_type=ret_type,
+ return_desc=ret_desc)
+
+
+def docs_for_obj_in_file(sql):
+ line_to_match_dict = match_pattern(create_table_view_pattern(), sql)
+ if not line_to_match_dict:
+ return []
+
+ lines = sql.split('\n')
+ ret = []
+ for line_id, match_groups in line_to_match_dict.items():
+ name, obj_type = match_groups[1], match_groups[0]
+ if re.match(r'^internal_.*', name):
+ continue
+
+ comment = fetch_comment(lines[line_id - 1::-1])
+ ret.append(dict(name=name, type=obj_type, **parse_comment(comment)))
+
+ return ret
+
+
+def docs_for_functions_in_file(sql):
+ line_to_match_dict = match_pattern(create_function_pattern(), sql)
+ if not bool(line_to_match_dict):
+ return []
+
+ lines = sql.split('\n')
+ ret = []
+ for line_id, match_groups in line_to_match_dict.items():
+ name = match_groups[0]
+ if re.match(r'^INTERNAL_.*', name):
+ continue
+
+ comment = fetch_comment(lines[line_id - 1::-1])
+ ret.append(dict(name=name, **parse_function_comment(comment)))
+ return ret
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--json-out', required=True)
+ parser.add_argument('--input-list-file')
+ parser.add_argument('--root-dir', required=True)
+ parser.add_argument('sql_files', nargs='*')
+ args = parser.parse_args()
+
+ if args.input_list_file and args.sql_files:
+ print("Only one of --input-list-file and list of SQL files expected")
+ return 1
+
+ sql_files = []
+ if args.input_list_file:
+ with open(args.input_list_file, 'r') as input_list_file:
+ for line in input_list_file.read().splitlines():
+ sql_files.append(line)
+ else:
+ sql_files = args.sql_files
+
+ # Extract the SQL output from each file.
+ sql_outputs = {}
+ for file_name in sql_files:
+ with open(file_name, 'r') as f:
+ relpath = os.path.relpath(file_name, args.root_dir)
+ sql_outputs[relpath] = f.read()
+
+ modules = defaultdict(list)
+
+ # Add documentation from each file
+ for path, sql in sql_outputs.items():
+ module_name = path.split("/")[0]
+
+ import_key = path.split(".sql")[0].replace("/", ".")
+ if module_name == 'common':
+ import_key = import_key.split(".", 1)[-1]
+
+ file_dict = dict(
+ import_key=import_key,
+ imports=docs_for_obj_in_file(sql),
+ functions=docs_for_functions_in_file(sql))
+ modules[module_name].append(file_dict)
+
+ with open(args.json_out, 'w+') as f:
+ json.dump(modules, f, indent=4)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())