From 53eeb27d3544627740f8557f467bcd308a4b861e Mon Sep 17 00:00:00 2001 From: TsXor Date: Sat, 10 Sep 2022 21:41:20 +0800 Subject: [PATCH] fix(action_manager/utils): 1. Requires initialization of app when needed instead of do it on import to pass import test. 2. Warns user when typeID collision happens. (And id2str will try to avoid giving user string that will cause collision.) typeID collision happens when a stringID and a charID share the same name. For example, charID 'From' -> stringID 'from' and charID 'from' -> stringID 'originalAddressAttr', so 'from' is a string that will cause collision. After scanning the cpp header "PITerminology.h" and "PIStringTerminology.h", I found 3 such strings and hardcoded them into utils.py. str2id() will not automatically correct user when user gives a collision string (because no one knows what he want it to be a stringID or an charID!), but will regard that string as stringID and throw a warning. Users can follow that warning's guide to fix error and suppress that warning. --- .../action_manager/js_converter/convert.py | 4 +- photoshop/api/action_manager/utils.py | 70 +++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/photoshop/api/action_manager/js_converter/convert.py b/photoshop/api/action_manager/js_converter/convert.py index e0b56958..429abbdf 100644 --- a/photoshop/api/action_manager/js_converter/convert.py +++ b/photoshop/api/action_manager/js_converter/convert.py @@ -85,8 +85,8 @@ def parseref(tdict): plist = ["!ref"] # py37 compat try: - exec( - """ext = [(str2refgetpacker[val["type"]](e) """ + ext = eval( + """[(str2refgetpacker[val["type"]](e) """ + """if type(val := e["Value"]) == dict """ + """else str2refgetpacker["default"](e)) for e in d2l]""" ) diff --git a/photoshop/api/action_manager/utils.py b/photoshop/api/action_manager/utils.py index dd4e8eee..faaf1676 100644 --- a/photoshop/api/action_manager/utils.py +++ b/photoshop/api/action_manager/utils.py @@ -1,5 +1,9 @@ """TypeID conversion utilities of this submodule.""" +# Import built-in modules +import warnings +from functools import wraps + # Import local modules from photoshop.api._core import Photoshop @@ -7,8 +11,9 @@ __all__ = ["str2id", "id2str"] -class app(Photoshop): - """Partially reimplement the Application class in this file to avoid circular import.""" +class AppAgent(Photoshop): + """Partially reimplement the Application class + in this file to avoid circular import.""" typename = "Application" @@ -19,7 +24,21 @@ def id2str(self, number: int) -> str: return self.app.typeIDToStringID(number) -converter = app() +converter = None + + +def requireapp(func): + """A simple decorator that initializes the global + app instance when the decorated function is called.""" + + @wraps(func) + def wrapped(*args, **kwargs): + global converter + if converter is None: + converter = AppAgent() + return func(*args, **kwargs) + + return wrapped def str2hash(x: str) -> int: @@ -35,10 +54,47 @@ def hash2str(x: int) -> str: return x.to_bytes(length=4, byteorder="big").decode() +# Current approach of str2id is not perfect, it will face some "collisions". +# This means, if there exists a charID which is the same as another stringID, +# it will cause a "collision". +# For example charID 'From' -> stringID 'from' +# and charID 'from' -> stringID 'originalAddressAttr' +# We can know when this will happen by prescanning the cpp header +# "PITerminology.h" and "PIStringTerminology.h". +# Prescanned collisions are stored here. If you suffer from collisions that +# str2id cannot handle, please open an issue. + +collisions = { + "chr": ("Cpy ", "From", "Type"), + "str": ("copyEvent", "originalAddressAttr", "class"), + "collision": ("copy", "from", "type"), +} + +COLLISION_WARNING = """ +You are using a string that is a collision of stringID and charID. +On this situation, str2id will consider this string as stringID. If you encounter COMerror +on app.executeAction() later, you can try to replace this string(%s) with its corresponding +charID(%s). If you are sure you want to use it as stringID and suppress this warning, +replace this string(%s) with its corresponding stringID(%s). +If this warning doesn't solve you problem, please open an issue.""" + + +@requireapp def str2id(psstr: str) -> str: """Convert charID or stringID to typeID""" assert type(psstr) == str if len(psstr) == 4: + try: + search = collisions["collision"].index(psstr) + warnstr = COLLISION_WARNING % ( + repr(collisions["collision"][search]), + repr(collisions["chr"][search]), + repr(collisions["collision"][search]), + repr(collisions["str"][search]), + ) + warnings.warn(warnstr, category=RuntimeWarning) + except ValueError: + pass # I know what I am doing. typeid = str2hash(psstr) try: restr = converter.id2str(typeid) @@ -51,6 +107,12 @@ def str2id(psstr: str) -> str: return typeid +@requireapp def id2str(typeid: int) -> str: """Convert typeID to stringID""" - return converter.id2str(typeid) + result = converter.id2str(typeid) + try: + search = collisions["collision"].index(result) + return collisions["chr"][search] + except ValueError: + return result