gen_xmlpool.py 7.54 KB
Newer Older
1
# encoding=utf-8
2 3 4 5 6 7 8 9
#
# Usage:
#     gen_xmlpool.py /path/to/t_option.h localedir lang lang lang ...
#
# For each given language, this script expects to find a .mo file at
# `{localedir}/{language}/LC_MESSAGES/options.mo`.
#

10
from __future__ import print_function, unicode_literals
11
import argparse
12
import gettext
13 14
import io
import os
15
import re
16
import sys
17

18 19 20 21 22
if sys.version_info < (3, 0):
    gettext_method = 'ugettext'
else:
    gettext_method = 'gettext'

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 28 29
    # " -> '' is a hack. Quotes (") aren't possible in XML attributes.
    # Better use Unicode characters for typographic quotes in option
    # descriptions and translations.
30
    last_quote = '”'
31 32
    i = 0
    r = ''
33 34
    for c in s:
        # Special case: escape double quote with “ or ”, depending
35 36
        # on whether it's an open or close quote. This is needed because plain
        # double quotes are not possible in XML attributes.
37 38 39
        if c == '"':
            if last_quote == '”':
                q = '“'
40
            else:
41 42
                q = '”'
            last_quote = q
43
            r = r + q
44 45
        elif c in escapeSeqs:
            r = r + escapeSeqs[c]
46
        else:
47
            r = r + c
48 49 50
    return r

# Expand escape sequences in C strings (needed for gettext lookup)
51
def expandCString(s):
52 53 54 55 56 57 58 59
    escapeSeqs = {'a' : '\a', 'b' : '\b', 'f' : '\f', 'n' : '\n',
                  'r' : '\r', 't' : '\t', 'v' : '\v',
                  '"' : '"', '\\' : '\\'}
    escape = False
    hexa = False
    octa = False
    num = 0
    digits = 0
60
    r = u''
61
    for c in s:
62
        if not escape:
63
            if c == '\\':
64 65
                escape = True
            else:
66
                r = r + c
67
        elif hexa:
68 69 70 71
            if (c >= '0' and c <= '9') or \
               (c >= 'a' and c <= 'f') or \
               (c >= 'A' and c <= 'F'):
                num = num * 16 + int(c, 16)
72 73 74 75 76 77 78 79
                digits = digits + 1
            else:
                digits = 2
            if digits >= 2:
                hexa = False
                escape = False
                r = r + chr(num)
        elif octa:
80 81
            if c >= '0' and c <= '7':
                num = num * 8 + int(c, 8)
82 83 84 85 86 87 88 89
                digits = digits + 1
            else:
                digits = 3
            if digits >= 3:
                octa = False
                escape = False
                r = r + chr(num)
        else:
90 91
            if c in escapeSeqs:
                r = r + escapeSeqs[c]
92
                escape = False
93
            elif c >= '0' and c <= '7':
94
                octa = True
95
                num = int(c, 8)
96 97 98 99
                if num <= 3:
                    digits = 1
                else:
                    digits = 2
100
            elif c == 'x' or c == 'X':
101 102 103 104
                hexa = True
                num = 0
                digits = 0
            else:
105
                r = r + c
106 107 108 109 110 111 112 113
                escape = False
    return r

# Expand matches. The first match is always a DESC or DESC_BEGIN match.
# Subsequent matches are ENUM matches.
#
# DESC, DESC_BEGIN format: \1 \2=<lang> \3 \4=gettext(" \5=<text> \6=") \7
# ENUM format:             \1 \2=gettext(" \3=<text> \4=") \5
114
def expandMatches(matches, translations, outfile, end=None):
115 116 117 118 119 120 121 122 123 124
    assert len(matches) > 0
    nTranslations = len(translations)
    i = 0
    # Expand the description+enums for all translations
    for lang,trans in translations:
        i = i + 1
        # Make sure that all but the last line of a simple description
        # are extended with a backslash.
        suffix = ''
        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 134
        # Expand any subsequent enum lines
        for match in matches[1:]:
135 136 137
            text = escapeCString(getattr(trans, gettext_method)(expandCString(
                match.expand(r'\3'))))
            text = match.expand(r'\1"' + text + r'"\5')
138

139
            outfile.write(text + '\n')
140 141 142

        # Expand description end
        if end:
143
            outfile.write(end)
144 145

# Regular expressions:
146 147 148 149 150
reLibintl_h = re.compile(r'#\s*include\s*<libintl.h>')
reDESC = re.compile(r'(\s*DRI_CONF_DESC\s*\(\s*)([a-z]+)(\s*,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$')
reDESC_BEGIN = re.compile(r'(\s*DRI_CONF_DESC_BEGIN\s*\(\s*)([a-z]+)(\s*,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$')
reENUM = re.compile(r'(\s*DRI_CONF_ENUM\s*\([^,]+,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$')
reDESC_END = re.compile(r'\s*DRI_CONF_DESC_END')
151 152


153 154
def main():
    parser = argparse.ArgumentParser()
155 156 157 158
    parser.add_argument('--template', required=True)
    parser.add_argument('--output', required=True)
    parser.add_argument('--localedir', required=True)
    parser.add_argument('--languages', nargs='*', default=[])
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
    args = parser.parse_args()

    # Compile a list of translation classes to all supported languages.
    # The first translation is always a NullTranslations.
    translations = [("en", gettext.NullTranslations())]
    for lang in args.languages:
        try:
            filename = os.path.join(args.localedir, '{}.gmo'.format(lang))
            with io.open(filename, 'rb') as f:
                trans = gettext.GNUTranslations(f)
        except (IOError, OSError):
            print("Warning: language '%s' not found." % lang, file=sys.stderr)
            continue
        translations.append((lang, trans))

174 175 176 177 178 179 180 181
    with io.open(args.output, mode='wt', encoding='utf-8') as output:
        output.write("/* This is file is generated automatically. Don't edit!  */\n")

        # Process the options template and generate options.h with all
        # translations.
        with io.open(args.template, mode="rt", encoding='utf-8') as template:
            descMatches = []
            for line in template:
182
                if descMatches:
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
                    matchENUM = reENUM.match(line)
                    matchDESC_END = reDESC_END.match(line)
                    if matchENUM:
                        descMatches.append(matchENUM)
                    elif matchDESC_END:
                        expandMatches(descMatches, translations, output, line)
                        descMatches = []
                    else:
                        print("Warning: unexpected line inside description dropped:\n",
                              line, file=sys.stderr)
                    continue
                if reLibintl_h.search(line):
                    # Ignore (comment out) #include <libintl.h>
                    output.write("/* %s * commented out by gen_xmlpool.py */\n" % line)
                    continue
                matchDESC = reDESC.match(line)
                matchDESC_BEGIN = reDESC_BEGIN.match(line)
                if matchDESC:
201
                    assert not descMatches
202 203
                    expandMatches([matchDESC], translations, output)
                elif matchDESC_BEGIN:
204
                    assert not descMatches
205
                    descMatches = [matchDESC_BEGIN]
206
                else:
207

208
                    output.write(line)
209

210
        if descMatches:
211 212
            print("Warning: unterminated description at end of file.", file=sys.stderr)
            expandMatches(descMatches, translations, output)
213

214

215 216
if __name__ == '__main__':
    main()