• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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