Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'release/scripts/modules/i18n/bl_process_msg.py')
-rw-r--r--release/scripts/modules/i18n/bl_process_msg.py544
1 files changed, 544 insertions, 0 deletions
diff --git a/release/scripts/modules/i18n/bl_process_msg.py b/release/scripts/modules/i18n/bl_process_msg.py
new file mode 100644
index 00000000000..0a28a6768e6
--- /dev/null
+++ b/release/scripts/modules/i18n/bl_process_msg.py
@@ -0,0 +1,544 @@
+# ***** 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-80 compliant>
+
+# Write out messages.txt from Blender.
+# XXX: This script is meant to be used from inside Blender!
+# You should not directly use this script, rather use update_msg.py!
+
+import os
+
+# Quite an ugly hack… But the simplest solution for now!
+#import sys
+#sys.path.append(os.path.abspath(os.path.dirname(__file__)))
+import i18n.settings as settings
+
+
+#classes = set()
+
+
+SOURCE_DIR = settings.SOURCE_DIR
+
+CUSTOM_PY_UI_FILES = [os.path.abspath(os.path.join(SOURCE_DIR, p))
+ for p in settings.CUSTOM_PY_UI_FILES]
+FILE_NAME_MESSAGES = settings.FILE_NAME_MESSAGES
+COMMENT_PREFIX = settings.COMMENT_PREFIX
+CONTEXT_PREFIX = settings.CONTEXT_PREFIX
+CONTEXT_DEFAULT = settings.CONTEXT_DEFAULT
+UNDOC_OPS_STR = settings.UNDOC_OPS_STR
+
+NC_ALLOWED = settings.WARN_MSGID_NOT_CAPITALIZED_ALLOWED
+
+def check(check_ctxt, messages, key, msgsrc):
+ if check_ctxt is None:
+ return
+ multi_rnatip = check_ctxt.get("multi_rnatip")
+ multi_lines = check_ctxt.get("multi_lines")
+ py_in_rna = check_ctxt.get("py_in_rna")
+ not_capitalized = check_ctxt.get("not_capitalized")
+ end_point = check_ctxt.get("end_point")
+ undoc_ops = check_ctxt.get("undoc_ops")
+
+ if multi_rnatip is not None:
+ if key in messages and key not in multi_rnatip:
+ multi_rnatip.add(key)
+ if multi_lines is not None:
+ if '\n' in key[1]:
+ multi_lines.add(key)
+ if py_in_rna is not None:
+ if key in py_in_rna[1]:
+ py_in_rna[0].add(key)
+ if not_capitalized is not None:
+ if(key[1] not in NC_ALLOWED and key[1][0].isalpha() and
+ not key[1][0].isupper()):
+ not_capitalized.add(key)
+ if end_point is not None:
+ if key[1].strip().endswith('.'):
+ end_point.add(key)
+ if undoc_ops is not None:
+ if key[1] == UNDOC_OPS_STR:
+ undoc_ops.add(key)
+
+
+def dump_messages_rna(messages, check_ctxt):
+ import bpy
+
+ def classBlackList():
+ blacklist_rna_class = [# core classes
+ "Context", "Event", "Function", "UILayout",
+ "BlendData",
+ # registerable classes
+ "Panel", "Menu", "Header", "RenderEngine",
+ "Operator", "OperatorMacro", "Macro",
+ "KeyingSetInfo", "UnknownType",
+ # window classes
+ "Window",
+ ]
+
+ # ---------------------------------------------------------------------
+ # Collect internal operators
+
+ # extend with all internal operators
+ # note that this uses internal api introspection functions
+ # all possible operator names
+ op_ids = set(cls.bl_rna.identifier for cls in
+ bpy.types.OperatorProperties.__subclasses__()) | \
+ set(cls.bl_rna.identifier for cls in
+ bpy.types.Operator.__subclasses__()) | \
+ set(cls.bl_rna.identifier for cls in
+ bpy.types.OperatorMacro.__subclasses__())
+
+ get_instance = __import__("_bpy").ops.get_instance
+ path_resolve = type(bpy.context).__base__.path_resolve
+ for idname in op_ids:
+ op = get_instance(idname)
+ if 'INTERNAL' in path_resolve(op, "bl_options"):
+ blacklist_rna_class.append(idname)
+
+ # ---------------------------------------------------------------------
+ # Collect builtin classes we don't need to doc
+ blacklist_rna_class.append("Property")
+ blacklist_rna_class.extend(
+ [cls.__name__ for cls in
+ bpy.types.Property.__subclasses__()])
+
+ # ---------------------------------------------------------------------
+ # Collect classes which are attached to collections, these are api
+ # access only.
+ collection_props = set()
+ for cls_id in dir(bpy.types):
+ cls = getattr(bpy.types, cls_id)
+ for prop in cls.bl_rna.properties:
+ if prop.type == 'COLLECTION':
+ prop_cls = prop.srna
+ if prop_cls is not None:
+ collection_props.add(prop_cls.identifier)
+ blacklist_rna_class.extend(sorted(collection_props))
+
+ return blacklist_rna_class
+
+ blacklist_rna_class = classBlackList()
+
+ def filterRNA(bl_rna):
+ rid = bl_rna.identifier
+ if rid in blacklist_rna_class:
+ print(" skipping", rid)
+ return True
+ return False
+
+ check_ctxt_rna = check_ctxt_rna_tip = None
+ if check_ctxt:
+ check_ctxt_rna = {"multi_lines": check_ctxt.get("multi_lines"),
+ "not_capitalized": check_ctxt.get("not_capitalized"),
+ "end_point": check_ctxt.get("end_point"),
+ "undoc_ops": check_ctxt.get("undoc_ops")}
+ check_ctxt_rna_tip = check_ctxt_rna
+ check_ctxt_rna_tip["multi_rnatip"] = check_ctxt.get("multi_rnatip")
+
+ # -------------------------------------------------------------------------
+ # Function definitions
+
+ def walkProperties(bl_rna):
+ import bpy
+
+ # Get our parents' properties, to not export them multiple times.
+ bl_rna_base = bl_rna.base
+ if bl_rna_base:
+ bl_rna_base_props = bl_rna_base.properties.values()
+ else:
+ bl_rna_base_props = ()
+
+ for prop in bl_rna.properties:
+ # Only write this property if our parent hasn't got it.
+ if prop in bl_rna_base_props:
+ continue
+ if prop.identifier == "rna_type":
+ continue
+
+ msgsrc = "bpy.types.{}.{}".format(bl_rna.identifier, prop.identifier)
+ context = getattr(prop, "translation_context", CONTEXT_DEFAULT)
+ if prop.name and (prop.name != prop.identifier or context):
+ key = (context, prop.name)
+ check(check_ctxt_rna, messages, key, msgsrc)
+ messages.setdefault(key, []).append(msgsrc)
+ if prop.description:
+ key = (CONTEXT_DEFAULT, prop.description)
+ check(check_ctxt_rna_tip, messages, key, msgsrc)
+ messages.setdefault(key, []).append(msgsrc)
+ if isinstance(prop, bpy.types.EnumProperty):
+ for item in prop.enum_items:
+ msgsrc = "bpy.types.{}.{}:'{}'".format(bl_rna.identifier,
+ prop.identifier,
+ item.identifier)
+ if item.name and item.name != item.identifier:
+ key = (CONTEXT_DEFAULT, item.name)
+ check(check_ctxt_rna, messages, key, msgsrc)
+ messages.setdefault(key, []).append(msgsrc)
+ if item.description:
+ key = (CONTEXT_DEFAULT, item.description)
+ check(check_ctxt_rna_tip, messages, key, msgsrc)
+ messages.setdefault(key, []).append(msgsrc)
+
+ def walkRNA(bl_rna):
+ if filterRNA(bl_rna):
+ return
+
+ msgsrc = ".".join(("bpy.types", bl_rna.identifier))
+ context = getattr(bl_rna, "translation_context", CONTEXT_DEFAULT)
+
+ if bl_rna.name and (bl_rna.name != bl_rna.identifier or context):
+ key = (context, bl_rna.name)
+ check(check_ctxt_rna, messages, key, msgsrc)
+ messages.setdefault(key, []).append(msgsrc)
+
+ if bl_rna.description:
+ key = (CONTEXT_DEFAULT, bl_rna.description)
+ check(check_ctxt_rna_tip, messages, key, msgsrc)
+ messages.setdefault(key, []).append(msgsrc)
+
+ if hasattr(bl_rna, 'bl_label') and bl_rna.bl_label:
+ key = (context, bl_rna.bl_label)
+ check(check_ctxt_rna, messages, key, msgsrc)
+ messages.setdefault(key, []).append(msgsrc)
+
+ walkProperties(bl_rna)
+
+ def walkClass(cls):
+ walkRNA(cls.bl_rna)
+
+ def walk_keymap_hierarchy(hier, msgsrc_prev):
+ for lvl in hier:
+ msgsrc = "{}.{}".format(msgsrc_prev, lvl[1])
+ messages.setdefault((CONTEXT_DEFAULT, lvl[0]), []).append(msgsrc)
+
+ if lvl[3]:
+ walk_keymap_hierarchy(lvl[3], msgsrc)
+
+ # -------------------------------------------------------------------------
+ # Dump Messages
+
+ def process_cls_list(cls_list):
+ if not cls_list:
+ return 0
+
+ def full_class_id(cls):
+ """ gives us 'ID.Lamp.AreaLamp' which is best for sorting.
+ """
+ cls_id = ""
+ bl_rna = cls.bl_rna
+ while bl_rna:
+ cls_id = "{}.{}".format(bl_rna.identifier, cls_id)
+ bl_rna = bl_rna.base
+ return cls_id
+
+ cls_list.sort(key=full_class_id)
+ processed = 0
+ for cls in cls_list:
+ walkClass(cls)
+# classes.add(cls)
+ # Recursively process subclasses.
+ processed += process_cls_list(cls.__subclasses__()) + 1
+ return processed
+
+ # Parse everything (recursively parsing from bpy_struct "class"...).
+ processed = process_cls_list(type(bpy.context).__base__.__subclasses__())
+ print("{} classes processed!".format(processed))
+# import pickle
+# global classes
+# classes = {str(c) for c in classes}
+# with open("/home/i7deb64/Bureau/tpck_2", "wb") as f:
+# pickle.dump(classes, f, protocol=0)
+
+ from bpy_extras.keyconfig_utils import KM_HIERARCHY
+
+ walk_keymap_hierarchy(KM_HIERARCHY, "KM_HIERARCHY")
+
+
+
+def dump_messages_pytext(messages, check_ctxt):
+ """ dumps text inlined in the python user interface: eg.
+
+ layout.prop("someprop", text="My Name")
+ """
+ import ast
+
+ # -------------------------------------------------------------------------
+ # Gather function names
+
+ import bpy
+ # key: func_id
+ # val: [(arg_kw, arg_pos), (arg_kw, arg_pos), ...]
+ func_translate_args = {}
+
+ # so far only 'text' keywords, but we may want others translated later
+ translate_kw = ("text", )
+
+ # Break recursive nodes look up on some kind of nodes.
+ # E.g. we don’t want to get strings inside subscripts (blah["foo"])!
+ stopper_nodes = {ast.Subscript,}
+
+ for func_id, func in bpy.types.UILayout.bl_rna.functions.items():
+ # check it has a 'text' argument
+ for (arg_pos, (arg_kw, arg)) in enumerate(func.parameters.items()):
+ if ((arg_kw in translate_kw) and
+ (arg.is_output == False) and
+ (arg.type == 'STRING')):
+
+ func_translate_args.setdefault(func_id, []).append((arg_kw,
+ arg_pos))
+ # print(func_translate_args)
+
+ check_ctxt_py = None
+ if check_ctxt:
+ check_ctxt_py = {"py_in_rna": (check_ctxt["py_in_rna"], messages.copy()),
+ "multi_lines": check_ctxt["multi_lines"],
+ "not_capitalized": check_ctxt["not_capitalized"],
+ "end_point": check_ctxt["end_point"]}
+
+ # -------------------------------------------------------------------------
+ # Function definitions
+
+ def extract_strings(fp_rel, node):
+ """ Recursively get strings, needed in case we have "Blah" + "Blah",
+ passed as an argument in that case it wont evaluate to a string.
+ However, break on some kind of stopper nodes, like e.g. Subscript.
+ """
+
+ if type(node) == ast.Str:
+ eval_str = ast.literal_eval(node)
+ if eval_str:
+ key = (CONTEXT_DEFAULT, eval_str)
+ msgsrc = "{}:{}".format(fp_rel, node.lineno)
+ check(check_ctxt_py, messages, key, msgsrc)
+ messages.setdefault(key, []).append(msgsrc)
+ return
+
+ for nd in ast.iter_child_nodes(node):
+ if type(nd) not in stopper_nodes:
+ extract_strings(fp_rel, nd)
+
+ def extract_strings_from_file(fp):
+ filedata = open(fp, 'r', encoding="utf8")
+ root_node = ast.parse(filedata.read(), fp, 'exec')
+ filedata.close()
+
+ fp_rel = os.path.relpath(fp, SOURCE_DIR)
+
+ for node in ast.walk(root_node):
+ if type(node) == ast.Call:
+ # print("found function at")
+ # print("%s:%d" % (fp, node.lineno))
+
+ # lambda's
+ if type(node.func) == ast.Name:
+ continue
+
+ # getattr(self, con.type)(context, box, con)
+ if not hasattr(node.func, "attr"):
+ continue
+
+ translate_args = func_translate_args.get(node.func.attr, ())
+
+ # do nothing if not found
+ for arg_kw, arg_pos in translate_args:
+ if arg_pos < len(node.args):
+ extract_strings(fp_rel, node.args[arg_pos])
+ else:
+ for kw in node.keywords:
+ if kw.arg == arg_kw:
+ extract_strings(fp_rel, kw.value)
+
+ # -------------------------------------------------------------------------
+ # Dump Messages
+
+ mod_dir = os.path.join(SOURCE_DIR,
+ "release",
+ "scripts",
+ "startup",
+ "bl_ui")
+
+ files = [os.path.join(mod_dir, fn)
+ for fn in sorted(os.listdir(mod_dir))
+ if not fn.startswith("_")
+ if fn.endswith("py")
+ ]
+
+ # Dummy Cycles has its py addon in its own dir!
+ files += CUSTOM_PY_UI_FILES
+
+ for fp in files:
+ extract_strings_from_file(fp)
+
+
+def dump_messages(do_messages, do_checks):
+ import collections
+
+ def enable_addons():
+ """For now, enable all official addons, before extracting msgids."""
+ import addon_utils
+ import bpy
+
+ userpref = bpy.context.user_preferences
+ used_ext = {ext.module for ext in userpref.addons}
+ support = {"OFFICIAL"}
+ # collect the categories that can be filtered on
+ addons = [(mod, addon_utils.module_bl_info(mod)) for mod in
+ addon_utils.modules(addon_utils.addons_fake_modules)]
+
+ for mod, info in addons:
+ module_name = mod.__name__
+ if module_name in used_ext or info["support"] not in support:
+ continue
+ print(" Enabling module ", module_name)
+ bpy.ops.wm.addon_enable(module=module_name)
+
+ # XXX There are currently some problems with bpy/rna...
+ # *Very* tricky to solve!
+ # So this is a hack to make all newly added operator visible by
+ # bpy.types.OperatorProperties.__subclasses__()
+ for cat in dir(bpy.ops):
+ cat = getattr(bpy.ops, cat)
+ for op in dir(cat):
+ getattr(cat, op).get_rna()
+
+ # check for strings like ": %d"
+ ignore = ("%d", "%f", "%s", "%r", # string formatting
+ "*", ".", "(", ")", "-", "/", "\\", "+", ":", "#", "%"
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+ "x", # used on its own eg: 100x200
+ "X", "Y", "Z", "W", # used alone. no need to include
+ )
+
+ def filter_message(msg):
+ msg_tmp = msg
+ for ign in ignore:
+ msg_tmp = msg_tmp.replace(ign, "")
+ if not msg_tmp.strip():
+ return True
+ # we could filter out different strings here
+ return False
+
+ if hasattr(collections, 'OrderedDict'):
+ messages = collections.OrderedDict()
+ else:
+ messages = {}
+
+ messages[(CONTEXT_DEFAULT, "")] = []
+
+ # Enable all wanted addons.
+ enable_addons()
+
+ check_ctxt = None
+ if do_checks:
+ check_ctxt = {"multi_rnatip": set(),
+ "multi_lines": set(),
+ "py_in_rna": set(),
+ "not_capitalized": set(),
+ "end_point": set(),
+ "undoc_ops": set()}
+
+ # get strings from RNA
+ dump_messages_rna(messages, check_ctxt)
+
+ # get strings from UI layout definitions text="..." args
+ dump_messages_pytext(messages, check_ctxt)
+
+ del messages[(CONTEXT_DEFAULT, "")]
+
+ if do_checks:
+ print("WARNINGS:")
+ keys = set()
+ for c in check_ctxt.values():
+ keys |= c
+ for key in keys:
+ if key in check_ctxt["undoc_ops"]:
+ print("\tThe following operators are undocumented:")
+ else:
+ print("\t“{}”|“{}”:".format(*key))
+ if key in check_ctxt["multi_lines"]:
+ print("\t\t-> newline in this message!")
+ if key in check_ctxt["not_capitalized"]:
+ print("\t\t-> message not capitalized!")
+ if key in check_ctxt["end_point"]:
+ print("\t\t-> message with endpoint!")
+ # XXX Hide this one for now, too much false positives.
+# if key in check_ctxt["multi_rnatip"]:
+# print("\t\t-> tip used in several RNA items")
+ if key in check_ctxt["py_in_rna"]:
+ print("\t\t-> RNA message also used in py UI code:")
+ print("\t\t{}".format("\n\t\t".join(messages[key])))
+
+ if do_messages:
+ print("Writing messages…")
+ num_written = 0
+ num_filtered = 0
+ with open(FILE_NAME_MESSAGES, 'w', encoding="utf8") as message_file:
+ for (ctx, key), value in messages.items():
+ # filter out junk values
+ if filter_message(key):
+ num_filtered += 1
+ continue
+
+ # Remove newlines in key and values!
+ message_file.write("\n".join(COMMENT_PREFIX + msgsrc.replace("\n", "") for msgsrc in value))
+ message_file.write("\n")
+ if ctx:
+ message_file.write(CONTEXT_PREFIX + ctx.replace("\n", "") + "\n")
+ message_file.write(key.replace("\n", "") + "\n")
+ num_written += 1
+
+ print("Written {} messages to: {} ({} were filtered out)." \
+ "".format(num_written, FILE_NAME_MESSAGES, num_filtered))
+
+
+def main():
+ try:
+ import bpy
+ except ImportError:
+ print("This script must run from inside blender")
+ return
+
+ import sys
+ back_argv = sys.argv
+ sys.argv = sys.argv[sys.argv.index("--") + 1:]
+
+ import argparse
+ parser = argparse.ArgumentParser(description="Process UI messages " \
+ "from inside Blender.")
+ parser.add_argument('-c', '--no_checks', default=True,
+ action="store_false",
+ help="No checks over UI messages.")
+ parser.add_argument('-m', '--no_messages', default=True,
+ action="store_false",
+ help="No export of UI messages.")
+ parser.add_argument('-o', '--output', help="Output messages file path.")
+ args = parser.parse_args()
+
+ if args.output:
+ global FILE_NAME_MESSAGES
+ FILE_NAME_MESSAGES = args.output
+
+ dump_messages(do_messages=args.no_messages, do_checks=args.no_checks)
+
+ sys.argv = back_argv
+
+
+if __name__ == "__main__":
+ print("\n\n *** Running {} *** \n".format(__file__))
+ main()