• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -- coding: utf-8 --
3#
4# Copyright (c) 2024-2025 Huawei Device Co., Ltd.
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18from os import environ
19
20from runner.common_exceptions import MacroNotExpanded, ParameterNotFound
21from runner.logger import Log
22from runner.options.local_env import LocalEnv
23from runner.options.options import IOptions
24from runner.utils import expand_file_name, get_all_macros, has_macro, replace_macro
25
26_LOGGER = Log.get_logger(__file__)
27
28
29class Macros:
30    __SPACE_SUBSTITUTION = "%20"
31
32    @staticmethod
33    def add_steps_2_macro(macro: str) -> str:
34        steps = 'steps'
35        if macro.startswith(f'{steps}.') and not macro.startswith(f'{steps}.{steps}.'):
36            return f"{steps}.{macro}"
37        return macro
38
39    @staticmethod
40    def remove_parameters_and_minuses(macro: str) -> str:
41        parameters = "parameters."
42        if macro.find(parameters) >= 0:
43            macro = macro.replace(parameters, "")
44        return macro.replace("-", "_")
45
46    @classmethod
47    def expand_macros_in_path(cls, value: str, config: IOptions) -> str | None:
48        corrected = cls.correct_macro(value, config)
49        return expand_file_name(corrected) if isinstance(corrected, str) else None
50
51    @classmethod
52    def correct_macro(cls, raw_value: str, config: IOptions) -> str | list[str]:
53        """
54        Macro can be expanded into single value of str or to list of str
55        :param raw_value: the source line with one or several macros
56        :param config: object with options where macros are searched
57        :return: the line with expanded macros or list of lines
58
59        If a macro is expanded into list of str the 2 cases are possible:
60        - if a source line contains only 1 macro (expanded into list) and no other symbols:
61            in this case the list of str is returned
62        - if a source line contains several macros or 1 macro and other symbols:
63            in this case the list is converted into the str
64        """
65        result_str = raw_value
66        if not has_macro(result_str):
67            return result_str
68        result_str, not_found = cls.__process(result_str, raw_value, config)
69        remained_macros = [item for item in get_all_macros(result_str) if item not in not_found]
70        if len(remained_macros) > 0:
71            processed = cls.correct_macro(result_str, config)
72            if isinstance(processed, list):
73                result_str = " ".join(processed)
74            else:
75                result_str = processed
76        if result_str != raw_value:
77            _LOGGER.all(f"Corrected macro: '{raw_value}' => '{result_str}'")
78        result_list = [value.replace(cls.__SPACE_SUBSTITUTION, " ") for value in result_str.split()]
79        return result_list[0] if len(result_list) == 1 else result_list
80
81    @classmethod
82    def __process(cls, result: str, raw_value: str, config: IOptions) -> tuple[str, list[str]]:
83        not_found: list[str] = []
84        prop_value: str | list[str] | None = None
85        for macro in get_all_macros(result):
86            prop_value = environ.get(macro)
87            if prop_value is not None:
88                local_env = LocalEnv.get()
89                local_env.add(macro, str(prop_value))
90            prop_value = config.get_value(macro) if prop_value is None else prop_value
91            if prop_value is None:
92                macro3 = cls.remove_parameters_and_minuses(macro)
93                prop_value = config.get_value(macro3)
94            macro2 = cls.add_steps_2_macro(macro)
95            prop_value = config.get_value(macro2) if prop_value is None else prop_value
96            prop_value = config.get_value(cls.remove_parameters_and_minuses(macro2)) \
97                if prop_value is None else prop_value
98            if prop_value is None:
99                not_found.append(macro)
100                if macro != 'test-id':
101                    raise ParameterNotFound(
102                        f"Cannot expand macro '{macro}' at value '{raw_value}'. "
103                        "Check yaml path or provide the value as environment variable")
104                continue
105            if isinstance(prop_value, list):
106                prop_value_list = [value.replace(" ", cls.__SPACE_SUBSTITUTION) for value in prop_value]
107                prop_value = " ".join(prop_value_list)
108            if isinstance(prop_value, str) and prop_value.find(f"${{{macro}}}") != -1:
109                raise MacroNotExpanded(
110                    f"The macro '{macro}' at value '{raw_value}' is expanded into '{prop_value}'. "
111                    "Check yaml path or provide the value as environment variable")
112            result = replace_macro(str(result), macro, str(prop_value))
113        return result, not_found
114