rAc - revert invalid suggestions to edit mode
[chromium-blink-merge.git] / remoting / tools / verify_resources.py
blob1b082a5d873bb7c8f8f9c733907ad47a39a5cc0c
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.
6 """Verifies that GRD resource files define all the strings used by a given
7 set of source files. For file formats where it is not possible to infer which
8 strings represent message identifiers, localized strings should be explicitly
9 annotated with the string "i18n-content", for example:
11 LocalizeString(/*i18n-content*/"PRODUCT_NAME");
13 This script also recognises localized strings in HTML and manifest.json files:
15 HTML: i18n-content="PRODUCT_NAME"
16 or i18n-value-name-1="BUTTON_NAME"
17 or i18n-title="TOOLTIP_NAME"
18 manifest.json: __MSG_PRODUCT_NAME__
20 Note that these forms must be exact; extra spaces are not permitted, though
21 either single or double quotes are recognized.
23 In addition, the script checks that all the messages are still in use; if
24 this is not the case then a warning is issued, but the script still succeeds.
25 """
27 import json
28 import os
29 import optparse
30 import re
31 import sys
32 import xml.dom.minidom as minidom
34 WARNING_MESSAGE = """
35 To remove this warning, either remove the unused tags from
36 resource files, add the files that use the tags listed above to
37 remoting.gyp, or annotate existing uses of those tags with the
38 prefix /*i18n-content*/
39 """
41 def LoadTagsFromGrd(filename):
42 xml = minidom.parse(filename)
43 android_tags = []
44 other_tags = []
45 msgs_and_structs = xml.getElementsByTagName("message")
46 msgs_and_structs.extend(xml.getElementsByTagName("structure"))
47 for res in msgs_and_structs:
48 name = res.getAttribute("name")
49 if not name or not name.startswith("IDS_"):
50 raise Exception("Tag name doesn't start with IDS_: %s" % name)
51 name = name[4:]
52 if 'android_java' in res.getAttribute('formatter_data'):
53 android_tags.append(name)
54 else:
55 other_tags.append(name)
56 return android_tags, other_tags
59 def ExtractTagFromLine(file_type, line):
60 """Extract a tag from a line of HTML, C++, JS or JSON."""
61 if file_type == "html":
62 # HTML-style (tags)
63 m = re.search('i18n-content=[\'"]([^\'"]*)[\'"]', line)
64 if m: return m.group(1)
65 # HTML-style (titles)
66 m = re.search('i18n-title=[\'"]([^\'"]*)[\'"]', line)
67 if m: return m.group(1)
68 # HTML-style (substitutions)
69 m = re.search('i18n-value-name-[1-9]=[\'"]([^\'"]*)[\'"]', line)
70 if m: return m.group(1)
71 elif file_type == 'js':
72 # Javascript style
73 m = re.search('/\*i18n-content\*/[\'"]([^\`"]*)[\'"]', line)
74 if m: return m.group(1)
75 elif file_type == 'cc' or file_type == 'mm':
76 # C++ style
77 m = re.search('IDS_([A-Z0-9_]*)', line)
78 if m: return m.group(1)
79 m = re.search('/\*i18n-content\*/["]([^\`"]*)["]', line)
80 if m: return m.group(1)
81 elif file_type == 'json':
82 # Manifest style
83 m = re.search('__MSG_(.*)__', line)
84 if m: return m.group(1)
85 elif file_type == 'jinja2':
86 # Jinja2 template file
87 m = re.search('\{\%\s+trans\s+\%\}([A-Z0-9_]+)\{\%\s+endtrans\s+\%\}', line)
88 if m: return m.group(1)
89 return None
92 def VerifyFile(filename, messages, used_tags):
93 """
94 Parse |filename|, looking for tags and report any that are not included in
95 |messages|. Return True if all tags are present and correct, or False if
96 any are missing. If no tags are found, print a warning message and return
97 True.
98 """
100 base_name, extension = os.path.splitext(filename)
101 extension = extension[1:]
102 if extension not in ['js', 'cc', 'html', 'json', 'jinja2', 'mm']:
103 raise Exception("Unknown file type: %s" % extension)
105 result = True
106 matches = False
107 f = open(filename, 'r')
108 lines = f.readlines()
109 for i in xrange(0, len(lines)):
110 tag = ExtractTagFromLine(extension, lines[i])
111 if tag:
112 tag = tag.upper()
113 used_tags.add(tag)
114 matches = True
115 if not tag in messages:
116 result = False
117 print '%s/%s:%d: error: Undefined tag: %s' % \
118 (os.getcwd(), filename, i + 1, tag)
119 if not matches:
120 print '%s/%s:0: warning: No tags found' % (os.getcwd(), filename)
121 f.close()
122 return result
125 def main():
126 parser = optparse.OptionParser(
127 usage='Usage: %prog [options...] [source_file...]')
128 parser.add_option('-t', '--touch', dest='touch',
129 help='File to touch when finished.')
130 parser.add_option('-r', '--grd', dest='grd', action='append',
131 help='grd file')
133 options, args = parser.parse_args()
134 if not options.touch:
135 print '-t is not specified.'
136 return 1
137 if len(options.grd) == 0 or len(args) == 0:
138 print 'At least one GRD file needs to be specified.'
139 return 1
141 all_resources = []
142 non_android_resources = []
143 for f in options.grd:
144 android_tags, other_tags = LoadTagsFromGrd(f)
145 all_resources.extend(android_tags + other_tags)
146 non_android_resources.extend(other_tags)
148 used_tags = set([])
149 exit_code = 0
150 for f in args:
151 if not VerifyFile(f, all_resources, used_tags):
152 exit_code = 1
154 # Determining if a resource is being used in the Android app is tricky
155 # because it requires annotating and parsing Android XML layout files.
156 # For now, exclude Android strings from this check.
157 warnings = False
158 for tag in non_android_resources:
159 if tag not in used_tags:
160 print ('%s/%s:0: warning: %s is defined but not used') % \
161 (os.getcwd(), sys.argv[2], tag)
162 warnings = True
163 if warnings:
164 print WARNING_MESSAGE
166 if exit_code == 0:
167 f = open(options.touch, 'a')
168 f.close()
169 os.utime(options.touch, None)
171 return exit_code
174 if __name__ == '__main__':
175 sys.exit(main())