1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright (c) 2021-2025 Huawei Device Co., Ltd. 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 16import os 17import argparse 18import re 19from copy import deepcopy 20import sys 21 22parser = argparse.ArgumentParser() 23parser.add_argument("--spec", dest="spec", default="", help="path to spec") 24parser.add_argument("--rst", dest="rst_file", default="", help="path to .rst file") 25 26args = parser.parse_args() 27spec_dir = os.fsencode(args.spec.strip()) 28rst_ = os.fsencode(args.rst_file.strip()) 29 30 31default_snippet_tags = { 32 "marked": False, 33 "name": '', 34 "first_line_ind": 0, 35 "skip": False, 36 "not-subset": False, 37 "expect-cte": False, 38 "expect_output": [], 39 "unrecognized tags": [], 40 "part": 0, 41} 42 43 44def get_space_len(line): 45 length = 0 46 for i in line: 47 if i.isspace(): 48 length += 1 49 else: 50 return length 51 return 10**10 52 53 54def write_snippet_line(line, space_len, ets_file, ts_file=None): 55 if not line[0].isspace(): 56 return 57 if line.isspace(): 58 return 59 indent_cropped_line = line[space_len:] 60 61 ets_file.write(indent_cropped_line) 62 if ts_file: 63 ts_file.write(indent_cropped_line) 64 return 65 66 67def write_code_meta(expect_cte, frontend_status, expect_subset, file): 68 file.write("//" + expect_cte + "\n") 69 file.write("//" + frontend_status + "\n") 70 file.write("//" + expect_subset + "\n") 71 72 73def write_snippet(rst_lines, snippet_name, snippet_meta, previous_snippet_name, frontend_statuses): 74 sm = snippet_meta 75 if sm["skip"]: 76 return previous_snippet_name 77 78 for ind in frontend_statuses: 79 if sm["first_line_ind"] > ind: 80 frontend_status = frontend_statuses[ind]["status"] 81 82 83 if int(snippet_meta.get("part")) >= 1 and previous_snippet_name: 84 current_snippet_name = previous_snippet_name 85 else: 86 current_snippet_name = snippet_name 87 88 expect_cte = "cte" if snippet_meta["expect-cte"] else "sc" 89 expect_subset = "ns" if sm["not-subset"] else "" 90 snippet_ets = os.fdopen(os.open("snippets/" + current_snippet_name + ".ets", os.O_WRONLY, 0o755), "w+") 91 write_code_meta(expect_cte, frontend_status, expect_subset, snippet_ets) 92 93 if not snippet_meta["not-subset"] and not snippet_meta["expect-cte"]: 94 snippet_ts = os.fdopen(os.open("snippets/" + current_snippet_name + ".ts", os.O_WRONLY, 0o755), "w+") 95 write_code_meta(expect_cte, frontend_status, expect_subset, snippet_ts) 96 97 else: 98 snippet_ts = None 99 100 # + 2 because of ":lineos:" (Varvara, do some smarter thing pls) 101 space_len = 10**10 102 for line in rst_lines[sm["first_line_ind"] + 2 :]: 103 if not line[0].isspace(): 104 break 105 space_len = min(get_space_len(line), space_len) 106 write_snippet_line(line, space_len, snippet_ets, snippet_ts) 107 108 snippet_ets.close() 109 if snippet_ts: 110 snippet_ts.close() 111 return current_snippet_name 112 113 114def check_tag(trailed_line): 115 if trailed_line in ["skip", "not-subset"]: 116 return "flag" 117 if re.fullmatch("part\d+", trailed_line): 118 return "part" 119 if re.match("name: [a-z]+", trailed_line): 120 return "name" 121 if re.fullmatch("expect-cte:{0,1}", trailed_line): 122 return "cte" 123 return "unrecognized tag" 124 125 126def add_tag(trailed_line, snippet_tags): 127 tag = check_tag(trailed_line) 128 if tag == "flag": 129 snippet_tags[trailed_line] = True 130 elif tag == "part": 131 snippet_tags["part"] = re.findall(r"\d+", trailed_line)[0] 132 elif tag == "cte": 133 snippet_tags["expect-cte"] = True 134 elif tag == "unrecognized tag": 135 snippet_tags["unrecognized tags"].append(trailed_line) 136 elif tag == "name": 137 snippet_tags["name"] = trailed_line.replace('name: ', '') 138 139 140def parse_snippet_meta(meta_block_ind, rst_lines, snippets_meta): 141 snippet_tags = deepcopy(default_snippet_tags) 142 snippet_tags["marked"] = True 143 144 for ind, line in enumerate(rst_lines[meta_block_ind + 1 :]): 145 if "code-block:: typescript" in line: 146 first_line_ind = ind + meta_block_ind + 1 147 snippet_tags["first_line_ind"] = first_line_ind 148 snippets_meta[first_line_ind] = snippet_tags 149 return snippet_tags 150 151 trailed_line = line.strip() 152 if trailed_line: 153 add_tag(trailed_line, snippet_tags) 154 return snippet_tags 155 156 157def parse_frontend_meta(rst_lines): 158 theme_indices = [ 159 i for i, x in enumerate(rst_lines) if re.fullmatch(r"[=]+", x.strip()) 160 or re.fullmatch(r"[\*]+", x.strip()) 161 or re.fullmatch(r"[\#]+", x.strip()) 162 or re.fullmatch(r"[-]+", x.strip()) 163 ] 164 theme_indices = [0] + theme_indices + [len(rst_lines)] 165 166 frontend_statuses = {} 167 for i in range(len(theme_indices) - 1): 168 frontend_status_line = [ 169 rst_lines[j].split(":")[1].strip() 170 for j in range(theme_indices[i], theme_indices[i + 1]) 171 if re.match(r"\s*frontend_status:.*", rst_lines[j]) 172 ] 173 frontend_statuses[theme_indices[i]] = {"end_theme" : theme_indices[i + 1], 174 "status" : frontend_status_line[0] 175 if frontend_status_line else "Partly"} 176 177 return frontend_statuses 178 179 180def print_error(mark, filename, line_idx): 181 print("{mark} {fname}::{line_idx} {mark}".format( 182 fname=filename, line_idx=line_idx, mark=mark), end=' ') 183 184 185def check_name(names, name, filename, i, correct_tags): 186 if name in names: 187 print_error("***", filename, i) 188 print("Snippet name in not unique") 189 return False 190 else: 191 names[name] = True 192 return correct_tags 193 194 195def check_snippets_meta(filename, snippets_meta): 196 correct_tags = True 197 names = {} 198 for i in snippets_meta: 199 sm = snippets_meta[i] 200 if not sm['marked']: 201 print_error("!!!", filename, i) 202 print("No testing options for snippet") 203 correct_tags = False 204 continue 205 206 if sm["expect-cte"] and sm["not-subset"]: 207 print_error("???", filename, i) 208 print("Incorrect options set. Please check meta docs") 209 correct_tags = False 210 211 if sm["unrecognized tags"]: 212 print_error("!!!", filename, i) 213 print("Unrecognized tags:", ' ,'.join(sm["unrecognized tags"])) 214 correct_tags = False 215 216 if sm["name"]: 217 correct_tags = check_name(names, sm['name'], 218 filename, i, correct_tags) 219 return correct_tags 220 221 222def write_snippets_from_rst(rst_lines, filename, skiplist): 223 frontend_statuses = parse_frontend_meta(rst_lines) 224 225 code_block_indices = [ 226 i for i, x in enumerate(rst_lines) if x.strip() == ".. code-block:: typescript" 227 ] 228 meta_block_indices = [i for i, x in enumerate(rst_lines) if x.strip() == ".. code-block-meta:"] 229 snippets_meta = {int(i): deepcopy(default_snippet_tags) for i in code_block_indices} 230 for i in snippets_meta: 231 snippets_meta[i]["first_line_ind"] = i 232 233 for i in meta_block_indices: 234 parse_snippet_meta(i, rst_lines, snippets_meta) 235 previous_snippet_name = "" 236 237 if not check_snippets_meta(filename, snippets_meta): 238 return False 239 240 for i in code_block_indices: 241 if snippets_meta[i]['name'] in skiplist: 242 continue 243 snippet_name = "{fname}_{first_line_idx}".format( 244 fname=filename, first_line_idx=i 245 ) 246 previous_snippet_name = write_snippet( 247 rst_lines, snippet_name, snippets_meta.get(i), 248 previous_snippet_name, frontend_statuses 249 ) 250 return True 251 252 253def parse_skiplist(): 254 with open('skiplist', 'r') as skiplist: 255 global SKIP_NAMES 256 SKIP_NAMES = skiplist.readlines() 257 return SKIP_NAMES 258 259 260def parse_dir(skiped_names): 261 result = True 262 for file in os.listdir(spec_dir): 263 if not parse_file(skiped_names, file, os.path.join(spec_dir, file)): 264 result = False 265 continue 266 parse_file(skiped_names, file, os.path.join(spec_dir, file)) 267 return result 268 269 270def parse_file(skiped_names, file, path=""): 271 filename = os.fsdecode(file) 272 result = True 273 if filename.endswith(".rst"): 274 with open(path) as rst_file: 275 if not write_snippets_from_rst(rst_file.readlines(), os.path.splitext(filename)[0], skiped_names): 276 result = False 277 write_snippets_from_rst(rst_file.readlines(), os.path.splitext(filename)[0], skiped_names) 278 return result 279 280SKIP_NAMES = parse_skiplist() 281 282if args.rst_file: 283 if not parse_file(SKIP_NAMES, os.path.basename(args.rst_file), rst_): 284 raise NameError("incorrect snippets meta") 285 parse_file(SKIP_NAMES, os.path.basename(args.rst_file), rst_) 286if args.spec: 287 if not parse_dir(SKIP_NAMES): 288 raise NameError("incorrect snippets meta") 289 parse_dir(SKIP_NAMES) 290 291if args.rst_file: 292 parse_file(SKIP_NAMES, os.path.basename(args.rst_file), rst_) 293if args.spec: 294 parse_dir(SKIP_NAMES) 295