| Index: grit/node/misc.py
|
| ===================================================================
|
| --- grit/node/misc.py (revision 202)
|
| +++ grit/node/misc.py (working copy)
|
| @@ -1,552 +0,0 @@
|
| -#!/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.
|
| -
|
| -"""Miscellaneous node types.
|
| -"""
|
| -
|
| -import os.path
|
| -import re
|
| -import sys
|
| -
|
| -from grit import constants
|
| -from grit import exception
|
| -from grit import util
|
| -import grit.format.rc_header
|
| -from grit.node import base
|
| -from grit.node import io
|
| -from grit.node import message
|
| -
|
| -
|
| -# RTL languages
|
| -# TODO(jennyz): remove this fixed set of RTL language array
|
| -# now that generic expand_variable code exists.
|
| -_RTL_LANGS = (
|
| - 'ar', # Arabic
|
| - 'fa', # Farsi
|
| - 'iw', # Hebrew
|
| - 'ks', # Kashmiri
|
| - 'ku', # Kurdish
|
| - 'ps', # Pashto
|
| - 'ur', # Urdu
|
| - 'yi', # Yiddish
|
| -)
|
| -
|
| -
|
| -def _ReadFirstIdsFromFile(filename, defines):
|
| - """Read the starting resource id values from |filename|. We also
|
| - expand variables of the form <(FOO) based on defines passed in on
|
| - the command line.
|
| -
|
| - Returns a tuple, the absolute path of SRCDIR followed by the
|
| - first_ids dictionary.
|
| - """
|
| - first_ids_dict = eval(util.ReadFile(filename, util.RAW_TEXT))
|
| - src_root_dir = os.path.abspath(os.path.join(os.path.dirname(filename),
|
| - first_ids_dict['SRCDIR']))
|
| -
|
| - def ReplaceVariable(matchobj):
|
| - for key, value in defines.iteritems():
|
| - if matchobj.group(1) == key:
|
| - return value
|
| - return ''
|
| -
|
| - renames = []
|
| - for grd_filename in first_ids_dict:
|
| - new_grd_filename = re.sub(r'<\(([A-Za-z_]+)\)', ReplaceVariable,
|
| - grd_filename)
|
| - if new_grd_filename != grd_filename:
|
| - abs_grd_filename = os.path.abspath(new_grd_filename)
|
| - if abs_grd_filename[:len(src_root_dir)] != src_root_dir:
|
| - new_grd_filename = os.path.basename(abs_grd_filename)
|
| - else:
|
| - new_grd_filename = abs_grd_filename[len(src_root_dir) + 1:]
|
| - new_grd_filename = new_grd_filename.replace('\\', '/')
|
| - renames.append((grd_filename, new_grd_filename))
|
| -
|
| - for grd_filename, new_grd_filename in renames:
|
| - first_ids_dict[new_grd_filename] = first_ids_dict[grd_filename]
|
| - del(first_ids_dict[grd_filename])
|
| -
|
| - return (src_root_dir, first_ids_dict)
|
| -
|
| -
|
| -class SplicingNode(base.Node):
|
| - """A node whose children should be considered to be at the same level as
|
| - its siblings for most purposes. This includes <if> and <part> nodes.
|
| - """
|
| -
|
| - def _IsValidChild(self, child):
|
| - assert self.parent, '<%s> node should never be root.' % self.name
|
| - if isinstance(child, SplicingNode):
|
| - return True # avoid O(n^2) behavior
|
| - return self.parent._IsValidChild(child)
|
| -
|
| -
|
| -class IfNode(SplicingNode):
|
| - """A node for conditional inclusion of resources.
|
| - """
|
| -
|
| - def MandatoryAttributes(self):
|
| - return ['expr']
|
| -
|
| - def _IsValidChild(self, child):
|
| - return (isinstance(child, (ThenNode, ElseNode)) or
|
| - super(IfNode, self)._IsValidChild(child))
|
| -
|
| - def EndParsing(self):
|
| - children = self.children
|
| - self.if_then_else = False
|
| - if any(isinstance(node, (ThenNode, ElseNode)) for node in children):
|
| - if (len(children) != 2 or not isinstance(children[0], ThenNode) or
|
| - not isinstance(children[1], ElseNode)):
|
| - raise exception.UnexpectedChild(
|
| - '<if> element must be <if><then>...</then><else>...</else></if>')
|
| - self.if_then_else = True
|
| -
|
| - def ActiveChildren(self):
|
| - cond = self.EvaluateCondition(self.attrs['expr'])
|
| - if self.if_then_else:
|
| - return self.children[0 if cond else 1].ActiveChildren()
|
| - else:
|
| - # Equivalent to having all children inside <then> with an empty <else>
|
| - return super(IfNode, self).ActiveChildren() if cond else []
|
| -
|
| -
|
| -class ThenNode(SplicingNode):
|
| - """A <then> node. Can only appear directly inside an <if> node."""
|
| - pass
|
| -
|
| -
|
| -class ElseNode(SplicingNode):
|
| - """An <else> node. Can only appear directly inside an <if> node."""
|
| - pass
|
| -
|
| -
|
| -class PartNode(SplicingNode):
|
| - """A node for inclusion of sub-grd (*.grp) files.
|
| - """
|
| -
|
| - def __init__(self):
|
| - super(PartNode, self).__init__()
|
| - self.started_inclusion = False
|
| -
|
| - def MandatoryAttributes(self):
|
| - return ['file']
|
| -
|
| - def _IsValidChild(self, child):
|
| - return self.started_inclusion and super(PartNode, self)._IsValidChild(child)
|
| -
|
| -
|
| -class ReleaseNode(base.Node):
|
| - """The <release> element."""
|
| -
|
| - def _IsValidChild(self, child):
|
| - from grit.node import empty
|
| - return isinstance(child, (empty.IncludesNode, empty.MessagesNode,
|
| - empty.StructuresNode, empty.IdentifiersNode))
|
| -
|
| - def _IsValidAttribute(self, name, value):
|
| - return (
|
| - (name == 'seq' and int(value) <= self.GetRoot().GetCurrentRelease()) or
|
| - name == 'allow_pseudo'
|
| - )
|
| -
|
| - def MandatoryAttributes(self):
|
| - return ['seq']
|
| -
|
| - def DefaultAttributes(self):
|
| - return { 'allow_pseudo' : 'true' }
|
| -
|
| - def GetReleaseNumber():
|
| - """Returns the sequence number of this release."""
|
| - return self.attribs['seq']
|
| -
|
| -class GritNode(base.Node):
|
| - """The <grit> root element."""
|
| -
|
| - def __init__(self):
|
| - super(GritNode, self).__init__()
|
| - self.output_language = ''
|
| - self.defines = {}
|
| - self.substituter = None
|
| - self.target_platform = sys.platform
|
| -
|
| - def _IsValidChild(self, child):
|
| - from grit.node import empty
|
| - return isinstance(child, (ReleaseNode, empty.TranslationsNode,
|
| - empty.OutputsNode))
|
| -
|
| - def _IsValidAttribute(self, name, value):
|
| - if name not in ['base_dir', 'first_ids_file', 'source_lang_id',
|
| - 'latest_public_release', 'current_release',
|
| - 'enc_check', 'tc_project', 'grit_version',
|
| - 'output_all_resource_defines', 'rc_header_format']:
|
| - return False
|
| - if name in ['latest_public_release', 'current_release'] and value.strip(
|
| - '0123456789') != '':
|
| - return False
|
| - return True
|
| -
|
| - def MandatoryAttributes(self):
|
| - return ['latest_public_release', 'current_release']
|
| -
|
| - def DefaultAttributes(self):
|
| - return {
|
| - 'base_dir' : '.',
|
| - 'first_ids_file': '',
|
| - 'grit_version': 1,
|
| - 'source_lang_id' : 'en',
|
| - 'enc_check' : constants.ENCODING_CHECK,
|
| - 'tc_project' : 'NEED_TO_SET_tc_project_ATTRIBUTE',
|
| - 'output_all_resource_defines': 'true',
|
| - 'rc_header_format': None
|
| - }
|
| -
|
| - def EndParsing(self):
|
| - super(GritNode, self).EndParsing()
|
| - if (int(self.attrs['latest_public_release'])
|
| - > int(self.attrs['current_release'])):
|
| - raise exception.Parsing('latest_public_release cannot have a greater '
|
| - 'value than current_release')
|
| -
|
| - self.ValidateUniqueIds()
|
| -
|
| - # Add the encoding check if it's not present (should ensure that it's always
|
| - # present in all .grd files generated by GRIT). If it's present, assert if
|
| - # it's not correct.
|
| - if 'enc_check' not in self.attrs or self.attrs['enc_check'] == '':
|
| - self.attrs['enc_check'] = constants.ENCODING_CHECK
|
| - else:
|
| - assert self.attrs['enc_check'] == constants.ENCODING_CHECK, (
|
| - 'Are you sure your .grd file is in the correct encoding (UTF-8)?')
|
| -
|
| - def ValidateUniqueIds(self):
|
| - """Validate that 'name' attribute is unique in all nodes in this tree
|
| - except for nodes that are children of <if> nodes.
|
| - """
|
| - unique_names = {}
|
| - duplicate_names = []
|
| - # To avoid false positives from mutually exclusive <if> clauses, check
|
| - # against whatever the output condition happens to be right now.
|
| - # TODO(benrg): do something better.
|
| - for node in self.ActiveDescendants():
|
| - if node.attrs.get('generateid', 'true') == 'false':
|
| - continue # Duplication not relevant in that case
|
| -
|
| - for node_id in node.GetTextualIds():
|
| - if util.SYSTEM_IDENTIFIERS.match(node_id):
|
| - continue # predefined IDs are sometimes used more than once
|
| -
|
| - if node_id in unique_names and node_id not in duplicate_names:
|
| - duplicate_names.append(node_id)
|
| - unique_names[node_id] = 1
|
| -
|
| - if len(duplicate_names):
|
| - raise exception.DuplicateKey(', '.join(duplicate_names))
|
| -
|
| -
|
| - def GetCurrentRelease(self):
|
| - """Returns the current release number."""
|
| - return int(self.attrs['current_release'])
|
| -
|
| - def GetLatestPublicRelease(self):
|
| - """Returns the latest public release number."""
|
| - return int(self.attrs['latest_public_release'])
|
| -
|
| - def GetSourceLanguage(self):
|
| - """Returns the language code of the source language."""
|
| - return self.attrs['source_lang_id']
|
| -
|
| - def GetTcProject(self):
|
| - """Returns the name of this project in the TranslationConsole, or
|
| - 'NEED_TO_SET_tc_project_ATTRIBUTE' if it is not defined."""
|
| - return self.attrs['tc_project']
|
| -
|
| - def SetOwnDir(self, dir):
|
| - """Informs the 'grit' element of the directory the file it is in resides.
|
| - This allows it to calculate relative paths from the input file, which is
|
| - what we desire (rather than from the current path).
|
| -
|
| - Args:
|
| - dir: r'c:\bla'
|
| -
|
| - Return:
|
| - None
|
| - """
|
| - assert dir
|
| - self.base_dir = os.path.normpath(os.path.join(dir, self.attrs['base_dir']))
|
| -
|
| - def GetBaseDir(self):
|
| - """Returns the base directory, relative to the working directory. To get
|
| - the base directory as set in the .grd file, use GetOriginalBaseDir()
|
| - """
|
| - if hasattr(self, 'base_dir'):
|
| - return self.base_dir
|
| - else:
|
| - return self.GetOriginalBaseDir()
|
| -
|
| - def GetOriginalBaseDir(self):
|
| - """Returns the base directory, as set in the .grd file.
|
| - """
|
| - return self.attrs['base_dir']
|
| -
|
| - def SetShouldOutputAllResourceDefines(self, value):
|
| - """Overrides the value of output_all_resource_defines found in the grd file.
|
| - """
|
| - self.attrs['output_all_resource_defines'] = 'true' if value else 'false'
|
| -
|
| - def ShouldOutputAllResourceDefines(self):
|
| - """Returns true if all resource defines should be output, false if
|
| - defines for resources not emitted to resource files should be
|
| - skipped.
|
| - """
|
| - return self.attrs['output_all_resource_defines'] == 'true'
|
| -
|
| - def GetRcHeaderFormat(self):
|
| - return self.attrs['rc_header_format']
|
| -
|
| - def AssignRcHeaderFormat(self, rc_header_format):
|
| - self.attrs['rc_header_format'] = rc_header_format
|
| -
|
| - def GetInputFiles(self):
|
| - """Returns the list of files that are read to produce the output."""
|
| -
|
| - # Importing this here avoids a circular dependency in the imports.
|
| - # pylint: disable-msg=C6204
|
| - from grit.node import include
|
| - from grit.node import misc
|
| - from grit.node import structure
|
| - from grit.node import variant
|
| -
|
| - # Check if the input is required for any output configuration.
|
| - input_files = set()
|
| - old_output_language = self.output_language
|
| - for lang, ctx, fallback in self.GetConfigurations():
|
| - self.SetOutputLanguage(lang or self.GetSourceLanguage())
|
| - self.SetOutputContext(ctx)
|
| - self.SetFallbackToDefaultLayout(fallback)
|
| -
|
| - for node in self.ActiveDescendants():
|
| - if isinstance(node, (io.FileNode, include.IncludeNode, misc.PartNode,
|
| - structure.StructureNode, variant.SkeletonNode)):
|
| - input_path = node.GetInputPath()
|
| - if input_path is not None:
|
| - input_files.add(self.ToRealPath(input_path))
|
| -
|
| - # If it's a flattened node, grab inlined resources too.
|
| - if ((node.name == 'structure' or node.name == 'include')
|
| - and node.attrs['flattenhtml'] == 'true'):
|
| - if node.name == 'structure':
|
| - node.RunPreSubstitutionGatherer()
|
| - input_files.update(node.GetHtmlResourceFilenames())
|
| -
|
| - self.SetOutputLanguage(old_output_language)
|
| - return sorted(input_files)
|
| -
|
| - def GetFirstIdsFile(self):
|
| - """Returns a usable path to the first_ids file, if set, otherwise
|
| - returns None.
|
| -
|
| - The first_ids_file attribute is by default relative to the
|
| - base_dir of the .grd file, but may be prefixed by GRIT_DIR/,
|
| - which makes it relative to the directory of grit.py
|
| - (e.g. GRIT_DIR/../gritsettings/resource_ids).
|
| - """
|
| - if not self.attrs['first_ids_file']:
|
| - return None
|
| -
|
| - path = self.attrs['first_ids_file']
|
| - GRIT_DIR_PREFIX = 'GRIT_DIR'
|
| - if (path.startswith(GRIT_DIR_PREFIX)
|
| - and path[len(GRIT_DIR_PREFIX)] in ['/', '\\']):
|
| - return util.PathFromRoot(path[len(GRIT_DIR_PREFIX) + 1:])
|
| - else:
|
| - return self.ToRealPath(path)
|
| -
|
| - def GetOutputFiles(self):
|
| - """Returns the list of <output> nodes that are descendants of this node's
|
| - <outputs> child and are not enclosed by unsatisfied <if> conditionals.
|
| - """
|
| - for child in self.children:
|
| - if child.name == 'outputs':
|
| - return [node for node in child.ActiveDescendants()
|
| - if node.name == 'output']
|
| - raise exception.MissingElement()
|
| -
|
| - def GetConfigurations(self):
|
| - """Returns the distinct (language, context, fallback_to_default_layout)
|
| - triples from the output nodes.
|
| - """
|
| - return set((n.GetLanguage(), n.GetContext(), n.GetFallbackToDefaultLayout()) for n in self.GetOutputFiles())
|
| -
|
| - def GetSubstitutionMessages(self):
|
| - """Returns the list of <message sub_variable="true"> nodes."""
|
| - return [n for n in self.ActiveDescendants()
|
| - if isinstance(n, message.MessageNode)
|
| - and n.attrs['sub_variable'] == 'true']
|
| -
|
| - def SetOutputLanguage(self, output_language):
|
| - """Set the output language. Prepares substitutions.
|
| -
|
| - The substitutions are reset every time the language is changed.
|
| - They include messages designated as variables, and language codes for html
|
| - and rc files.
|
| -
|
| - Args:
|
| - output_language: a two-letter language code (eg: 'en', 'ar'...) or ''
|
| - """
|
| - if not output_language:
|
| - # We do not specify the output language for .grh files,
|
| - # so we get an empty string as the default.
|
| - # The value should match grit.clique.MessageClique.source_language.
|
| - output_language = self.GetSourceLanguage()
|
| - if output_language != self.output_language:
|
| - self.output_language = output_language
|
| - self.substituter = None # force recalculate
|
| -
|
| - def SetOutputContext(self, output_context):
|
| - self.output_context = output_context
|
| - self.substituter = None # force recalculate
|
| -
|
| - def SetFallbackToDefaultLayout(self, fallback_to_default_layout):
|
| - self.fallback_to_default_layout = fallback_to_default_layout
|
| - self.substituter = None # force recalculate
|
| -
|
| - def SetDefines(self, defines):
|
| - self.defines = defines
|
| - self.substituter = None # force recalculate
|
| -
|
| - def SetTargetPlatform(self, target_platform):
|
| - self.target_platform = target_platform
|
| -
|
| - def GetSubstituter(self):
|
| - if self.substituter is None:
|
| - self.substituter = util.Substituter()
|
| - self.substituter.AddMessages(self.GetSubstitutionMessages(),
|
| - self.output_language)
|
| - if self.output_language in _RTL_LANGS:
|
| - direction = 'dir="RTL"'
|
| - else:
|
| - direction = 'dir="LTR"'
|
| - self.substituter.AddSubstitutions({
|
| - 'GRITLANGCODE': self.output_language,
|
| - 'GRITDIR': direction,
|
| - })
|
| - from grit.format import rc # avoid circular dep
|
| - rc.RcSubstitutions(self.substituter, self.output_language)
|
| - return self.substituter
|
| -
|
| - def AssignFirstIds(self, filename_or_stream, defines):
|
| - """Assign first ids to each grouping node based on values from the
|
| - first_ids file (if specified on the <grit> node).
|
| - """
|
| - # If the input is a stream, then we're probably in a unit test and
|
| - # should skip this step.
|
| - if type(filename_or_stream) not in (str, unicode):
|
| - return
|
| -
|
| - # Nothing to do if the first_ids_filename attribute isn't set.
|
| - first_ids_filename = self.GetFirstIdsFile()
|
| - if not first_ids_filename:
|
| - return
|
| -
|
| - src_root_dir, first_ids = _ReadFirstIdsFromFile(first_ids_filename,
|
| - defines)
|
| - from grit.node import empty
|
| - for node in self.Preorder():
|
| - if isinstance(node, empty.GroupingNode):
|
| - abs_filename = os.path.abspath(filename_or_stream)
|
| - if abs_filename[:len(src_root_dir)] != src_root_dir:
|
| - filename = os.path.basename(filename_or_stream)
|
| - else:
|
| - filename = abs_filename[len(src_root_dir) + 1:]
|
| - filename = filename.replace('\\', '/')
|
| -
|
| - if node.attrs['first_id'] != '':
|
| - raise Exception(
|
| - "Don't set the first_id attribute when using the first_ids_file "
|
| - "attribute on the <grit> node, update %s instead." %
|
| - first_ids_filename)
|
| -
|
| - try:
|
| - id_list = first_ids[filename][node.name]
|
| - except KeyError, e:
|
| - print '-' * 78
|
| - print 'Resource id not set for %s (%s)!' % (filename, node.name)
|
| - print ('Please update %s to include an entry for %s. See the '
|
| - 'comments in resource_ids for information on why you need to '
|
| - 'update that file.' % (first_ids_filename, filename))
|
| - print '-' * 78
|
| - raise e
|
| -
|
| - try:
|
| - node.attrs['first_id'] = str(id_list.pop(0))
|
| - except IndexError, e:
|
| - raise Exception('Please update %s and add a first id for %s (%s).'
|
| - % (first_ids_filename, filename, node.name))
|
| -
|
| - def RunGatherers(self, debug=False):
|
| - '''Call RunPreSubstitutionGatherer() on every node of the tree, then apply
|
| - substitutions, then call RunPostSubstitutionGatherer() on every node.
|
| -
|
| - The substitutions step requires that the output language has been set.
|
| - Locally, get the Substitution messages and add them to the substituter.
|
| - Also add substitutions for language codes in the Rc.
|
| -
|
| - Args:
|
| - debug: will print information while running gatherers.
|
| - '''
|
| - for node in self.ActiveDescendants():
|
| - if hasattr(node, 'RunPreSubstitutionGatherer'):
|
| - with node:
|
| - node.RunPreSubstitutionGatherer(debug=debug)
|
| -
|
| - assert self.output_language
|
| - self.SubstituteMessages(self.GetSubstituter())
|
| -
|
| - for node in self.ActiveDescendants():
|
| - if hasattr(node, 'RunPostSubstitutionGatherer'):
|
| - with node:
|
| - node.RunPostSubstitutionGatherer(debug=debug)
|
| -
|
| -
|
| -class IdentifierNode(base.Node):
|
| - """A node for specifying identifiers that should appear in the resource
|
| - header file, and be unique amongst all other resource identifiers, but don't
|
| - have any other attributes or reference any resources.
|
| - """
|
| -
|
| - def MandatoryAttributes(self):
|
| - return ['name']
|
| -
|
| - def DefaultAttributes(self):
|
| - return { 'comment' : '', 'id' : '', 'systemid': 'false' }
|
| -
|
| - def GetId(self):
|
| - """Returns the id of this identifier if it has one, None otherwise
|
| - """
|
| - if 'id' in self.attrs:
|
| - return self.attrs['id']
|
| - return None
|
| -
|
| - def EndParsing(self):
|
| - """Handles system identifiers."""
|
| - super(IdentifierNode, self).EndParsing()
|
| - if self.attrs['systemid'] == 'true':
|
| - util.SetupSystemIdentifiers((self.attrs['name'],))
|
| -
|
| - @staticmethod
|
| - def Construct(parent, name, id, comment, systemid='false'):
|
| - """Creates a new node which is a child of 'parent', with attributes set
|
| - by parameters of the same name.
|
| - """
|
| - node = IdentifierNode()
|
| - node.StartParsing('identifier', parent)
|
| - node.HandleAttribute('name', name)
|
| - node.HandleAttribute('id', id)
|
| - node.HandleAttribute('comment', comment)
|
| - node.HandleAttribute('systemid', systemid)
|
| - node.EndParsing()
|
| - return node
|
|
|