tp: Create stdlib utils python lib

Bug: 255535171
Change-Id: I7c173dac52ee70d59d6495f10bf68dfad89c2567
diff --git a/tools/gen_stdlib_docs_json.py b/tools/gen_stdlib_docs_json.py
index 2b6b8a9..e6953d0 100755
--- a/tools/gen_stdlib_docs_json.py
+++ b/tools/gen_stdlib_docs_json.py
@@ -17,311 +17,13 @@
 import os
 import sys
 import json
-import re
 from collections import defaultdict
-from sql_modules_utils import *
-from typing import Union, List
+from typing import Dict
 
-# Creates a JSON file with documentation for stdlib files.
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.append(os.path.join(ROOT_DIR))
 
-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>
-'''
-
-
-# Stores documentation for CREATE {TABLE|VIEW} with comment split into
-# segments.
-class TableViewDocs:
-
-  def __init__(self, name: str, type: str, desc: List[str], columns: List[str]):
-    self.name = name
-    self.type = type
-    self.desc = desc
-    self.columns = columns
-
-  # Contructs new TableViewDocs from whole comment, by splitting it on typed
-  # lines.
-  @staticmethod
-  def create_from_comment(comment: List[str],
-                          matches: tuple) -> "TableViewDocs":
-    obj_type, name = matches[:2]
-
-    # Ignore internal tables and views.
-    if re.match(r"^internal_.*", name):
-      return None
-
-    col_start = None
-
-    # Splits code into segments by finding beginning of column segment.
-    for i, line in enumerate(comment):
-      # Ignore only '--' line.
-      if line == "--":
-        continue
-
-      m = re.match(typed_comment_pattern(), line)
-
-      # Ignore untyped lines
-      if not m:
-        continue
-
-      line_type = m.group(1)
-      if line_type == "column" and not col_start:
-        col_start = i
-        continue
-
-    return TableViewDocs(name, obj_type, comment[:col_start],
-                         comment[col_start:])
-
-  def parse_comment(self) -> dict:
-    return {
-        'name': self.name,
-        'type': self.type,
-        'desc': parse_desc(self),
-        'cols': parse_columns(self)
-    }
-
-
-# Stores documentation for CREATE_FUNCTION with comment split into segments.
-class FunctionDocs:
-
-  def __init__(
-      self,
-      name: str,
-      desc: str,
-      args: List[str],
-      ret: List[str],
-  ):
-    self.name = name
-    self.desc = desc
-    self.args = args
-    self.ret = ret
-
-  @staticmethod
-  def create_from_comment(comment: List[str], matches: tuple) -> "FunctionDocs":
-    name = matches[0]
-
-    # Ignore internal functions.
-    if re.match(r"^INTERNAL_.*", name):
-      return None
-
-    start_args, start_ret = None, None
-
-    # Splits code into segments by finding beginning of args and ret segments.
-    for i, line in enumerate(comment):
-      # Ignore only '--' line.
-      if line == "--":
-        continue
-
-      m = re.match(typed_comment_pattern(), line)
-
-      # Ignore untyped lines
-      if not m:
-        continue
-
-      line_type = m.group(1)
-      if line_type == "arg" and not start_args:
-        start_args = i
-        continue
-
-      if line_type == "ret" and not start_ret:
-        start_ret = i
-        continue
-
-    return FunctionDocs(name, comment[:start_args],
-                        comment[start_args:start_ret], comment[start_ret:])
-
-  def parse_comment(self) -> dict:
-    ret_type, ret_desc = parse_ret(self)
-    return {
-        'name': self.name,
-        'desc': parse_desc(self),
-        'args': parse_args(self),
-        'return_type': ret_type,
-        'return_desc': ret_desc
-    }
-
-
-# Stores documentation for CREATE_VIEW_FUNCTION with comment split into
-# segments.
-class ViewFunctionDocs:
-
-  def __init__(
-      self,
-      name: str,
-      desc: List[str],
-      args: List[str],
-      columns: List[str],
-  ):
-    self.name = name
-    self.desc = desc
-    self.args = args
-    self.columns = columns
-
-  @staticmethod
-  def create_from_comment(comment: List[str],
-                          matches: tuple) -> "ViewFunctionDocs":
-    name = matches[0]
-
-    # Ignore internal functions.
-    if re.match(r"^INTERNAL_.*", name):
-      return None
-
-    start_args, start_cols = None, None
-
-    # Splits code into segments by finding beginning of args and cols segments.
-    for i, line in enumerate(comment):
-      # Ignore only '--' line.
-      if line == "--":
-        continue
-
-      m = re.match(typed_comment_pattern(), line)
-
-      # Ignore untyped lines
-      if not m:
-        continue
-
-      line_type = m.group(1)
-      if line_type == "arg" and not start_args:
-        start_args = i
-        continue
-
-      if line_type == "column" and not start_cols:
-        start_cols = i
-        continue
-
-    return ViewFunctionDocs(
-        name,
-        comment[:start_args],
-        comment[start_args:start_cols],
-        comment[start_cols:],
-    )
-
-  def parse_comment(self) -> dict:
-    return {
-        'name': self.name,
-        'desc': parse_desc(self),
-        'args': parse_args(self),
-        'cols': parse_columns(self)
-    }
-
-
-def get_text(line: str, no_break_line: bool = True) -> str:
-  line = line.lstrip('--').strip()
-  if not line:
-    return '' if no_break_line else '\n'
-  return line
-
-
-def parse_desc(docs: Union["TableViewDocs", "FunctionDocs", "ViewFunctionDocs"]
-              ) -> str:
-  desc_lines = [get_text(line, False) for line in docs.desc]
-  return ' '.join(desc_lines).strip('\n').strip()
-
-
-# Whether comment segment about columns contain proper schema. Can be matched
-# against parsed SQL data by setting `use_data_from_sql`.
-def parse_columns(docs: Union["TableViewDocs", "ViewFunctionDocs"]) -> dict:
-  cols = {}
-  last_col = None
-  last_desc = []
-  for line in docs.columns:
-    # Ignore only '--' line.
-    if line == "--" or not line.startswith("-- @column"):
-      last_desc.append(get_text(line))
-      continue
-
-    # Look for '-- @column' line as a column description
-    m = re.match(column_pattern(), line)
-    if last_col:
-      cols[last_col] = ' '.join(last_desc)
-    last_col, last_desc = m.group(1), [m.group(2)]
-
-  cols[last_col] = ' '.join(last_desc)
-  return cols
-
-
-def parse_args(docs: "FunctionDocs") -> dict:
-  args = {}
-  last_arg, last_desc, last_type = None, [], None
-  for line in docs.args:
-    # Ignore only '--' line.
-    if line == "--" or not line.startswith("-- @arg"):
-      last_desc.append(get_text(line))
-      continue
-
-    m = re.match(args_pattern(), line)
-    if last_arg:
-      args[last_arg] = {'type': last_type, 'desc': ' '.join(last_desc)}
-    last_arg, last_type, last_desc = m.group(1), m.group(2), [m.group(3)]
-
-  args[last_arg] = {'type': last_type, 'desc': ' '.join(last_desc)}
-  return args
-
-
-# Whether comment segment about return contain proper schema. Matches against
-# parsed SQL data.
-def parse_ret(docs: "FunctionDocs") -> tuple:
-  desc = []
-  for line in docs.ret:
-    # Ignore only '--' line.
-    if line == "--" or not line.startswith("-- @ret"):
-      desc.append(get_text(line))
-
-    m = re.match(function_return_pattern(), line)
-    ret_type, desc = m.group(1), [m.group(2)]
-  return (ret_type, ' '.join(desc))
-
-
-# After matching file to pattern, fetches and validates related documentation.
-def parse_docs_for_sql_object_type(sql: str, pattern: str,
-                                   docs_object: type) -> List:
-  ret = []
-  line_id_to_match = match_pattern(pattern, sql)
-  lines = sql.split("\n")
-  for line_id, matches in line_id_to_match.items():
-    # Fetch comment by looking at lines over beginning of match in reverse
-    # order.
-    comment = fetch_comment(lines[line_id - 1::-1])
-    docs = docs_object.create_from_comment(comment, matches)
-    if docs:
-      ret.append(docs.parse_comment())
-
-  return ret
-
-
-def parse_file(sql: str):
-  return {
-      'imports':
-          parse_docs_for_sql_object_type(sql, create_table_view_pattern(),
-                                         TableViewDocs),
-      'functions':
-          parse_docs_for_sql_object_type(sql, create_function_pattern(),
-                                         FunctionDocs),
-      'view_functions':
-          parse_docs_for_sql_object_type(sql, create_view_function_pattern(),
-                                         ViewFunctionDocs)
-  }
+from python.generators.stdlib_docs.stdlib import *
 
 
 def main():
@@ -345,20 +47,19 @@
     sql_files = args.sql_files
 
   # Extract the SQL output from each file.
-  sql_outputs = {}
+  sql_outputs: Dict[str, str] = {}
   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("/", ".")
 
-    docs = parse_file(sql)
+    docs = parse_file_to_dict(path, sql)[0]
     if not any(docs.values()):
       continue
     file_dict = {'import_key': import_key, **docs}