Index: grit/node/misc.py |
=================================================================== |
--- grit/node/misc.py (revision 0) |
+++ grit/node/misc.py (revision 0) |
@@ -0,0 +1,401 @@ |
+#!/usr/bin/python2.4 |
+# Copyright (c) 2010 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.node import base |
+from grit.node import message |
+ |
+from grit import exception |
+from grit import constants |
+from grit import util |
+ |
+import grit.format.rc_header |
+ |
+ |
+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(open(filename).read()) |
+ |
+ # TODO(joi@chromium.org): It might make sense to make this a |
+ # parameter of the .grd file rather than of the resource_ids file. |
+ 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: |
+ value = os.path.join(src_root_dir, value) |
+ value = os.path.abspath(value)[len(src_root_dir) + 1:] |
+ 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: |
+ 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 IfNode(base.Node): |
+ '''A node for conditional inclusion of resources. |
+ ''' |
+ |
+ def _IsValidChild(self, child): |
+ from grit.node import empty |
+ assert self.parent, '<if> node should never be root.' |
+ if isinstance(self.parent, empty.IncludesNode): |
+ from grit.node import include |
+ return isinstance(child, include.IncludeNode) |
+ elif isinstance(self.parent, empty.MessagesNode): |
+ from grit.node import message |
+ return isinstance(child, message.MessageNode) |
+ elif isinstance(self.parent, empty.StructuresNode): |
+ from grit.node import structure |
+ return isinstance(child, structure.StructureNode) |
+ elif isinstance(self.parent, empty.OutputsNode): |
+ from grit.node import io |
+ return isinstance(child, io.OutputNode) |
+ elif isinstance(self.parent, empty.TranslationsNode): |
+ from grit.node import io |
+ return isinstance(child, io.FileNode) |
+ else: |
+ return False |
+ |
+ def MandatoryAttributes(self): |
+ return ['expr'] |
+ |
+ def IsConditionSatisfied(self): |
+ '''Returns true if and only if the Python expression stored in attribute |
+ 'expr' evaluates to true. |
+ ''' |
+ return self.EvaluateCondition(self.attrs['expr']) |
+ |
+ |
+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'] |
+ |
+ def ItemFormatter(self, t): |
+ if t == 'data_package': |
+ from grit.format import data_pack |
+ return data_pack.DataPack() |
+ else: |
+ return super(type(self), self).ItemFormatter(t) |
+ |
+class GritNode(base.Node): |
+ '''The <grit> root element.''' |
+ |
+ def __init__(self): |
+ base.Node.__init__(self) |
+ self.output_language = '' |
+ self.defines = {} |
+ |
+ 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', 'source_lang_id', |
+ 'latest_public_release', 'current_release', |
+ 'enc_check', 'tc_project']: |
+ 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' : '.', |
+ 'source_lang_id' : 'en', |
+ 'enc_check' : constants.ENCODING_CHECK, |
+ 'tc_project' : 'NEED_TO_SET_tc_project_ATTRIBUTE', |
+ } |
+ |
+ def EndParsing(self): |
+ base.Node.EndParsing(self) |
+ 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 = [] |
+ for node in self: |
+ if isinstance(node, message.PhNode): |
+ continue # PhNode objects have a 'name' attribute which is not an ID |
+ |
+ node_ids = node.GetTextualIds() |
+ if node_ids: |
+ for node_id in node_ids: |
+ if util.SYSTEM_IDENTIFIERS.match(node_id): |
+ continue # predefined IDs are sometimes used more than once |
+ |
+ # Don't complain about duplicate IDs if they occur in a node that is |
+ # inside an <if> node. |
+ if (node_id in unique_names and node_id not in duplicate_names and |
+ (not node.parent or not isinstance(node.parent, IfNode))): |
+ 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 _CollectOutputFiles(self, nodes, output_files): |
+ '''Recursively filters the list of nodes that may contain other lists |
+ in <if> nodes, and collects all the nodes that are not enclosed by |
+ unsatisfied <if> conditionals and not <if> nodes themselves. |
+ |
+ Args: |
+ nodes: The list of nodes to filter. |
+ output_files: The list of satisfying nodes. |
+ ''' |
+ for node in nodes: |
+ if node.name == 'if': |
+ if node.IsConditionSatisfied(): |
+ self._CollectOutputFiles(node.children, output_files) |
+ else: |
+ output_files.append(node) |
+ |
+ 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': |
+ output_files = [] |
+ self._CollectOutputFiles(child.children, output_files) |
+ return output_files |
+ raise exception.MissingElement() |
+ |
+ def ItemFormatter(self, t): |
+ if t == 'rc_header': |
+ from grit.format import rc_header # import here to avoid circular dep |
+ return rc_header.TopLevel() |
+ elif t in ['rc_all', 'rc_translateable', 'rc_nontranslateable']: |
+ from grit.format import rc # avoid circular dep |
+ return rc.TopLevel() |
+ elif t == 'resource_map_header': |
+ from grit.format import resource_map |
+ return resource_map.HeaderTopLevel() |
+ elif t in ('resource_map_source', 'resource_file_map_source'): |
+ from grit.format import resource_map |
+ return resource_map.SourceTopLevel() |
+ elif t == 'js_map_format': |
+ from grit.format import js_map_format |
+ return js_map_format.TopLevel() |
+ elif t in ('adm', 'plist', 'plist_strings', 'admx', 'adml', 'doc', 'json', |
+ 'reg'): |
+ from grit.format.policy_templates import template_formatter |
+ return template_formatter.TemplateFormatter(t) |
+ else: |
+ return super(type(self), self).ItemFormatter(t) |
+ |
+ def SetOutputContext(self, output_language, defines): |
+ self.output_language = output_language |
+ self.defines = defines |
+ |
+ def SetDefines(self, defines): |
+ self.defines = defines |
+ |
+ def AssignFirstIds(self, filename_or_stream, first_id_filename, defines): |
+ '''Assign first ids to each grouping node based on values from |
+ tools/grit/resource_ids.''' |
+ # 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 |
+ |
+ # TODO(joi@chromium.org): Get rid of this hack by making it |
+ # possible to specify the resource_ids file to use as an attribute |
+ # of the <grit> node in the .grd file, and doing so in all Chrome |
+ # .grd files. |
+ # |
+ # For now, by default, we use the the file |
+ # ../gritsettings/resource_ids relative to grit.py. |
+ if not first_id_filename: |
+ first_id_filename = os.path.join( |
+ os.path.dirname(__file__), |
+ '..', '..', '..', |
+ 'gritsettings', 'resource_ids') |
+ |
+ first_ids = None |
+ from grit.node import empty |
+ for node in self.inorder(): |
+ if isinstance(node, empty.GroupingNode): |
+ if not first_ids: |
+ src_root_dir, first_ids = _ReadFirstIdsFromFile(first_id_filename, |
+ defines) |
+ filename = os.path.abspath(filename_or_stream)[ |
+ len(src_root_dir) + 1:] |
+ filename = filename.replace('\\', '/') |
+ |
+ # TODO(joi@chromium.org): Generalize this; users other than |
+ # Chrome might want to use the first_id attribute; could check |
+ # for first_ids == None to indicate not loaded, first_ids == |
+ # {} to indicate tried to load but found no resource_ids file. |
+ if node.attrs['first_id'] != '': |
+ raise Exception("Don't set the first_id attribute, update " |
+ "%s instead." % first_id_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_id_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_id_filename, filename, node.name)) |
+ |
+ |
+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' : '' } |
+ |
+ def ItemFormatter(self, t): |
+ if t == 'rc_header': |
+ return grit.format.rc_header.Item() |
+ |
+ 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 |
+ |
+ # static method |
+ def Construct(parent, name, id, comment): |
+ '''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.EndParsing() |
+ return node |
+ Construct = staticmethod(Construct) |
Property changes on: grit/node/misc.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |