-
+# encoding=utf-8
#
# Usage:
# gen_xmlpool.py /path/to/t_option.h localedir lang lang lang ...
# `{localedir}/{language}/LC_MESSAGES/options.mo`.
#
-from __future__ import print_function
-
-import io
-import sys
+from __future__ import print_function, unicode_literals
+import argparse
import gettext
+import io
+import os
import re
-
+import sys
if sys.version_info < (3, 0):
gettext_method = 'ugettext'
else:
gettext_method = 'gettext'
-# Path to t_options.h
-template_header_path = sys.argv[1]
-
-localedir = sys.argv[2]
-
-# List of supported languages
-languages = sys.argv[3:]
-
# Escape special characters in C strings
-def escapeCString (s):
+def escapeCString(s):
escapeSeqs = {'\a' : '\\a', '\b' : '\\b', '\f' : '\\f', '\n' : '\\n',
'\r' : '\\r', '\t' : '\\t', '\v' : '\\v', '\\' : '\\\\'}
# " -> '' is a hack. Quotes (") aren't possible in XML attributes.
# Better use Unicode characters for typographic quotes in option
# descriptions and translations.
+ last_quote = '”'
i = 0
r = ''
- while i < len(s):
- # Special case: escape double quote with \u201c or \u201d, depending
+ for c in s:
+ # Special case: escape double quote with “ or ”, 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] == '"':
- if i == len(s)-1 or s[i+1].isspace():
- # close quote
- q = u'\u201c'
+ if c == '"':
+ if last_quote == '”':
+ q = '“'
else:
- # open quote
- q = u'\u201d'
+ q = '”'
+ last_quote = q
r = r + q
- elif s[i] in escapeSeqs:
- r = r + escapeSeqs[s[i]]
+ elif c in escapeSeqs:
+ r = r + escapeSeqs[c]
else:
- r = r + s[i]
- i = i + 1
+ r = r + c
return r
# Expand escape sequences in C strings (needed for gettext lookup)
-def expandCString (s):
+def expandCString(s):
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
r = u''
- while i < len(s):
+ for c in s:
if not escape:
- if s[i] == '\\':
+ if c == '\\':
escape = True
else:
- r = r + s[i]
+ r = r + c
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)
+ if (c >= '0' and c <= '9') or \
+ (c >= 'a' and c <= 'f') or \
+ (c >= 'A' and c <= 'F'):
+ num = num * 16 + int(c, 16)
digits = digits + 1
else:
digits = 2
escape = False
r = r + chr(num)
elif octa:
- if s[i] >= '0' and s[i] <= '7':
- num = num * 8 + int(s[i],8)
+ if c >= '0' and c <= '7':
+ num = num * 8 + int(c, 8)
digits = digits + 1
else:
digits = 3
escape = False
r = r + chr(num)
else:
- if s[i] in escapeSeqs:
- r = r + escapeSeqs[s[i]]
+ if c in escapeSeqs:
+ r = r + escapeSeqs[c]
escape = False
- elif s[i] >= '0' and s[i] <= '7':
+ elif c >= '0' and c <= '7':
octa = True
- num = int(s[i],8)
+ num = int(c, 8)
if num <= 3:
digits = 1
else:
digits = 2
- elif s[i] == 'x' or s[i] == 'X':
+ elif c == 'x' or c == 'X':
hexa = True
num = 0
digits = 0
else:
- r = r + s[i]
+ r = r + c
escape = False
- i = i + 1
return r
# Expand matches. The first match is always a DESC or DESC_BEGIN match.
#
# DESC, DESC_BEGIN format: \1 \2=<lang> \3 \4=gettext(" \5=<text> \6=") \7
# ENUM format: \1 \2=gettext(" \3=<text> \4=") \5
-def expandMatches (matches, translations, end=None):
+def expandMatches(matches, translations, outfile, end=None):
assert len(matches) > 0
nTranslations = len(translations)
i = 0
# are extended with a backslash.
suffix = ''
if len(matches) == 1 and i < len(translations) and \
- not matches[0].expand (r'\7').endswith('\\'):
+ not matches[0].expand(r'\7').endswith('\\'):
suffix = ' \\'
- text = escapeCString (getattr(trans, gettext_method) (expandCString (
+ text = escapeCString(getattr(trans, gettext_method)(expandCString(
matches[0].expand (r'\5'))))
- text = (matches[0].expand (r'\1' + lang + r'\3"' + text + r'"\7') + suffix)
-
- # 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')
+ text = (matches[0].expand(r'\1' + lang + r'\3"' + text + r'"\7') + suffix)
- print(text)
+ outfile.write(text + '\n')
# Expand any subsequent enum lines
for match in matches[1:]:
- text = escapeCString (getattr(trans, gettext_method) (expandCString (
- match.expand (r'\3'))))
- text = match.expand (r'\1"' + text + r'"\5')
+ text = escapeCString(getattr(trans, gettext_method)(expandCString(
+ match.expand(r'\3'))))
+ text = match.expand(r'\1"' + text + r'"\5')
- # 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)
+ outfile.write(text + '\n')
# Expand description end
if end:
- print(end, end='')
-
-# Compile a list of translation classes to all supported languages.
-# The first translation is always a NullTranslations.
-translations = [("en", gettext.NullTranslations())]
-for lang in languages:
- try:
- trans = gettext.translation ("options", localedir, [lang])
- except IOError:
- sys.stderr.write ("Warning: language '%s' not found.\n" % lang)
- continue
- translations.append ((lang, trans))
+ outfile.write(end)
# Regular expressions:
-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')
-
-# Print a header
-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 (template_header_path, 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)
+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')
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--template', required=True)
+ parser.add_argument('--output', required=True)
+ parser.add_argument('--localedir', required=True)
+ parser.add_argument('--languages', nargs='*', default=[])
+ 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))
+
+ 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 = []
- else:
- sys.stderr.write (
- "Warning: unexpected line inside description dropped:\n%s\n" \
- % line)
- 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]
- else:
- # 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')
-
- print(line, end='')
-
-template.close()
-
-if len(descMatches) > 0:
- sys.stderr.write ("Warning: unterminated description at end of file.\n")
- expandMatches (descMatches, translations)
+ for line in template:
+ if descMatches:
+ 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:
+ assert not descMatches
+ expandMatches([matchDESC], translations, output)
+ elif matchDESC_BEGIN:
+ assert not descMatches
+ descMatches = [matchDESC_BEGIN]
+ else:
+
+ output.write(line)
+
+ if descMatches:
+ print("Warning: unterminated description at end of file.", file=sys.stderr)
+ expandMatches(descMatches, translations, output)
+
+
+if __name__ == '__main__':
+ main()