1# Copyright (C) 2018 and later: Unicode, Inc. and others. 2# License & terms of use: http://www.unicode.org/copyright.html 3 4# Python 2/3 Compatibility (ICU-20299) 5# TODO(ICU-20301): Remove this. 6from __future__ import print_function 7 8from abc import abstractmethod 9import copy 10import sys 11 12from . import * 13from . import utils 14 15 16# TODO(ICU-20301): Remove arguments from all instances of super() in this file 17 18# Note: for this to be a proper abstract class, it should extend abc.ABC. 19# There is no nice way to do this that works in both Python 2 and 3. 20# TODO(ICU-20301): Make this inherit from abc.ABC. 21class AbstractRequest(object): 22 def __init__(self, **kwargs): 23 24 # Used for identification purposes 25 self.name = None 26 27 # The filter category that applies to this request 28 self.category = None 29 30 self._set_fields(kwargs) 31 32 def _set_fields(self, kwargs): 33 for key, value in list(kwargs.items()): 34 if hasattr(self, key): 35 if isinstance(value, list): 36 value = copy.copy(value) 37 elif isinstance(value, dict): 38 value = copy.deepcopy(value) 39 setattr(self, key, value) 40 else: 41 raise ValueError("Unknown argument: %s" % key) 42 43 def apply_file_filter(self, filter): 44 """ 45 Returns True if this request still has input files after filtering, 46 or False if the request is "empty" after filtering. 47 """ 48 return True 49 50 def flatten(self, config, all_requests, common_vars): 51 return [self] 52 53 def all_input_files(self): 54 return [] 55 56 def all_output_files(self): 57 return [] 58 59 60class AbstractExecutionRequest(AbstractRequest): 61 def __init__(self, **kwargs): 62 63 # Names of targets (requests) or files that this request depends on. 64 # The entries of dep_targets may be any of the following types: 65 # 66 # 1. DepTarget, for the output of an execution request. 67 # 2. InFile, TmpFile, etc., for a specific file. 68 # 3. A list of InFile, TmpFile, etc., where the list is the same 69 # length as self.input_files and self.output_files. 70 # 71 # In cases 1 and 2, the dependency is added to all rules that the 72 # request generates. In case 3, the dependency is added only to the 73 # rule that generates the output file at the same array index. 74 self.dep_targets = [] 75 76 # Computed during self.flatten(); don't edit directly. 77 self.common_dep_files = [] 78 79 # Primary input files 80 self.input_files = [] 81 82 # Output files; for some subclasses, this must be the same length 83 # as input_files 84 self.output_files = [] 85 86 # What tool to execute 87 self.tool = None 88 89 # Argument string to pass to the tool with optional placeholders 90 self.args = "" 91 92 # Placeholders to substitute into the argument string; if any of these 93 # have a list type, the list must be equal in length to input_files 94 self.format_with = {} 95 96 super(AbstractExecutionRequest, self).__init__(**kwargs) 97 98 def apply_file_filter(self, filter): 99 i = 0 100 while i < len(self.input_files): 101 if filter.match(self.input_files[i]): 102 i += 1 103 else: 104 self._del_at(i) 105 return i > 0 106 107 def _del_at(self, i): 108 del self.input_files[i] 109 for _, v in self.format_with.items(): 110 if isinstance(v, list): 111 assert len(v) == len(self.input_files) + 1 112 del v[i] 113 for v in self.dep_targets: 114 if isinstance(v, list): 115 assert len(v) == len(self.input_files) + 1 116 del v[i] 117 118 def flatten(self, config, all_requests, common_vars): 119 self._dep_targets_to_files(all_requests) 120 return super(AbstractExecutionRequest, self).flatten(config, all_requests, common_vars) 121 122 def _dep_targets_to_files(self, all_requests): 123 if not self.dep_targets: 124 return 125 for dep_target in self.dep_targets: 126 if isinstance(dep_target, list): 127 if hasattr(self, "specific_dep_files"): 128 assert len(dep_target) == len(self.specific_dep_files) 129 for file, out_list in zip(dep_target, self.specific_dep_files): 130 assert hasattr(file, "filename") 131 out_list.append(file) 132 else: 133 self.common_dep_files += dep_target 134 continue 135 if not isinstance(dep_target, DepTarget): 136 # Copy file entries directly to dep_files. 137 assert hasattr(dep_target, "filename") 138 self.common_dep_files.append(dep_target) 139 continue 140 # For DepTarget entries, search for the target. 141 for request in all_requests: 142 if request.name == dep_target.name: 143 self.common_dep_files += request.all_output_files() 144 break 145 else: 146 print("Warning: Unable to find target %s, a dependency of %s" % ( 147 dep_target.name, 148 self.name 149 ), file=sys.stderr) 150 self.dep_targets = [] 151 152 def all_input_files(self): 153 return self.common_dep_files + self.input_files 154 155 def all_output_files(self): 156 return self.output_files 157 158 159class SingleExecutionRequest(AbstractExecutionRequest): 160 def __init__(self, **kwargs): 161 super(SingleExecutionRequest, self).__init__(**kwargs) 162 163 164class RepeatedExecutionRequest(AbstractExecutionRequest): 165 def __init__(self, **kwargs): 166 167 # Placeholders to substitute into the argument string unique to each 168 # iteration; all values must be lists equal in length to input_files 169 self.repeat_with = {} 170 171 # Lists for dep files that are specific to individual resource bundle files 172 self.specific_dep_files = [[] for _ in range(len(kwargs["input_files"]))] 173 174 super(RepeatedExecutionRequest, self).__init__(**kwargs) 175 176 def _del_at(self, i): 177 super(RepeatedExecutionRequest, self)._del_at(i) 178 del self.output_files[i] 179 del self.specific_dep_files[i] 180 for _, v in self.repeat_with.items(): 181 if isinstance(v, list): 182 del v[i] 183 184 def all_input_files(self): 185 files = super(RepeatedExecutionRequest, self).all_input_files() 186 for specific_file_list in self.specific_dep_files: 187 files += specific_file_list 188 return files 189 190 191class RepeatedOrSingleExecutionRequest(AbstractExecutionRequest): 192 def __init__(self, **kwargs): 193 self.repeat_with = {} 194 super(RepeatedOrSingleExecutionRequest, self).__init__(**kwargs) 195 196 def flatten(self, config, all_requests, common_vars): 197 if config.max_parallel: 198 new_request = RepeatedExecutionRequest( 199 name = self.name, 200 category = self.category, 201 dep_targets = self.dep_targets, 202 input_files = self.input_files, 203 output_files = self.output_files, 204 tool = self.tool, 205 args = self.args, 206 format_with = self.format_with, 207 repeat_with = self.repeat_with 208 ) 209 else: 210 new_request = SingleExecutionRequest( 211 name = self.name, 212 category = self.category, 213 dep_targets = self.dep_targets, 214 input_files = self.input_files, 215 output_files = self.output_files, 216 tool = self.tool, 217 args = self.args, 218 format_with = utils.concat_dicts(self.format_with, self.repeat_with) 219 ) 220 return new_request.flatten(config, all_requests, common_vars) 221 222 def _del_at(self, i): 223 super(RepeatedOrSingleExecutionRequest, self)._del_at(i) 224 del self.output_files[i] 225 for _, v in self.repeat_with.items(): 226 if isinstance(v, list): 227 del v[i] 228 229 230class PrintFileRequest(AbstractRequest): 231 def __init__(self, **kwargs): 232 self.output_file = None 233 self.content = None 234 super(PrintFileRequest, self).__init__(**kwargs) 235 236 def all_output_files(self): 237 return [self.output_file] 238 239 240class CopyRequest(AbstractRequest): 241 def __init__(self, **kwargs): 242 self.input_file = None 243 self.output_file = None 244 super(CopyRequest, self).__init__(**kwargs) 245 246 def all_input_files(self): 247 return [self.input_file] 248 249 def all_output_files(self): 250 return [self.output_file] 251 252 253class VariableRequest(AbstractRequest): 254 def __init__(self, **kwargs): 255 self.input_files = [] 256 super(VariableRequest, self).__init__(**kwargs) 257 258 def all_input_files(self): 259 return self.input_files 260 261 262class ListRequest(AbstractRequest): 263 def __init__(self, **kwargs): 264 self.variable_name = None 265 self.output_file = None 266 self.include_tmp = None 267 super(ListRequest, self).__init__(**kwargs) 268 269 def flatten(self, config, all_requests, common_vars): 270 list_files = list(sorted(utils.get_all_output_files(all_requests))) 271 if self.include_tmp: 272 variable_files = list(sorted(utils.get_all_output_files(all_requests, include_tmp=True))) 273 else: 274 # Always include the list file itself 275 variable_files = list_files + [self.output_file] 276 return PrintFileRequest( 277 name = self.name, 278 output_file = self.output_file, 279 content = "\n".join(file.filename for file in list_files) 280 ).flatten(config, all_requests, common_vars) + VariableRequest( 281 name = self.variable_name, 282 input_files = variable_files 283 ).flatten(config, all_requests, common_vars) 284 285 def all_output_files(self): 286 return [self.output_file] 287 288 289class IndexRequest(AbstractRequest): 290 def __init__(self, **kwargs): 291 self.installed_files = [] 292 self.alias_files = [] 293 self.txt_file = None 294 self.output_file = None 295 self.cldr_version = "" 296 self.args = "" 297 self.format_with = {} 298 super(IndexRequest, self).__init__(**kwargs) 299 300 def apply_file_filter(self, filter): 301 i = 0 302 while i < len(self.installed_files): 303 if filter.match(self.installed_files[i]): 304 i += 1 305 else: 306 del self.installed_files[i] 307 j = 0 308 while j < len(self.alias_files): 309 if filter.match(self.alias_files[j]): 310 j += 1 311 else: 312 del self.alias_files[j] 313 return i + j > 0 314 315 def flatten(self, config, all_requests, common_vars): 316 return ( 317 PrintFileRequest( 318 name = self.name, 319 output_file = self.txt_file, 320 content = self._generate_index_file(common_vars) 321 ).flatten(config, all_requests, common_vars) + 322 SingleExecutionRequest( 323 name = "%s_res" % self.name, 324 category = self.category, 325 input_files = [self.txt_file], 326 output_files = [self.output_file], 327 tool = IcuTool("genrb"), 328 args = self.args, 329 format_with = self.format_with 330 ).flatten(config, all_requests, common_vars) 331 ) 332 333 def _generate_index_file(self, common_vars): 334 installed_locales = [IndexRequest.locale_file_stem(f) for f in self.installed_files] 335 alias_locales = [IndexRequest.locale_file_stem(f) for f in self.alias_files] 336 formatted_version = " CLDRVersion { \"%s\" }\n" % self.cldr_version if self.cldr_version else "" 337 formatted_installed_locales = "\n".join([" %s {\"\"}" % v for v in installed_locales]) 338 formatted_alias_locales = "\n".join([" %s {\"\"}" % v for v in alias_locales]) 339 # TODO: CLDRVersion is required only in the base file 340 return ("// Warning this file is automatically generated\n" 341 "{INDEX_NAME}:table(nofallback) {{\n" 342 "{FORMATTED_VERSION}" 343 " InstalledLocales:table {{\n" 344 "{FORMATTED_INSTALLED_LOCALES}\n" 345 " }}\n" 346 " AliasLocales:table {{\n" 347 "{FORMATTED_ALIAS_LOCALES}\n" 348 " }}\n" 349 "}}").format( 350 FORMATTED_VERSION = formatted_version, 351 FORMATTED_INSTALLED_LOCALES = formatted_installed_locales, 352 FORMATTED_ALIAS_LOCALES = formatted_alias_locales, 353 **common_vars 354 ) 355 356 def all_input_files(self): 357 return self.installed_files + self.alias_files 358 359 def all_output_files(self): 360 return [self.output_file] 361 362 @staticmethod 363 def locale_file_stem(f): 364 return f.filename[f.filename.rfind("/")+1:-4] 365