• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright (C) 2022 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from __future__ import absolute_import
17from __future__ import division
18from __future__ import print_function
19
20import re
21from typing import List, Tuple
22
23Errors = List[str]
24CommentLines = List[str]
25
26LOWER_NAME = r'[a-z_\d]*'
27UPPER_NAME = r'[A-Z_\d]*'
28ANY_WORDS = r'[A-Za-z_\d, \n]*'
29TYPE = r'[A-Z]*'
30SQL = r'[\s\S]*?'
31
32Pattern = {
33    'create_table_view': (
34        # Match create table/view and catch type
35        r'CREATE (?:VIRTUAL )?(TABLE|VIEW)?(?:IF NOT EXISTS)?\s*'
36        # Catch the name
37        fr'({LOWER_NAME})\s*(?:AS|USING)?.*'),
38    'create_function': (
39        r"SELECT\s*CREATE_FUNCTION\(\s*"
40        # Function name: we are matching everything [A-Z]* between ' and ).
41        fr"'\s*({UPPER_NAME})\s*\("
42        # Args: anything before closing bracket with '.
43        fr"({ANY_WORDS})\)',\s*"
44        # Type: [A-Z]* between two '.
45        fr"'({TYPE})',\s*"
46        # Sql: Anything between ' and ');. We are catching \'.
47        fr"'({SQL})'\s*\);"),
48    'create_view_function': (
49        r"SELECT\s*CREATE_VIEW_FUNCTION\(\s*"
50        # Function name: we are matching everything [A-Z]* between ' and ).
51        fr"'({UPPER_NAME})\s*\("
52        # Args: anything before closing bracket with '.
53        fr"({ANY_WORDS})\)',\s*"
54        # Return columns: anything between two '.
55        fr"'\s*({ANY_WORDS})',\s*"
56        # Sql: Anything between ' and ');. We are catching \'.
57        fr"'({SQL})'\s*\);"),
58    'column': fr'^-- @column\s*({LOWER_NAME})\s*({ANY_WORDS})',
59    'arg_str': fr"\s*({LOWER_NAME})\s*({TYPE})\s*",
60    'args': fr'^-- @arg\s*({LOWER_NAME})\s*({TYPE})\s*(.*)',
61    'return_arg': fr"^-- @ret ({TYPE})\s*(.*)",
62    'typed_line': fr'^-- @([a-z]*)'
63}
64
65
66def fetch_comment(lines_reversed: CommentLines) -> CommentLines:
67  comment_reversed = []
68  for line in lines_reversed:
69    # Break on empty line, as that suggests it is no longer a part of
70    # this comment.
71    if not line or not line.startswith('--'):
72      break
73
74    # The only  option left is a description, but it has to be after
75    # schema columns.
76    comment_reversed.append(line)
77
78  comment_reversed.reverse()
79  return comment_reversed
80
81
82def match_pattern(pattern: str, file_str: str) -> dict:
83  objects = {}
84  for match in re.finditer(pattern, file_str):
85    line_id = file_str[:match.start()].count('\n')
86    objects[line_id] = match.groups()
87  return dict(sorted(objects.items()))
88
89
90# Whether the name starts with module_name.
91def validate_name(name: str, module: str, upper: bool = False) -> Errors:
92  module_pattern = f"^{module}_.*"
93  if upper:
94    module_pattern = module_pattern.upper()
95  starts_with_module_name = re.match(module_pattern, name)
96  if module == "common":
97    if starts_with_module_name:
98      return [(f"Invalid name in module {name}. "
99               f"In module 'common' the name shouldn't "
100               f"start with '{module_pattern}'.\n")]
101  else:
102    if not starts_with_module_name:
103      return [(f"Invalid name in module {name}. "
104               f"Name has to begin with '{module_pattern}'.\n")]
105  return []
106
107
108# Parses string with multiple arguments with type separated by comma into dict.
109def parse_args_str(args_str: str) -> Tuple[dict, Errors]:
110  if not args_str.strip():
111    return None, []
112
113  errors = []
114  args = {}
115  for arg_str in args_str.split(","):
116    m = re.match(Pattern['arg_str'], arg_str)
117    if m is None:
118      errors.append(f"Wrong arguments formatting for '{arg_str}'\n")
119      continue
120    args[m.group(1)] = m.group(2)
121  return args, errors
122
123
124def get_text(line: str, no_break_line: bool = True) -> str:
125  line = line.lstrip('--').strip()
126  if not line:
127    return '' if no_break_line else '\n'
128  return line
129