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