2005-04-11 21:41:40 +00:00
|
|
|
|
2012-12-05 20:47:52 -08:00
|
|
|
#
|
|
|
|
|
# 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`.
|
|
|
|
|
#
|
|
|
|
|
|
2018-07-05 15:17:32 +02:00
|
|
|
from __future__ import print_function
|
2018-08-24 06:28:41 -07:00
|
|
|
import argparse
|
2005-04-11 21:41:40 +00:00
|
|
|
import gettext
|
2018-08-24 06:49:55 -07:00
|
|
|
import io
|
|
|
|
|
import os
|
2005-04-11 21:41:40 +00:00
|
|
|
import re
|
2018-08-24 06:49:55 -07:00
|
|
|
import sys
|
2005-04-11 21:41:40 +00:00
|
|
|
|
2018-08-10 23:17:08 +02:00
|
|
|
if sys.version_info < (3, 0):
|
|
|
|
|
gettext_method = 'ugettext'
|
|
|
|
|
else:
|
|
|
|
|
gettext_method = 'gettext'
|
|
|
|
|
|
2005-04-11 21:41:40 +00:00
|
|
|
# Escape special characters in C strings
|
2018-10-24 11:56:49 -07:00
|
|
|
def escapeCString(s):
|
2005-04-11 21:41:40 +00:00
|
|
|
escapeSeqs = {'\a' : '\\a', '\b' : '\\b', '\f' : '\\f', '\n' : '\\n',
|
2005-04-12 20:58:34 +00:00
|
|
|
'\r' : '\\r', '\t' : '\\t', '\v' : '\\v', '\\' : '\\\\'}
|
2005-04-11 21:41:40 +00:00
|
|
|
# " -> '' is a hack. Quotes (") aren't possible in XML attributes.
|
|
|
|
|
# Better use Unicode characters for typographic quotes in option
|
|
|
|
|
# descriptions and translations.
|
|
|
|
|
i = 0
|
|
|
|
|
r = ''
|
|
|
|
|
while i < len(s):
|
2005-04-12 20:58:34 +00:00
|
|
|
# Special case: escape double quote with \u201c or \u201d, depending
|
|
|
|
|
# on whether it's an open or close quote. This is needed because plain
|
|
|
|
|
# double quotes are not possible in XML attributes.
|
|
|
|
|
if s[i] == '"':
|
2018-10-24 11:56:49 -07:00
|
|
|
if i == len(s) - 1 or s[i + 1].isspace():
|
2005-04-12 20:58:34 +00:00
|
|
|
# close quote
|
|
|
|
|
q = u'\u201c'
|
|
|
|
|
else:
|
|
|
|
|
# open quote
|
|
|
|
|
q = u'\u201d'
|
|
|
|
|
r = r + q
|
2018-07-05 15:17:35 +02:00
|
|
|
elif s[i] in escapeSeqs:
|
2005-04-11 21:41:40 +00:00
|
|
|
r = r + escapeSeqs[s[i]]
|
|
|
|
|
else:
|
|
|
|
|
r = r + s[i]
|
|
|
|
|
i = i + 1
|
|
|
|
|
return r
|
|
|
|
|
|
|
|
|
|
# Expand escape sequences in C strings (needed for gettext lookup)
|
2018-10-24 11:56:49 -07:00
|
|
|
def expandCString(s):
|
2005-04-11 21:41:40 +00:00
|
|
|
escapeSeqs = {'a' : '\a', 'b' : '\b', 'f' : '\f', 'n' : '\n',
|
|
|
|
|
'r' : '\r', 't' : '\t', 'v' : '\v',
|
|
|
|
|
'"' : '"', '\\' : '\\'}
|
|
|
|
|
i = 0
|
|
|
|
|
escape = False
|
|
|
|
|
hexa = False
|
|
|
|
|
octa = False
|
|
|
|
|
num = 0
|
|
|
|
|
digits = 0
|
2018-08-10 23:17:08 +02:00
|
|
|
r = u''
|
2005-04-11 21:41:40 +00:00
|
|
|
while i < len(s):
|
|
|
|
|
if not escape:
|
|
|
|
|
if s[i] == '\\':
|
|
|
|
|
escape = True
|
|
|
|
|
else:
|
|
|
|
|
r = r + s[i]
|
|
|
|
|
elif hexa:
|
|
|
|
|
if (s[i] >= '0' and s[i] <= '9') or \
|
|
|
|
|
(s[i] >= 'a' and s[i] <= 'f') or \
|
|
|
|
|
(s[i] >= 'A' and s[i] <= 'F'):
|
|
|
|
|
num = num * 16 + int(s[i],16)
|
|
|
|
|
digits = digits + 1
|
|
|
|
|
else:
|
|
|
|
|
digits = 2
|
|
|
|
|
if digits >= 2:
|
|
|
|
|
hexa = False
|
|
|
|
|
escape = False
|
|
|
|
|
r = r + chr(num)
|
|
|
|
|
elif octa:
|
|
|
|
|
if s[i] >= '0' and s[i] <= '7':
|
|
|
|
|
num = num * 8 + int(s[i],8)
|
|
|
|
|
digits = digits + 1
|
|
|
|
|
else:
|
|
|
|
|
digits = 3
|
|
|
|
|
if digits >= 3:
|
|
|
|
|
octa = False
|
|
|
|
|
escape = False
|
|
|
|
|
r = r + chr(num)
|
|
|
|
|
else:
|
2018-07-05 15:17:35 +02:00
|
|
|
if s[i] in escapeSeqs:
|
2005-04-11 21:41:40 +00:00
|
|
|
r = r + escapeSeqs[s[i]]
|
|
|
|
|
escape = False
|
|
|
|
|
elif s[i] >= '0' and s[i] <= '7':
|
|
|
|
|
octa = True
|
|
|
|
|
num = int(s[i],8)
|
|
|
|
|
if num <= 3:
|
|
|
|
|
digits = 1
|
|
|
|
|
else:
|
|
|
|
|
digits = 2
|
|
|
|
|
elif s[i] == 'x' or s[i] == 'X':
|
|
|
|
|
hexa = True
|
|
|
|
|
num = 0
|
|
|
|
|
digits = 0
|
|
|
|
|
else:
|
|
|
|
|
r = r + s[i]
|
|
|
|
|
escape = False
|
|
|
|
|
i = i + 1
|
|
|
|
|
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
|
2018-10-24 11:56:49 -07:00
|
|
|
def expandMatches(matches, translations, end=None):
|
2005-04-11 21:41:40 +00:00
|
|
|
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 \
|
2018-10-24 11:56:49 -07:00
|
|
|
not matches[0].expand(r'\7').endswith('\\'):
|
2005-04-11 21:41:40 +00:00
|
|
|
suffix = ' \\'
|
2018-10-24 11:56:49 -07:00
|
|
|
text = escapeCString(getattr(trans, gettext_method)(expandCString(
|
2018-08-10 23:17:08 +02:00
|
|
|
matches[0].expand (r'\5'))))
|
2018-10-24 11:56:49 -07:00
|
|
|
text = (matches[0].expand(r'\1' + lang + r'\3"' + text + r'"\7') + suffix)
|
2018-08-10 23:17:08 +02:00
|
|
|
|
|
|
|
|
# In Python 2, stdout expects encoded byte strings, or else it will
|
|
|
|
|
# encode them with the ascii 'codec'
|
|
|
|
|
if sys.version_info.major == 2:
|
|
|
|
|
text = text.encode('utf-8')
|
|
|
|
|
|
|
|
|
|
print(text)
|
|
|
|
|
|
2005-04-11 21:41:40 +00:00
|
|
|
# Expand any subsequent enum lines
|
|
|
|
|
for match in matches[1:]:
|
2018-10-24 11:56:49 -07:00
|
|
|
text = escapeCString(getattr(trans, gettext_method)(expandCString(
|
|
|
|
|
match.expand(r'\3'))))
|
|
|
|
|
text = match.expand(r'\1"' + text + r'"\5')
|
2018-08-10 23:17:08 +02:00
|
|
|
|
|
|
|
|
# In Python 2, stdout expects encoded byte strings, or else it will
|
|
|
|
|
# encode them with the ascii 'codec'
|
|
|
|
|
if sys.version_info.major == 2:
|
|
|
|
|
text = text.encode('utf-8')
|
|
|
|
|
|
|
|
|
|
print(text)
|
2005-04-11 21:41:40 +00:00
|
|
|
|
|
|
|
|
# Expand description end
|
|
|
|
|
if end:
|
2018-07-05 15:17:32 +02:00
|
|
|
print(end, end='')
|
2005-04-11 21:41:40 +00:00
|
|
|
|
|
|
|
|
# Regular expressions:
|
2018-10-24 11:56:49 -07:00
|
|
|
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')
|
2005-04-11 21:41:40 +00:00
|
|
|
|
|
|
|
|
|
2018-10-24 12:05:56 -07:00
|
|
|
def main():
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
|
parser.add_argument('template')
|
|
|
|
|
parser.add_argument('localedir')
|
|
|
|
|
parser.add_argument('languages', nargs='*')
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
print("/***********************************************************************\n" \
|
|
|
|
|
" *** THIS FILE IS GENERATED AUTOMATICALLY. DON'T EDIT! ***\n" \
|
|
|
|
|
" ***********************************************************************/")
|
|
|
|
|
|
|
|
|
|
# Process the options template and generate options.h with all
|
|
|
|
|
# translations.
|
|
|
|
|
template = io.open(args.template, mode="rt", encoding='utf-8')
|
|
|
|
|
descMatches = []
|
|
|
|
|
for line in template:
|
|
|
|
|
if len(descMatches) > 0:
|
|
|
|
|
matchENUM = reENUM.match(line)
|
|
|
|
|
matchDESC_END = reDESC_END.match(line)
|
|
|
|
|
if matchENUM:
|
|
|
|
|
descMatches.append(matchENUM)
|
|
|
|
|
elif matchDESC_END:
|
|
|
|
|
expandMatches(descMatches, translations, 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>
|
|
|
|
|
print("/* %s * commented out by gen_xmlpool.py */" % line)
|
|
|
|
|
continue
|
|
|
|
|
matchDESC = reDESC.match(line)
|
|
|
|
|
matchDESC_BEGIN = reDESC_BEGIN.match(line)
|
|
|
|
|
if matchDESC:
|
|
|
|
|
assert len(descMatches) == 0
|
|
|
|
|
expandMatches([matchDESC], translations)
|
|
|
|
|
elif matchDESC_BEGIN:
|
|
|
|
|
assert len(descMatches) == 0
|
|
|
|
|
descMatches = [matchDESC_BEGIN]
|
2005-04-11 21:41:40 +00:00
|
|
|
else:
|
2018-10-24 12:05:56 -07:00
|
|
|
# In Python 2, stdout expects encoded byte strings, or else it will
|
|
|
|
|
# encode them with the ascii 'codec'
|
|
|
|
|
if sys.version_info.major == 2:
|
|
|
|
|
line = line.encode('utf-8')
|
2018-08-17 09:33:02 -06:00
|
|
|
|
2018-10-24 12:05:56 -07:00
|
|
|
print(line, end='')
|
|
|
|
|
|
|
|
|
|
template.close()
|
|
|
|
|
|
|
|
|
|
if len(descMatches) > 0:
|
|
|
|
|
print("Warning: unterminated description at end of file.", file=sys.stderr)
|
|
|
|
|
expandMatches(descMatches, translations)
|
2005-04-11 21:41:40 +00:00
|
|
|
|
2018-06-01 15:02:21 +02:00
|
|
|
|
2018-10-24 12:05:56 -07:00
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|