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