OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 '''The <structure> element. | |
7 ''' | |
8 | |
9 import os | |
10 import platform | |
11 import re | |
12 | |
13 from grit import exception | |
14 from grit import util | |
15 from grit.node import base | |
16 from grit.node import variant | |
17 | |
18 import grit.gather.admin_template | |
19 import grit.gather.chrome_html | |
20 import grit.gather.chrome_scaled_image | |
21 import grit.gather.igoogle_strings | |
22 import grit.gather.muppet_strings | |
23 import grit.gather.policy_json | |
24 import grit.gather.rc | |
25 import grit.gather.tr_html | |
26 import grit.gather.txt | |
27 | |
28 import grit.format.rc | |
29 import grit.format.rc_header | |
30 | |
31 # Type of the gatherer to use for each type attribute | |
32 _GATHERERS = { | |
33 'accelerators' : grit.gather.rc.Accelerators, | |
34 'admin_template' : grit.gather.admin_template.AdmGatherer, | |
35 'chrome_html' : grit.gather.chrome_html.ChromeHtml, | |
36 'chrome_scaled_image' : grit.gather.chrome_scaled_image.ChromeScaledImage, | |
37 'dialog' : grit.gather.rc.Dialog, | |
38 'igoogle' : grit.gather.igoogle_strings.IgoogleStrings, | |
39 'menu' : grit.gather.rc.Menu, | |
40 'muppet' : grit.gather.muppet_strings.MuppetStrings, | |
41 'rcdata' : grit.gather.rc.RCData, | |
42 'tr_html' : grit.gather.tr_html.TrHtml, | |
43 'txt' : grit.gather.txt.TxtFile, | |
44 'version' : grit.gather.rc.Version, | |
45 'policy_template_metafile' : grit.gather.policy_json.PolicyJson, | |
46 } | |
47 | |
48 | |
49 # TODO(joi) Print a warning if the 'variant_of_revision' attribute indicates | |
50 # that a skeleton variant is older than the original file. | |
51 | |
52 | |
53 class StructureNode(base.Node): | |
54 '''A <structure> element.''' | |
55 | |
56 # Regular expression for a local variable definition. Each definition | |
57 # is of the form NAME=VALUE, where NAME cannot contain '=' or ',' and | |
58 # VALUE must escape all commas: ',' -> ',,'. Each variable definition | |
59 # should be separated by a comma with no extra whitespace. | |
60 # Example: THING1=foo,THING2=bar | |
61 variable_pattern = re.compile('([^,=\s]+)=((?:,,|[^,])*)') | |
62 | |
63 def __init__(self): | |
64 super(StructureNode, self).__init__() | |
65 | |
66 # Keep track of the last filename we flattened to, so we can | |
67 # avoid doing it more than once. | |
68 self._last_flat_filename = None | |
69 | |
70 # See _Substitute; this substituter is used for local variables and | |
71 # the root substituter is used for global variables. | |
72 self.substituter = None | |
73 | |
74 def _IsValidChild(self, child): | |
75 return isinstance(child, variant.SkeletonNode) | |
76 | |
77 def _ParseVariables(self, variables): | |
78 '''Parse a variable string into a dictionary.''' | |
79 matches = StructureNode.variable_pattern.findall(variables) | |
80 return dict((name, value.replace(',,', ',')) for name, value in matches) | |
81 | |
82 def EndParsing(self): | |
83 super(StructureNode, self).EndParsing() | |
84 | |
85 # Now that we have attributes and children, instantiate the gatherers. | |
86 gathertype = _GATHERERS[self.attrs['type']] | |
87 | |
88 self.gatherer = gathertype(self.attrs['file'], | |
89 self.attrs['name'], | |
90 self.attrs['encoding']) | |
91 self.gatherer.SetGrdNode(self) | |
92 self.gatherer.SetUberClique(self.UberClique()) | |
93 if hasattr(self.GetRoot(), 'defines'): | |
94 self.gatherer.SetDefines(self.GetRoot().defines) | |
95 self.gatherer.SetAttributes(self.attrs) | |
96 if self.ExpandVariables(): | |
97 self.gatherer.SetFilenameExpansionFunction(self._Substitute) | |
98 | |
99 # Parse local variables and instantiate the substituter. | |
100 if self.attrs['variables']: | |
101 variables = self.attrs['variables'] | |
102 self.substituter = util.Substituter() | |
103 self.substituter.AddSubstitutions(self._ParseVariables(variables)) | |
104 | |
105 self.skeletons = {} # Maps expressions to skeleton gatherers | |
106 for child in self.children: | |
107 assert isinstance(child, variant.SkeletonNode) | |
108 skel = gathertype(child.attrs['file'], | |
109 self.attrs['name'], | |
110 child.GetEncodingToUse(), | |
111 is_skeleton=True) | |
112 skel.SetGrdNode(self) # TODO(benrg): Or child? Only used for ToRealPath | |
113 skel.SetUberClique(self.UberClique()) | |
114 if hasattr(self.GetRoot(), 'defines'): | |
115 skel.SetDefines(self.GetRoot().defines) | |
116 if self.ExpandVariables(): | |
117 skel.SetFilenameExpansionFunction(self._Substitute) | |
118 self.skeletons[child.attrs['expr']] = skel | |
119 | |
120 def MandatoryAttributes(self): | |
121 return ['type', 'name', 'file'] | |
122 | |
123 def DefaultAttributes(self): | |
124 return { 'encoding' : 'cp1252', | |
125 'exclude_from_rc' : 'false', | |
126 'line_end' : 'unix', | |
127 'output_encoding' : 'utf-8', | |
128 'generateid': 'true', | |
129 'expand_variables' : 'false', | |
130 'output_filename' : '', | |
131 'fold_whitespace': 'false', | |
132 # Run an arbitrary command after translation is complete | |
133 # so that it doesn't interfere with what's in translation | |
134 # console. | |
135 'run_command' : '', | |
136 # Leave empty to run on all platforms, comma-separated | |
137 # for one or more specific platforms. Values must match | |
138 # output of platform.system(). | |
139 'run_command_on_platforms' : '', | |
140 'allowexternalscript': 'false', | |
141 'flattenhtml': 'false', | |
142 'fallback_to_low_resolution': 'default', | |
143 # TODO(joi) this is a hack - should output all generated files | |
144 # as SCons dependencies; however, for now there is a bug I can't | |
145 # find where GRIT doesn't build the matching fileset, therefore | |
146 # this hack so that only the files you really need are marked as | |
147 # dependencies. | |
148 'sconsdep' : 'false', | |
149 'variables': '', | |
150 } | |
151 | |
152 def IsExcludedFromRc(self): | |
153 return self.attrs['exclude_from_rc'] == 'true' | |
154 | |
155 def Process(self, output_dir): | |
156 """Writes the processed data to output_dir. In the case of a chrome_html | |
157 structure this will add references to other scale factors. If flattening | |
158 this will also write file references to be base64 encoded data URLs. The | |
159 name of the new file is returned.""" | |
160 filename = self.ToRealPath(self.GetInputPath()) | |
161 flat_filename = os.path.join(output_dir, | |
162 self.attrs['name'] + '_' + os.path.basename(filename)) | |
163 | |
164 if self._last_flat_filename == flat_filename: | |
165 return | |
166 | |
167 with open(flat_filename, 'wb') as outfile: | |
168 if self.ExpandVariables(): | |
169 text = self.gatherer.GetText() | |
170 file_contents = self._Substitute(text).encode('utf-8') | |
171 else: | |
172 file_contents = self.gatherer.GetData('', 'utf-8') | |
173 outfile.write(file_contents) | |
174 | |
175 self._last_flat_filename = flat_filename | |
176 return os.path.basename(flat_filename) | |
177 | |
178 def GetLineEnd(self): | |
179 '''Returns the end-of-line character or characters for files output because | |
180 of this node ('\r\n', '\n', or '\r' depending on the 'line_end' attribute). | |
181 ''' | |
182 if self.attrs['line_end'] == 'unix': | |
183 return '\n' | |
184 elif self.attrs['line_end'] == 'windows': | |
185 return '\r\n' | |
186 elif self.attrs['line_end'] == 'mac': | |
187 return '\r' | |
188 else: | |
189 raise exception.UnexpectedAttribute( | |
190 "Attribute 'line_end' must be one of 'unix' (default), 'windows' or 'mac
'") | |
191 | |
192 def GetCliques(self): | |
193 return self.gatherer.GetCliques() | |
194 | |
195 def GetDataPackPair(self, lang, encoding): | |
196 """Returns a (id, string|None) pair that represents the resource id and raw | |
197 bytes of the data (or None if no resource is generated). This is used to | |
198 generate the data pack data file. | |
199 """ | |
200 from grit.format import rc_header | |
201 id_map = rc_header.GetIds(self.GetRoot()) | |
202 id = id_map[self.GetTextualIds()[0]] | |
203 if self.ExpandVariables(): | |
204 text = self.gatherer.GetText() | |
205 return id, util.Encode(self._Substitute(text), encoding) | |
206 return id, self.gatherer.GetData(lang, encoding) | |
207 | |
208 def GetHtmlResourceFilenames(self): | |
209 """Returns a set of all filenames inlined by this node.""" | |
210 return self.gatherer.GetHtmlResourceFilenames() | |
211 | |
212 def GetInputPath(self): | |
213 return self.gatherer.GetInputPath() | |
214 | |
215 def GetTextualIds(self): | |
216 if not hasattr(self, 'gatherer'): | |
217 # This case is needed because this method is called by | |
218 # GritNode.ValidateUniqueIds before RunGatherers has been called. | |
219 # TODO(benrg): Fix this? | |
220 return [self.attrs['name']] | |
221 return self.gatherer.GetTextualIds() | |
222 | |
223 def RunPreSubstitutionGatherer(self, debug=False): | |
224 if debug: | |
225 print 'Running gatherer %s for file %s' % ( | |
226 str(type(self.gatherer)), self.GetInputPath()) | |
227 | |
228 # Note: Parse() is idempotent, therefore this method is also. | |
229 self.gatherer.Parse() | |
230 for skel in self.skeletons.values(): | |
231 skel.Parse() | |
232 | |
233 def GetSkeletonGatherer(self): | |
234 '''Returns the gatherer for the alternate skeleton that should be used, | |
235 based on the expressions for selecting skeletons, or None if the skeleton | |
236 from the English version of the structure should be used. | |
237 ''' | |
238 for expr in self.skeletons: | |
239 if self.EvaluateCondition(expr): | |
240 return self.skeletons[expr] | |
241 return None | |
242 | |
243 def HasFileForLanguage(self): | |
244 return self.attrs['type'] in ['tr_html', 'admin_template', 'txt', | |
245 'muppet', 'igoogle', 'chrome_scaled_image', | |
246 'chrome_html'] | |
247 | |
248 def ExpandVariables(self): | |
249 '''Variable expansion on structures is controlled by an XML attribute. | |
250 | |
251 However, old files assume that expansion is always on for Rc files. | |
252 | |
253 Returns: | |
254 A boolean. | |
255 ''' | |
256 attrs = self.GetRoot().attrs | |
257 if 'grit_version' in attrs and attrs['grit_version'] > 1: | |
258 return self.attrs['expand_variables'] == 'true' | |
259 else: | |
260 return (self.attrs['expand_variables'] == 'true' or | |
261 self.attrs['file'].lower().endswith('.rc')) | |
262 | |
263 def _Substitute(self, text): | |
264 '''Perform local and global variable substitution.''' | |
265 if self.substituter: | |
266 text = self.substituter.Substitute(text) | |
267 return self.GetRoot().GetSubstituter().Substitute(text) | |
268 | |
269 def RunCommandOnCurrentPlatform(self): | |
270 if self.attrs['run_command_on_platforms'] == '': | |
271 return True | |
272 else: | |
273 target_platforms = self.attrs['run_command_on_platforms'].split(',') | |
274 return platform.system() in target_platforms | |
275 | |
276 def FileForLanguage(self, lang, output_dir, create_file=True, | |
277 return_if_not_generated=True): | |
278 '''Returns the filename of the file associated with this structure, | |
279 for the specified language. | |
280 | |
281 Args: | |
282 lang: 'fr' | |
283 output_dir: 'c:\temp' | |
284 create_file: True | |
285 ''' | |
286 assert self.HasFileForLanguage() | |
287 # If the source language is requested, and no extra changes are requested, | |
288 # use the existing file. | |
289 if ((not lang or lang == self.GetRoot().GetSourceLanguage()) and | |
290 self.attrs['expand_variables'] != 'true' and | |
291 (not self.attrs['run_command'] or | |
292 not self.RunCommandOnCurrentPlatform())): | |
293 if return_if_not_generated: | |
294 input_path = self.GetInputPath() | |
295 if input_path is None: | |
296 return None | |
297 return self.ToRealPath(input_path) | |
298 else: | |
299 return None | |
300 | |
301 if self.attrs['output_filename'] != '': | |
302 filename = self.attrs['output_filename'] | |
303 else: | |
304 filename = os.path.basename(self.attrs['file']) | |
305 assert len(filename) | |
306 filename = '%s_%s' % (lang, filename) | |
307 filename = os.path.join(output_dir, filename) | |
308 | |
309 # Only create the output if it was requested by the call. | |
310 if create_file: | |
311 text = self.gatherer.Translate( | |
312 lang, | |
313 pseudo_if_not_available=self.PseudoIsAllowed(), | |
314 fallback_to_english=self.ShouldFallbackToEnglish(), | |
315 skeleton_gatherer=self.GetSkeletonGatherer()) | |
316 | |
317 file_contents = util.FixLineEnd(text, self.GetLineEnd()) | |
318 if self.ExpandVariables(): | |
319 # Note that we reapply substitution a second time here. | |
320 # This is because a) we need to look inside placeholders | |
321 # b) the substitution values are language-dependent | |
322 file_contents = self._Substitute(file_contents) | |
323 | |
324 with open(filename, 'wb') as file_object: | |
325 output_stream = util.WrapOutputStream(file_object, | |
326 self.attrs['output_encoding']) | |
327 output_stream.write(file_contents) | |
328 | |
329 if self.attrs['run_command'] and self.RunCommandOnCurrentPlatform(): | |
330 # Run arbitrary commands after translation is complete so that it | |
331 # doesn't interfere with what's in translation console. | |
332 command = self.attrs['run_command'] % {'filename': filename} | |
333 result = os.system(command) | |
334 assert result == 0, '"%s" failed.' % command | |
335 | |
336 return filename | |
337 | |
338 def IsResourceMapSource(self): | |
339 return True | |
340 | |
341 def GeneratesResourceMapEntry(self, output_all_resource_defines, | |
342 is_active_descendant): | |
343 if output_all_resource_defines: | |
344 return True | |
345 return is_active_descendant | |
346 | |
347 @staticmethod | |
348 def Construct(parent, name, type, file, encoding='cp1252'): | |
349 '''Creates a new node which is a child of 'parent', with attributes set | |
350 by parameters of the same name. | |
351 ''' | |
352 node = StructureNode() | |
353 node.StartParsing('structure', parent) | |
354 node.HandleAttribute('name', name) | |
355 node.HandleAttribute('type', type) | |
356 node.HandleAttribute('file', file) | |
357 node.HandleAttribute('encoding', encoding) | |
358 node.EndParsing() | |
359 return node | |
360 | |
361 def SubstituteMessages(self, substituter): | |
362 '''Propagates substitution to gatherer. | |
363 | |
364 Args: | |
365 substituter: a grit.util.Substituter object. | |
366 ''' | |
367 assert hasattr(self, 'gatherer') | |
368 if self.ExpandVariables(): | |
369 self.gatherer.SubstituteMessages(substituter) | |
370 | |
OLD | NEW |