Libraries upgrade:
[blender.git] / po / update_msg.py
index 7bb659ca93cc7889bfbecd0c48e916f8deb1f559..21d727922d40f3dabb0c283bce83235fc01290bf 100644 (file)
@@ -1,4 +1,3 @@
-# $Id$
 # ***** BEGIN GPL LICENSE BLOCK *****
 #
 # This program is free software; you can redistribute it and/or
@@ -17,7 +16,7 @@
 #
 # ***** END GPL LICENSE BLOCK *****
 
-# <pep8 compliant>
+# <pep8-80 compliant>
 
 # Write out messages.txt from blender
 
 
 import os
 
-CURRENT_DIR = os.path.dirname(__file__)
+CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
 SOURCE_DIR = os.path.normpath(os.path.abspath(os.path.join(CURRENT_DIR, "..")))
 
 FILE_NAME_MESSAGES = os.path.join(CURRENT_DIR, "messages.txt")
+COMMENT_PREFIX = "#~ "
 
 
 def dump_messages_rna(messages):
     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
+                               "WindowManager", "Window"
+                               ]
+
+        # ---------------------------------------------------------------------
+        # Collect internal operators
+
+        # extend with all internal operators
+        # note that this uses internal api introspection functions
+        # all possible operator names
+        op_names = list(sorted(set(
+            [cls.bl_rna.identifier for cls in
+             bpy.types.OperatorProperties.__subclasses__()] +
+            [cls.bl_rna.identifier for cls in
+             bpy.types.Operator.__subclasses__()] +
+            [cls.bl_rna.identifier for cls in
+             bpy.types.OperatorMacro.__subclasses__()]
+            )))
+
+        get_inatance = __import__("_bpy").ops.get_instance
+        path_resolve = type(bpy.context).__base__.path_resolve
+        for idname in op_names:
+            op = get_inatance(idname)
+            if 'INTERNAL' in path_resolve(op, "bl_options"):
+                blacklist_rna_class.append(idname)
+
+        # ---------------------------------------------------------------------
+        # Collect builtin classes we dont 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):
+        id = bl_rna.identifier
+        if id in blacklist_rna_class:
+            print("  skipping", id)
+            return True
+        return False
+
     # -------------------------------------------------------------------------
     # Function definitions
 
-    def walkProperties(properties):
+    def walkProperties(bl_rna):
         import bpy
-        for prop in properties:
-            messages.add(prop.name)
-            messages.add(prop.description)
+
+        # get our parents properties not to 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 is our parent hasn't got it.
+            if prop in bl_rna_base_props:
+                continue
+            if prop.identifier == "rna_type":
+                continue
+
+            msgsrc = "bpy.types.%s.%s" % (bl_rna.identifier, prop.identifier)
+            if prop.name and prop.name != prop.identifier:
+                messages.setdefault(prop.name, []).append(msgsrc)
+            if prop.description:
+                messages.setdefault(prop.description, []).append(msgsrc)
 
             if isinstance(prop, bpy.types.EnumProperty):
                 for item in prop.enum_items:
-                    messages.add(item.name)
-                    messages.add(item.description)
+                    msgsrc = "bpy.types.%s.%s, '%s'" % (bl_rna.identifier,
+                                                        prop.identifier,
+                                                        item.identifier,
+                                                        )
+                    # Here identifier and name can be the same!
+                    if item.name:  # and item.name != item.identifier:
+                        messages.setdefault(item.name,
+                                            []).append(msgsrc)
+                    if item.description:
+                        messages.setdefault(item.description,
+                                            []).append(msgsrc)
 
     def walkRNA(bl_rna):
+
+        if filterRNA(bl_rna):
+            return
+
+        msgsrc = "bpy.types.%s" % bl_rna.identifier
+
         if bl_rna.name and bl_rna.name != bl_rna.identifier:
-            messages.add(bl_rna.name)
+            messages.setdefault(bl_rna.name, []).append(msgsrc)
 
         if bl_rna.description:
-            messages.add(bl_rna.description)
+            messages.setdefault(bl_rna.description, []).append(msgsrc)
+
+        if hasattr(bl_rna, 'bl_label') and  bl_rna.bl_label:
+            messages.setdefault(bl_rna.bl_label, []).append(msgsrc)
 
-        walkProperties(bl_rna.properties)
+        walkProperties(bl_rna)
 
     def walkClass(cls):
         walkRNA(cls.bl_rna)
 
-    def walk_keymap_hierarchy(hier):
+    def walk_keymap_hierarchy(hier, msgsrc_prev):
         for lvl in hier:
-            messages.add(lvl[0])
+            msgsrc = "%s.%s" % (msgsrc_prev, lvl[1])
+            messages.setdefault(lvl[0], []).append(msgsrc)
 
             if lvl[3]:
-                walk_keymap_hierarchy(lvl[3])
+                walk_keymap_hierarchy(lvl[3], msgsrc)
 
     # -------------------------------------------------------------------------
     # Dump Messages
 
-    for cls in type(bpy.context).__base__.__subclasses__():
+    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 = "%s.%s" % (bl_rna.identifier, cls_id)
+            bl_rna = bl_rna.base
+        return cls_id
+
+    cls_list = type(bpy.context).__base__.__subclasses__()
+    cls_list.sort(key=full_class_id)
+    for cls in cls_list:
         walkClass(cls)
 
-    for cls in bpy.types.Space.__subclasses__():
+    cls_list = bpy.types.Space.__subclasses__()
+    cls_list.sort(key=full_class_id)
+    for cls in cls_list:
         walkClass(cls)
 
-    for cls in bpy.types.Operator.__subclasses__():
+    cls_list = bpy.types.Operator.__subclasses__()
+    cls_list.sort(key=full_class_id)
+    for cls in cls_list:
         walkClass(cls)
 
-    from bl_ui.space_userpref_keymap import KM_HIERARCHY
+    cls_list = bpy.types.OperatorProperties.__subclasses__()
+    cls_list.sort(key=full_class_id)
+    for cls in cls_list:
+        walkClass(cls)
 
-    walk_keymap_hierarchy(KM_HIERARCHY)
+    cls_list = bpy.types.Menu.__subclasses__()
+    cls_list.sort(key=full_class_id)
+    for cls in cls_list:
+        walkClass(cls)
 
+    from bpy_extras.keyconfig_utils import KM_HIERARCHY
 
-    ## XXX. what is this supposed to do, we wrote the file already???
-    #_walkClass(bpy.types.SpaceDopeSheetEditor)
+    walk_keymap_hierarchy(KM_HIERARCHY, "KM_HIERARCHY")
 
 
 def dump_messages_pytext(messages):
@@ -121,26 +245,30 @@ def dump_messages_pytext(messages):
     # -------------------------------------------------------------------------
     # Function definitions
 
-    def extract_strings(fp, node_container):
+    def extract_strings(fp_rel, node_container):
         """ Recursively get strings, needed incase we have "Blah" + "Blah",
             passed as an argument in that case it wont evaluate to a string.
         """
+
         for node in ast.walk(node_container):
             if type(node) == ast.Str:
                 eval_str = ast.literal_eval(node)
                 if eval_str:
-                    # print("%s:%d: %s" % (fp, node.lineno, eval_str))  # testing
-                    messages.add(eval_str)
+                    # print("%s:%d: %s" % (fp, node.lineno, eval_str))
+                    msgsrc = "%s:%s" % (fp_rel, node.lineno)
+                    messages.setdefault(eval_str, []).append(msgsrc)
 
-    def extract_strings_from_file(fn):
-        filedata = open(fn, 'r', encoding="utf8")
-        root_node = ast.parse(filedata.read(), fn, 'exec')
+    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" % (fn, node.lineno))
+                # print("%s:%d" % (fp, node.lineno))
 
                 # lambda's
                 if type(node.func) == ast.Name:
@@ -155,29 +283,60 @@ def dump_messages_pytext(messages):
                 # do nothing if not found
                 for arg_kw, arg_pos in translate_args:
                     if arg_pos < len(node.args):
-                        extract_strings(fn, node.args[arg_pos])
+                        extract_strings(fp_rel, node.args[arg_pos])
                     else:
                         for kw in node.keywords:
                             if kw.arg == arg_kw:
-                                extract_strings(fn, kw.value)
+                                extract_strings(fp_rel, kw.value)
 
     # -------------------------------------------------------------------------
     # Dump Messages
 
-    mod_dir = os.path.join(SOURCE_DIR, "release", "scripts", "startup", "bl_ui")
+    mod_dir = os.path.join(SOURCE_DIR,
+                           "release",
+                           "scripts",
+                           "startup",
+                           "bl_ui")
 
-    files = [os.path.join(mod_dir, f)
-             for f in os.listdir(mod_dir)
-             if not f.startswith("_")
-             if f.endswith("py")
+    files = [os.path.join(mod_dir, fn)
+             for fn in sorted(os.listdir(mod_dir))
+             if not fn.startswith("_")
+             if fn.endswith("py")
              ]
 
-    for fn in files:
-        extract_strings_from_file(fn)
+    for fp in files:
+        extract_strings_from_file(fp)
 
 
 def dump_messages():
-    messages = {""}
+
+    def filter_message(msg):
+
+        # check for strings like ": %d"
+        msg_test = msg
+        for ignore in ("%d", "%s", "%r",  # string formatting
+                       "*", ".", "(", ")", "-", "/", "\\", "+", ":", "#", "%"
+                       "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+                       "x",  # used on its own eg: 100x200
+                       "X", "Y", "Z",  # used alone. no need to include
+                       ):
+            msg_test = msg_test.replace(ignore, "")
+        msg_test = msg_test.strip()
+        if not msg_test:
+            # print("Skipping: '%s'" % msg)
+            return True
+
+        # we could filter out different strings here
+
+        return False
+
+    if 1:
+        import collections
+        messages = collections.OrderedDict()
+    else:
+        messages = {}
+
+    messages[""] = []
 
     # get strings from RNA
     dump_messages_rna(messages)
@@ -185,10 +344,21 @@ def dump_messages():
     # get strings from UI layout definitions text="..." args
     dump_messages_pytext(messages)
 
-    messages.remove("")
+    del messages[""]
 
     message_file = open(FILE_NAME_MESSAGES, 'w', encoding="utf8")
-    message_file.writelines("\n".join(sorted(messages)))
+    # message_file.writelines("\n".join(sorted(messages)))
+
+    for key, value in messages.items():
+
+        # filter out junk values
+        if filter_message(key):
+            continue
+
+        for msgsrc in value:
+            message_file.write("%s%s\n" % (COMMENT_PREFIX, msgsrc))
+        message_file.write("%s\n" % key)
+
     message_file.close()
 
     print("Written %d messages to: %r" % (len(messages), FILE_NAME_MESSAGES))