| 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 |