1# Copyright 2021 The Meson development team 2# SPDX-license-identifier: Apache-2.0 3 4import re 5import os 6from pathlib import PurePath 7 8import typing as T 9 10from ...mesonlib import version_compare 11from ...interpreterbase import ( 12 ObjectHolder, 13 MesonOperator, 14 FeatureNew, 15 typed_operator, 16 noArgsFlattening, 17 noKwargs, 18 noPosargs, 19 typed_pos_args, 20 21 TYPE_var, 22 TYPE_kwargs, 23 24 InvalidArguments, 25) 26 27 28if T.TYPE_CHECKING: 29 # Object holders need the actual interpreter 30 from ...interpreter import Interpreter 31 32class StringHolder(ObjectHolder[str]): 33 def __init__(self, obj: str, interpreter: 'Interpreter') -> None: 34 super().__init__(obj, interpreter) 35 self.methods.update({ 36 'contains': self.contains_method, 37 'startswith': self.startswith_method, 38 'endswith': self.endswith_method, 39 'format': self.format_method, 40 'join': self.join_method, 41 'replace': self.replace_method, 42 'split': self.split_method, 43 'strip': self.strip_method, 44 'substring': self.substring_method, 45 'to_int': self.to_int_method, 46 'to_lower': self.to_lower_method, 47 'to_upper': self.to_upper_method, 48 'underscorify': self.underscorify_method, 49 'version_compare': self.version_compare_method, 50 }) 51 52 self.trivial_operators.update({ 53 # Arithmetic 54 MesonOperator.PLUS: (str, lambda x: self.held_object + x), 55 56 # Comparison 57 MesonOperator.EQUALS: (str, lambda x: self.held_object == x), 58 MesonOperator.NOT_EQUALS: (str, lambda x: self.held_object != x), 59 MesonOperator.GREATER: (str, lambda x: self.held_object > x), 60 MesonOperator.LESS: (str, lambda x: self.held_object < x), 61 MesonOperator.GREATER_EQUALS: (str, lambda x: self.held_object >= x), 62 MesonOperator.LESS_EQUALS: (str, lambda x: self.held_object <= x), 63 }) 64 65 # Use actual methods for functions that require additional checks 66 self.operators.update({ 67 MesonOperator.DIV: self.op_div, 68 MesonOperator.INDEX: self.op_index, 69 }) 70 71 def display_name(self) -> str: 72 return 'str' 73 74 @noKwargs 75 @typed_pos_args('str.contains', str) 76 def contains_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: 77 return self.held_object.find(args[0]) >= 0 78 79 @noKwargs 80 @typed_pos_args('str.startswith', str) 81 def startswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: 82 return self.held_object.startswith(args[0]) 83 84 @noKwargs 85 @typed_pos_args('str.endswith', str) 86 def endswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: 87 return self.held_object.endswith(args[0]) 88 89 @noArgsFlattening 90 @noKwargs 91 @typed_pos_args('str.format', varargs=object) 92 def format_method(self, args: T.Tuple[T.List[object]], kwargs: TYPE_kwargs) -> str: 93 arg_strings: T.List[str] = [] 94 for arg in args[0]: 95 if isinstance(arg, bool): # Python boolean is upper case. 96 arg = str(arg).lower() 97 arg_strings.append(str(arg)) 98 99 def arg_replace(match: T.Match[str]) -> str: 100 idx = int(match.group(1)) 101 if idx >= len(arg_strings): 102 raise InvalidArguments(f'Format placeholder @{idx}@ out of range.') 103 return arg_strings[idx] 104 105 return re.sub(r'@(\d+)@', arg_replace, self.held_object) 106 107 @noKwargs 108 @typed_pos_args('str.join', varargs=str) 109 def join_method(self, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> str: 110 return self.held_object.join(args[0]) 111 112 @noKwargs 113 @typed_pos_args('str.replace', str, str) 114 def replace_method(self, args: T.Tuple[str, str], kwargs: TYPE_kwargs) -> str: 115 return self.held_object.replace(args[0], args[1]) 116 117 @noKwargs 118 @typed_pos_args('str.split', optargs=[str]) 119 def split_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> T.List[str]: 120 return self.held_object.split(args[0]) 121 122 @noKwargs 123 @typed_pos_args('str.strip', optargs=[str]) 124 def strip_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> str: 125 return self.held_object.strip(args[0]) 126 127 @noKwargs 128 @typed_pos_args('str.substring', optargs=[int, int]) 129 def substring_method(self, args: T.Tuple[T.Optional[int], T.Optional[int]], kwargs: TYPE_kwargs) -> str: 130 start = args[0] if args[0] is not None else 0 131 end = args[1] if args[1] is not None else len(self.held_object) 132 return self.held_object[start:end] 133 134 @noKwargs 135 @noPosargs 136 def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: 137 try: 138 return int(self.held_object) 139 except ValueError: 140 raise InvalidArguments(f'String {self.held_object!r} cannot be converted to int') 141 142 @noKwargs 143 @noPosargs 144 def to_lower_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 145 return self.held_object.lower() 146 147 @noKwargs 148 @noPosargs 149 def to_upper_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 150 return self.held_object.upper() 151 152 @noKwargs 153 @noPosargs 154 def underscorify_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 155 return re.sub(r'[^a-zA-Z0-9]', '_', self.held_object) 156 157 @noKwargs 158 @typed_pos_args('str.version_compare', str) 159 def version_compare_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: 160 return version_compare(self.held_object, args[0]) 161 162 @FeatureNew('/ with string arguments', '0.49.0') 163 @typed_operator(MesonOperator.DIV, str) 164 def op_div(self, other: str) -> str: 165 return os.path.join(self.held_object, other).replace('\\', '/') 166 167 @typed_operator(MesonOperator.INDEX, int) 168 def op_index(self, other: int) -> str: 169 try: 170 return self.held_object[other] 171 except IndexError: 172 raise InvalidArguments(f'Index {other} out of bounds of string of size {len(self.held_object)}.') 173 174 175class MesonVersionString(str): 176 pass 177 178class MesonVersionStringHolder(StringHolder): 179 @noKwargs 180 @typed_pos_args('str.version_compare', str) 181 def version_compare_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: 182 self.interpreter.tmp_meson_version = args[0] 183 return version_compare(self.held_object, args[0]) 184