3a352312644218ff52b10b7a102655deb5e95739
[mesa.git] / src / util / xmlpool / gen_xmlpool.py
1 # encoding=utf-8
2 #
3 # Usage:
4 # gen_xmlpool.py /path/to/t_option.h localedir lang lang lang ...
5 #
6 # For each given language, this script expects to find a .mo file at
7 # `{localedir}/{language}/LC_MESSAGES/options.mo`.
8 #
9
10 from __future__ import print_function, unicode_literals
11 import argparse
12 import gettext
13 import io
14 import os
15 import re
16 import sys
17
18 if sys.version_info < (3, 0):
19 gettext_method = 'ugettext'
20 else:
21 gettext_method = 'gettext'
22
23 # Escape special characters in C strings
24 def escapeCString(s):
25 escapeSeqs = {'\a' : '\\a', '\b' : '\\b', '\f' : '\\f', '\n' : '\\n',
26 '\r' : '\\r', '\t' : '\\t', '\v' : '\\v', '\\' : '\\\\'}
27 # " -> '' is a hack. Quotes (") aren't possible in XML attributes.
28 # Better use Unicode characters for typographic quotes in option
29 # descriptions and translations.
30 last_quote = '”'
31 i = 0
32 r = ''
33 for c in s:
34 # Special case: escape double quote with “ or ”, depending
35 # on whether it's an open or close quote. This is needed because plain
36 # double quotes are not possible in XML attributes.
37 if c == '"':
38 if last_quote == '”':
39 q = '“'
40 else:
41 q = '”'
42 last_quote = q
43 r = r + q
44 elif c in escapeSeqs:
45 r = r + escapeSeqs[c]
46 else:
47 r = r + c
48 return r
49
50 # Expand escape sequences in C strings (needed for gettext lookup)
51 def expandCString(s):
52 escapeSeqs = {'a' : '\a', 'b' : '\b', 'f' : '\f', 'n' : '\n',
53 'r' : '\r', 't' : '\t', 'v' : '\v',
54 '"' : '"', '\\' : '\\'}
55 escape = False
56 hexa = False
57 octa = False
58 num = 0
59 digits = 0
60 r = u''
61 for c in s:
62 if not escape:
63 if c == '\\':
64 escape = True
65 else:
66 r = r + c
67 elif hexa:
68 if (c >= '0' and c <= '9') or \
69 (c >= 'a' and c <= 'f') or \
70 (c >= 'A' and c <= 'F'):
71 num = num * 16 + int(c, 16)
72 digits = digits + 1
73 else:
74 digits = 2
75 if digits >= 2:
76 hexa = False
77 escape = False
78 r = r + chr(num)
79 elif octa:
80 if c >= '0' and c <= '7':
81 num = num * 8 + int(c, 8)
82 digits = digits + 1
83 else:
84 digits = 3
85 if digits >= 3:
86 octa = False
87 escape = False
88 r = r + chr(num)
89 else:
90 if c in escapeSeqs:
91 r = r + escapeSeqs[c]
92 escape = False
93 elif c >= '0' and c <= '7':
94 octa = True
95 num = int(c, 8)
96 if num <= 3:
97 digits = 1
98 else:
99 digits = 2
100 elif c == 'x' or c == 'X':
101 hexa = True
102 num = 0
103 digits = 0
104 else:
105 r = r + c
106 escape = False
107 return r
108
109 # Expand matches. The first match is always a DESC or DESC_BEGIN match.
110 # Subsequent matches are ENUM matches.
111 #
112 # DESC, DESC_BEGIN format: \1 \2=<lang> \3 \4=gettext(" \5=<text> \6=") \7
113 # ENUM format: \1 \2=gettext(" \3=<text> \4=") \5
114 def expandMatches(matches, translations, outfile, end=None):
115 assert len(matches) > 0
116 nTranslations = len(translations)
117 i = 0
118 # Expand the description+enums for all translations
119 for lang,trans in translations:
120 i = i + 1
121 # Make sure that all but the last line of a simple description
122 # are extended with a backslash.
123 suffix = ''
124 if len(matches) == 1 and i < len(translations) and \
125 not matches[0].expand(r'\7').endswith('\\'):
126 suffix = ' \\'
127 text = escapeCString(getattr(trans, gettext_method)(expandCString(
128 matches[0].expand (r'\5'))))
129 text = (matches[0].expand(r'\1' + lang + r'\3"' + text + r'"\7') + suffix)
130
131 outfile.write(text + '\n')
132
133 # Expand any subsequent enum lines
134 for match in matches[1:]:
135 text = escapeCString(getattr(trans, gettext_method)(expandCString(
136 match.expand(r'\3'))))
137 text = match.expand(r'\1"' + text + r'"\5')
138
139 outfile.write(text + '\n')
140
141 # Expand description end
142 if end:
143 outfile.write(end)
144
145 # Regular expressions:
146 reLibintl_h = re.compile(r'#\s*include\s*<libintl.h>')
147 reDESC = re.compile(r'(\s*DRI_CONF_DESC\s*\(\s*)([a-z]+)(\s*,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$')
148 reDESC_BEGIN = re.compile(r'(\s*DRI_CONF_DESC_BEGIN\s*\(\s*)([a-z]+)(\s*,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$')
149 reENUM = re.compile(r'(\s*DRI_CONF_ENUM\s*\([^,]+,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$')
150 reDESC_END = re.compile(r'\s*DRI_CONF_DESC_END')
151
152
153 def main():
154 parser = argparse.ArgumentParser()
155 parser.add_argument('--template', required=True)
156 parser.add_argument('--output', required=True)
157 parser.add_argument('--localedir', required=True)
158 parser.add_argument('--languages', nargs='*', default=[])
159 args = parser.parse_args()
160
161 # Compile a list of translation classes to all supported languages.
162 # The first translation is always a NullTranslations.
163 translations = [("en", gettext.NullTranslations())]
164 for lang in args.languages:
165 try:
166 filename = os.path.join(args.localedir, '{}.gmo'.format(lang))
167 with io.open(filename, 'rb') as f:
168 trans = gettext.GNUTranslations(f)
169 except (IOError, OSError):
170 print("Warning: language '%s' not found." % lang, file=sys.stderr)
171 continue
172 translations.append((lang, trans))
173
174 with io.open(args.output, mode='wt', encoding='utf-8') as output:
175 output.write("/* This is file is generated automatically. Don't edit! */\n")
176
177 # Process the options template and generate options.h with all
178 # translations.
179 with io.open(args.template, mode="rt", encoding='utf-8') as template:
180 descMatches = []
181 for line in template:
182 if descMatches:
183 matchENUM = reENUM.match(line)
184 matchDESC_END = reDESC_END.match(line)
185 if matchENUM:
186 descMatches.append(matchENUM)
187 elif matchDESC_END:
188 expandMatches(descMatches, translations, output, line)
189 descMatches = []
190 else:
191 print("Warning: unexpected line inside description dropped:\n",
192 line, file=sys.stderr)
193 continue
194 if reLibintl_h.search(line):
195 # Ignore (comment out) #include <libintl.h>
196 output.write("/* %s * commented out by gen_xmlpool.py */\n" % line)
197 continue
198 matchDESC = reDESC.match(line)
199 matchDESC_BEGIN = reDESC_BEGIN.match(line)
200 if matchDESC:
201 assert not descMatches
202 expandMatches([matchDESC], translations, output)
203 elif matchDESC_BEGIN:
204 assert not descMatches
205 descMatches = [matchDESC_BEGIN]
206 else:
207
208 output.write(line)
209
210 if descMatches:
211 print("Warning: unterminated description at end of file.", file=sys.stderr)
212 expandMatches(descMatches, translations, output)
213
214
215 if __name__ == '__main__':
216 main()