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