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