blob: 5b30037d1d2ec1860fb7137f68a2fb32b5449613 [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
'''The <structure> element.
'''
import os
from grit.node import base
from grit.node import variant
from grit import constants
from grit import exception
from grit import util
import grit.gather.admin_template
import grit.gather.igoogle_strings
import grit.gather.muppet_strings
import grit.gather.policy_json
import grit.gather.rc
import grit.gather.tr_html
import grit.gather.txt
import grit.format.rc
import grit.format.rc_header
# Type of the gatherer to use for each type attribute
_GATHERERS = {
'accelerators' : grit.gather.rc.Accelerators,
'admin_template' : grit.gather.admin_template.AdmGatherer,
'dialog' : grit.gather.rc.Dialog,
'igoogle' : grit.gather.igoogle_strings.IgoogleStrings,
'menu' : grit.gather.rc.Menu,
'muppet' : grit.gather.muppet_strings.MuppetStrings,
'rcdata' : grit.gather.rc.RCData,
'tr_html' : grit.gather.tr_html.TrHtml,
'txt' : grit.gather.txt.TxtFile,
'version' : grit.gather.rc.Version,
'policy_template_metafile' : grit.gather.policy_json.PolicyJson,
}
# Formatter instance to use for each type attribute
# when formatting .rc files.
_RC_FORMATTERS = {
'accelerators' : grit.format.rc.RcSection(),
'admin_template' : grit.format.rc.RcInclude('ADM'),
'dialog' : grit.format.rc.RcSection(),
'igoogle' : grit.format.rc.RcInclude('XML'),
'menu' : grit.format.rc.RcSection(),
'muppet' : grit.format.rc.RcInclude('XML'),
'rcdata' : grit.format.rc.RcSection(),
'tr_html' : grit.format.rc.RcInclude('HTML'),
'txt' : grit.format.rc.RcInclude('TXT'),
'version' : grit.format.rc.RcSection(),
'policy_template_metafile': None,
}
# TODO(joi) Print a warning if the 'variant_of_revision' attribute indicates
# that a skeleton variant is older than the original file.
class StructureNode(base.Node):
'''A <structure> element.'''
def __init__(self):
base.Node.__init__(self)
self.gatherer = None
self.skeletons = {} # expressions to skeleton gatherers
def _IsValidChild(self, child):
return isinstance(child, variant.SkeletonNode)
def MandatoryAttributes(self):
return ['type', 'name', 'file']
def DefaultAttributes(self):
return { 'encoding' : 'cp1252',
'exclude_from_rc' : 'false',
'line_end' : 'unix',
'output_encoding' : 'utf-8',
'generateid': 'true',
'expand_variables' : 'false',
'output_filename' : '',
'fold_whitespace': 'false',
'run_command' : '',
# TODO(joi) this is a hack - should output all generated files
# as SCons dependencies; however, for now there is a bug I can't
# find where GRIT doesn't build the matching fileset, therefore
# this hack so that only the files you really need are marked as
# dependencies.
'sconsdep' : 'false',
}
def IsExcludedFromRc(self):
return self.attrs['exclude_from_rc'] == 'true'
def GetLineEnd(self):
'''Returns the end-of-line character or characters for files output because
of this node ('\r\n', '\n', or '\r' depending on the 'line_end' attribute).
'''
if self.attrs['line_end'] == 'unix':
return '\n'
elif self.attrs['line_end'] == 'windows':
return '\r\n'
elif self.attrs['line_end'] == 'mac':
return '\r'
else:
raise exception.UnexpectedAttribute(
"Attribute 'line_end' must be one of 'linux' (default), 'windows' or 'mac'")
def GetCliques(self):
if self.gatherer:
return self.gatherer.GetCliques()
else:
return []
def GetTextualIds(self):
if self.gatherer and self.attrs['type'] not in ['tr_html', 'admin_template', 'txt']:
return self.gatherer.GetTextualIds()
else:
return [self.attrs['name']]
def ItemFormatter(self, t):
if t == 'rc_header':
return grit.format.rc_header.Item()
elif (t in ['rc_all', 'rc_translateable', 'rc_nontranslateable'] and
self.SatisfiesOutputCondition()):
return _RC_FORMATTERS[self.attrs['type']]
else:
return super(type(self), self).ItemFormatter(t)
def RunGatherers(self, recursive=False, debug=False):
if self.gatherer:
return # idempotent
gathertype = _GATHERERS[self.attrs['type']]
if debug:
print 'Running gatherer %s for file %s' % (str(gathertype), self.FilenameToOpen())
self.gatherer = gathertype.FromFile(self.FilenameToOpen(),
self.attrs['name'],
self.attrs['encoding'])
self.gatherer.SetUberClique(self.UberClique())
self.gatherer.SetAttributes(self.attrs)
self.gatherer.Parse()
for child in self.children:
assert isinstance(child, variant.SkeletonNode)
skel = gathertype.FromFile(child.FilenameToOpen(),
self.attrs['name'],
child.GetEncodingToUse())
skel.SetUberClique(self.UberClique())
skel.SetSkeleton(True)
skel.Parse()
self.skeletons[child.attrs['expr']] = skel
def GetSkeletonGatherer(self):
'''Returns the gatherer for the alternate skeleton that should be used,
based on the expressions for selecting skeletons, or None if the skeleton
from the English version of the structure should be used.
'''
for expr in self.skeletons:
if self.EvaluateCondition(expr):
return self.skeletons[expr]
return None
def GetFilePath(self):
return self.ToRealPath(self.attrs['file'])
def HasFileForLanguage(self):
return self.attrs['type'] in [
'tr_html', 'admin_template', 'txt', 'muppet', 'igoogle']
def ExpandVariables(self):
'''Variable expansion on structures is controlled by an XML attribute.
However, old files assume that expansion is always on for Rc files.
Returns:
A boolean.
'''
attrs = self.GetRoot().attrs
if 'grit_version' in attrs and attrs['grit_version'] > 1:
return self.attrs['expand_variables'] == 'true'
else:
return (self.attrs['expand_variables'] == 'true' or
self.attrs['file'].lower().endswith('.rc'))
def FileForLanguage(self, lang, output_dir, create_file=True,
return_if_not_generated=True):
'''Returns the filename of the file associated with this structure,
for the specified language.
Args:
lang: 'fr'
output_dir: 'c:\temp'
create_file: True
'''
assert self.HasFileForLanguage()
# If the source language is requested, and no extra changes are requested,
# use the existing file.
if (lang == self.GetRoot().GetSourceLanguage() and
self.attrs['expand_variables'] != 'true' and
not self.attrs['run_command']):
if return_if_not_generated:
return self.GetFilePath()
else:
return None
if self.attrs['output_filename'] != '':
filename = self.attrs['output_filename']
else:
filename = os.path.basename(self.attrs['file'])
assert len(filename)
filename = '%s_%s' % (lang, filename)
filename = os.path.join(output_dir, filename)
# Only create the output if it was requested by the call.
if create_file:
text = self.gatherer.Translate(
lang,
pseudo_if_not_available=self.PseudoIsAllowed(),
fallback_to_english=self.ShouldFallbackToEnglish(),
skeleton_gatherer=self.GetSkeletonGatherer())
file_object = util.WrapOutputStream(file(filename, 'wb'),
self._GetOutputEncoding())
file_contents = util.FixLineEnd(text, self.GetLineEnd())
if self.ExpandVariables():
# Note that we reapply substitution a second time here.
# This is because a) we need to look inside placeholders
# b) the substitution values are language-dependent
file_contents = self.GetRoot().substituter.Substitute(file_contents)
if self._ShouldAddBom():
file_object.write(constants.BOM)
file_object.write(file_contents)
file_object.close()
if self.attrs['run_command']:
# Run arbitrary commands after translation is complete so that it
# doesn't interfere with what's in translation console.
command = self.attrs['run_command'] % {'filename': filename}
result = os.system(command)
assert result == 0, '"%s" failed.' % command
return filename
def _GetOutputEncoding(self):
'''Python doesn't natively support UTF encodings with a BOM signature,
so we add support by allowing you to append '-sig' to the encoding name.
This function returns the specified output encoding minus that part.
'''
enc = self.attrs['output_encoding']
if enc.endswith('-sig'):
return enc[0:len(enc) - len('-sig')]
else:
return enc
def _ShouldAddBom(self):
'''Returns true if output files should have the Unicode BOM prepended.
'''
return self.attrs['output_encoding'].endswith('-sig')
@staticmethod
def Construct(parent, name, type, file, encoding='cp1252'):
'''Creates a new node which is a child of 'parent', with attributes set
by parameters of the same name.
'''
node = StructureNode()
node.StartParsing('structure', parent)
node.HandleAttribute('name', name)
node.HandleAttribute('type', type)
node.HandleAttribute('file', file)
node.HandleAttribute('encoding', encoding)
node.EndParsing()
return node
def SubstituteMessages(self, substituter):
'''Propagates substitution to gatherer.
Args:
substituter: a grit.util.Substituter object.
'''
assert self.gatherer
if self.ExpandVariables():
self.gatherer.SubstituteMessages(substituter)