2012-07-02 19:51:06 +00:00
|
|
|
|
#!/usr/bin/python3
|
|
|
|
|
|
|
|
|
|
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
|
|
|
#
|
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
|
# modify it under the terms of the GNU General Public License
|
|
|
|
|
# as published by the Free Software Foundation; either version 2
|
|
|
|
|
# of the License, or (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
|
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
#
|
|
|
|
|
# ***** END GPL LICENSE BLOCK *****
|
|
|
|
|
|
|
|
|
|
# <pep8 compliant>
|
|
|
|
|
|
|
|
|
|
# Update blender.pot file from messages.txt
|
|
|
|
|
|
|
|
|
|
import subprocess
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
import re
|
|
|
|
|
import tempfile
|
|
|
|
|
import argparse
|
|
|
|
|
import time
|
|
|
|
|
import pickle
|
|
|
|
|
|
2012-07-08 17:10:10 +00:00
|
|
|
|
try:
|
|
|
|
|
import settings
|
|
|
|
|
import utils
|
|
|
|
|
except:
|
|
|
|
|
from . import (settings, utils)
|
2012-07-02 19:51:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
COMMENT_PREFIX = settings.COMMENT_PREFIX
|
|
|
|
|
COMMENT_PREFIX_SOURCE = settings.COMMENT_PREFIX_SOURCE
|
|
|
|
|
CONTEXT_PREFIX = settings.CONTEXT_PREFIX
|
|
|
|
|
FILE_NAME_MESSAGES = settings.FILE_NAME_MESSAGES
|
|
|
|
|
FILE_NAME_POT = settings.FILE_NAME_POT
|
|
|
|
|
SOURCE_DIR = settings.SOURCE_DIR
|
|
|
|
|
POTFILES_DIR = settings.POTFILES_SOURCE_DIR
|
|
|
|
|
SRC_POTFILES = settings.FILE_NAME_SRC_POTFILES
|
|
|
|
|
|
|
|
|
|
CONTEXT_DEFAULT = settings.CONTEXT_DEFAULT
|
|
|
|
|
PYGETTEXT_ALLOWED_EXTS = settings.PYGETTEXT_ALLOWED_EXTS
|
|
|
|
|
|
|
|
|
|
SVN_EXECUTABLE = settings.SVN_EXECUTABLE
|
|
|
|
|
|
|
|
|
|
WARN_NC = settings.WARN_MSGID_NOT_CAPITALIZED
|
|
|
|
|
NC_ALLOWED = settings.WARN_MSGID_NOT_CAPITALIZED_ALLOWED
|
|
|
|
|
|
|
|
|
|
SPELL_CACHE = settings.SPELL_CACHE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Do this only once!
|
|
|
|
|
# Get contexts defined in blf.
|
|
|
|
|
CONTEXTS = {}
|
|
|
|
|
with open(os.path.join(SOURCE_DIR, settings.PYGETTEXT_CONTEXTS_DEFSRC)) as f:
|
|
|
|
|
reg = re.compile(settings.PYGETTEXT_CONTEXTS)
|
|
|
|
|
f = f.read()
|
|
|
|
|
# This regex is supposed to yield tuples
|
|
|
|
|
# (key=C_macro_name, value=C_string).
|
|
|
|
|
CONTEXTS = dict(m.groups() for m in reg.finditer(f))
|
|
|
|
|
|
2012-07-07 14:28:49 +00:00
|
|
|
|
# Build regexes to extract messages (with optional contexts) from C source.
|
2012-07-02 19:51:06 +00:00
|
|
|
|
pygettexts = tuple(re.compile(r).search
|
|
|
|
|
for r in settings.PYGETTEXT_KEYWORDS)
|
|
|
|
|
_clean_str = re.compile(settings.str_clean_re).finditer
|
|
|
|
|
clean_str = lambda s: "".join(m.group("clean") for m in _clean_str(s))
|
|
|
|
|
|
|
|
|
|
def check_file(path, rel_path, messages):
|
|
|
|
|
with open(path, encoding="utf-8") as f:
|
|
|
|
|
f = f.read()
|
|
|
|
|
for srch in pygettexts:
|
|
|
|
|
m = srch(f)
|
|
|
|
|
line = pos =0
|
|
|
|
|
while m:
|
|
|
|
|
d = m.groupdict()
|
|
|
|
|
# Context.
|
|
|
|
|
ctxt = d.get("ctxt_raw")
|
|
|
|
|
if ctxt:
|
|
|
|
|
if ctxt in CONTEXTS:
|
|
|
|
|
ctxt = CONTEXTS[ctxt]
|
|
|
|
|
elif '"' in ctxt or "'" in ctxt:
|
|
|
|
|
ctxt = clean_str(ctxt)
|
|
|
|
|
else:
|
|
|
|
|
print("WARNING: raw context “{}” couldn’t be resolved!"
|
|
|
|
|
"".format(ctxt))
|
|
|
|
|
ctxt = CONTEXT_DEFAULT
|
|
|
|
|
else:
|
|
|
|
|
ctxt = CONTEXT_DEFAULT
|
|
|
|
|
# Message.
|
|
|
|
|
msg = d.get("msg_raw")
|
|
|
|
|
if msg:
|
|
|
|
|
if '"' in msg or "'" in msg:
|
|
|
|
|
msg = clean_str(msg)
|
|
|
|
|
else:
|
|
|
|
|
print("WARNING: raw message “{}” couldn’t be resolved!"
|
|
|
|
|
"".format(msg))
|
|
|
|
|
msg = ""
|
|
|
|
|
else:
|
|
|
|
|
msg = ""
|
|
|
|
|
# Line.
|
|
|
|
|
line += f[pos:m.start()].count('\n')
|
|
|
|
|
# And we are done for this item!
|
|
|
|
|
messages.setdefault((ctxt, msg), []).append(":".join((rel_path, str(line))))
|
|
|
|
|
pos = m.end()
|
|
|
|
|
line += f[m.start():pos].count('\n')
|
|
|
|
|
m = srch(f, pos)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def py_xgettext(messages):
|
|
|
|
|
with open(SRC_POTFILES) as src:
|
|
|
|
|
forbidden = set()
|
|
|
|
|
forced = set()
|
|
|
|
|
for l in src:
|
|
|
|
|
if l[0] == '-':
|
|
|
|
|
forbidden.add(l[1:].rstrip('\n'))
|
|
|
|
|
elif l[0] != '#':
|
|
|
|
|
forced.add(l.rstrip('\n'))
|
|
|
|
|
for root, dirs, files in os.walk(POTFILES_DIR):
|
|
|
|
|
if "/.svn" in root:
|
|
|
|
|
continue
|
|
|
|
|
for fname in files:
|
|
|
|
|
if os.path.splitext(fname)[1] not in PYGETTEXT_ALLOWED_EXTS:
|
|
|
|
|
continue
|
|
|
|
|
path = os.path.join(root, fname)
|
|
|
|
|
rel_path = os.path.relpath(path, SOURCE_DIR)
|
|
|
|
|
if rel_path in forbidden | forced:
|
|
|
|
|
continue
|
|
|
|
|
check_file(path, rel_path, messages)
|
|
|
|
|
for path in forced:
|
|
|
|
|
if os.path.exists(path):
|
|
|
|
|
check_file(os.path.join(SOURCE_DIR, path), path, messages)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Spell checking!
|
|
|
|
|
import enchant
|
|
|
|
|
dict_spelling = enchant.Dict("en_US")
|
|
|
|
|
|
|
|
|
|
from spell_check_utils import (dict_uimsgs,
|
|
|
|
|
split_words,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_spell_checked = set()
|
|
|
|
|
def spell_check(txt, cache):
|
|
|
|
|
ret = []
|
|
|
|
|
|
|
|
|
|
if cache is not None and txt in cache:
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
for w in split_words(txt):
|
|
|
|
|
w_lower = w.lower()
|
|
|
|
|
if w_lower in dict_uimsgs | _spell_checked:
|
|
|
|
|
continue
|
|
|
|
|
if not dict_spelling.check(w):
|
|
|
|
|
ret.append("{}: suggestions are ({})"
|
|
|
|
|
.format(w, "'" + "', '".join(dict_spelling.suggest(w))
|
|
|
|
|
+ "'"))
|
|
|
|
|
else:
|
|
|
|
|
_spell_checked.add(w_lower)
|
|
|
|
|
|
|
|
|
|
if not ret:
|
|
|
|
|
if cache is not None:
|
|
|
|
|
cache.add(txt)
|
|
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_svnrev():
|
|
|
|
|
cmd = [SVN_EXECUTABLE,
|
|
|
|
|
"info",
|
|
|
|
|
"--xml",
|
|
|
|
|
SOURCE_DIR,
|
|
|
|
|
]
|
|
|
|
|
xml = subprocess.check_output(cmd)
|
|
|
|
|
return re.search(b'revision="(\d+)"', xml).group(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def gen_empty_pot():
|
|
|
|
|
blender_rev = get_svnrev()
|
|
|
|
|
utctime = time.gmtime()
|
|
|
|
|
time_str = time.strftime("%Y-%m-%d %H:%M+0000", utctime)
|
|
|
|
|
year_str = time.strftime("%Y", utctime)
|
|
|
|
|
|
|
|
|
|
return utils.gen_empty_messages(blender_rev, time_str, year_str)
|
|
|
|
|
|
|
|
|
|
|
2012-07-07 14:28:49 +00:00
|
|
|
|
escape_re = tuple(re.compile(r[0]) for r in settings.ESCAPE_RE)
|
|
|
|
|
escape = lambda s, n: escape_re[n].sub(settings.ESCAPE_RE[n][1], s)
|
2012-07-02 19:51:06 +00:00
|
|
|
|
def merge_messages(msgs, states, messages, do_checks, spell_cache):
|
|
|
|
|
num_added = num_present = 0
|
|
|
|
|
for (context, msgid), srcs in messages.items():
|
|
|
|
|
if do_checks:
|
|
|
|
|
err = spell_check(msgid, spell_cache)
|
|
|
|
|
if err:
|
|
|
|
|
print("WARNING: spell check failed on “" + msgid + "”:")
|
|
|
|
|
print("\t\t" + "\n\t\t".join(err))
|
|
|
|
|
print("\tFrom:\n\t\t" + "\n\t\t".join(srcs))
|
|
|
|
|
|
|
|
|
|
# Escape some chars in msgid!
|
2012-07-07 14:28:49 +00:00
|
|
|
|
for n in range(len(escape_re)):
|
|
|
|
|
msgid = escape(msgid, n)
|
2012-07-02 19:51:06 +00:00
|
|
|
|
|
|
|
|
|
srcs = [COMMENT_PREFIX_SOURCE + s for s in srcs]
|
|
|
|
|
|
|
|
|
|
key = (context, msgid)
|
|
|
|
|
if key not in msgs:
|
|
|
|
|
msgs[key] = {"msgid_lines": [msgid],
|
|
|
|
|
"msgstr_lines": [""],
|
|
|
|
|
"comment_lines": srcs,
|
|
|
|
|
"msgctxt_lines": [context]}
|
|
|
|
|
num_added += 1
|
|
|
|
|
else:
|
|
|
|
|
# We need to merge comments!
|
|
|
|
|
msgs[key]["comment_lines"].extend(srcs)
|
|
|
|
|
num_present += 1
|
|
|
|
|
|
|
|
|
|
return num_added, num_present
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
parser = argparse.ArgumentParser(description="Update blender.pot file " \
|
|
|
|
|
"from messages.txt")
|
|
|
|
|
parser.add_argument('-w', '--warning', action="store_true",
|
|
|
|
|
help="Show warnings.")
|
|
|
|
|
parser.add_argument('-i', '--input', metavar="File",
|
|
|
|
|
help="Input messages file path.")
|
|
|
|
|
parser.add_argument('-o', '--output', metavar="File",
|
|
|
|
|
help="Output pot file path.")
|
|
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
if args.input:
|
|
|
|
|
global FILE_NAME_MESSAGES
|
|
|
|
|
FILE_NAME_MESSAGES = args.input
|
|
|
|
|
if args.output:
|
|
|
|
|
global FILE_NAME_POT
|
|
|
|
|
FILE_NAME_POT = args.output
|
|
|
|
|
|
|
|
|
|
print("Running fake py gettext…")
|
|
|
|
|
# Not using any more xgettext, simpler to do it ourself!
|
|
|
|
|
messages = {}
|
|
|
|
|
py_xgettext(messages)
|
|
|
|
|
print("Finished, found {} messages.".format(len(messages)))
|
|
|
|
|
|
|
|
|
|
if SPELL_CACHE and os.path.exists(SPELL_CACHE):
|
|
|
|
|
with open(SPELL_CACHE, 'rb') as f:
|
|
|
|
|
spell_cache = pickle.load(f)
|
|
|
|
|
else:
|
|
|
|
|
spell_cache = set()
|
|
|
|
|
print(len(spell_cache))
|
|
|
|
|
|
|
|
|
|
print("Generating POT file {}…".format(FILE_NAME_POT))
|
|
|
|
|
msgs, states = gen_empty_pot()
|
|
|
|
|
tot_messages, _a = merge_messages(msgs, states, messages,
|
|
|
|
|
True, spell_cache)
|
|
|
|
|
|
|
|
|
|
# add messages collected automatically from RNA
|
|
|
|
|
print("\tMerging RNA messages from {}…".format(FILE_NAME_MESSAGES))
|
|
|
|
|
messages = {}
|
|
|
|
|
with open(FILE_NAME_MESSAGES, encoding="utf-8") as f:
|
|
|
|
|
srcs = []
|
|
|
|
|
context = ""
|
|
|
|
|
for line in f:
|
|
|
|
|
line = utils.stripeol(line)
|
|
|
|
|
|
|
|
|
|
if line.startswith(COMMENT_PREFIX):
|
|
|
|
|
srcs.append(line[len(COMMENT_PREFIX):].strip())
|
|
|
|
|
elif line.startswith(CONTEXT_PREFIX):
|
|
|
|
|
context = line[len(CONTEXT_PREFIX):].strip()
|
|
|
|
|
else:
|
|
|
|
|
key = (context, line)
|
|
|
|
|
messages[key] = srcs
|
|
|
|
|
srcs = []
|
|
|
|
|
context = ""
|
|
|
|
|
num_added, num_present = merge_messages(msgs, states, messages,
|
|
|
|
|
True, spell_cache)
|
|
|
|
|
tot_messages += num_added
|
|
|
|
|
print("\tMerged {} messages ({} were already present)."
|
|
|
|
|
"".format(num_added, num_present))
|
|
|
|
|
|
|
|
|
|
# Write back all messages into blender.pot.
|
|
|
|
|
utils.write_messages(FILE_NAME_POT, msgs, states["comm_msg"],
|
|
|
|
|
states["fuzzy_msg"])
|
|
|
|
|
|
|
|
|
|
print(len(spell_cache))
|
|
|
|
|
if SPELL_CACHE and spell_cache:
|
|
|
|
|
with open(SPELL_CACHE, 'wb') as f:
|
|
|
|
|
pickle.dump(spell_cache, f)
|
|
|
|
|
|
|
|
|
|
print("Finished, total: {} messages!".format(tot_messages - 1))
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
print("\n\n *** Running {} *** \n".format(__file__))
|
|
|
|
|
sys.exit(main())
|