diff --git a/src/autoscript/ASEngine.py b/src/autoscript/ASEngine.py index e8623a4..7c5916b 100644 --- a/src/autoscript/ASEngine.py +++ b/src/autoscript/ASEngine.py @@ -14,6 +14,16 @@ from datetime import ( from lupa import LuaRuntime as _LuaRuntime +from autoscript._helpers import ( + _TYPE_DEFAULT_VAR, + _assignPath, + _checkDateFormat, + _checkTimeFormat, + _checkType, + _cleanLuaError, + _navigatePath, +) + try: from lupa.lua55 import LuaError as _LuaError, LuaSyntaxError as _LuaSyntaxError except ImportError: @@ -24,435 +34,229 @@ except ImportError: _LuaSyntaxError = Exception -__all__ = ["execute", "addTargetVar", "resetEngine"] +__all__ = ["ASEngine"] -# Engine state -_TARGET_VARS: dict[str, dict] = {} -_lua = None +class ASEngine: -# Built-in meta variable definitions (name / type / display-name) -META_VARS: dict[str, dict[str, str]] = { - "CURRENT_DATE": {"name": "CURRENT_DATE", "type": "Date", "display": "当前日期"}, - "CURRENT_TIME": {"name": "CURRENT_TIME", "type": "Time", "display": "当前时间"}, -} + @staticmethod + def getCurrentDate( + ) -> str: -# Per-type fallback value when target_data entry is missing. -_DEFAULT_BY_TYPE: dict[str, str | int | float | bool] = { - "String": "", - "Int": 0, - "Float": 0.0, - "Boolean": False, -} + return date.today().isoformat() -def _getLua( -): - """ - Return the sandboxed Lua runtime singleton. - """ + @staticmethod + def getCurrentTime( + ) -> str: - global _lua - if _lua is None: - _lua = _LuaRuntime(unpack_returned_tuples = True) - _sandbox(_lua) - _registerHelpers(_lua) - return _lua + return datetime.now().strftime("%H:%M") -def _sandbox( - lua, -) -> None: - """ - Remove dangerous Lua globals while keeping os.date / os.time for date-time helpers. - """ + @staticmethod + def _sandbox( + lua, + ) -> None: - lua.execute(""" - io = nil - require = nil - dofile = nil - loadfile = nil - load = nil - package = nil - rawget = nil - rawset = nil - rawequal = nil - getfenv = nil - setfenv = nil - debug = nil - -- selectively disable dangerous os functions, keep date / time - if os then - os.execute = nil - os.exit = nil - os.getenv = nil - os.remove = nil - os.rename = nil - os.tmpname = nil - os.setlocale = nil - end - """) + lua.execute(""" + io = nil + require = nil + dofile = nil + loadfile = nil + load = nil + package = nil + rawget = nil + rawset = nil + rawequal = nil + getfenv = nil + setfenv = nil + debug = nil + if os then + os.execute = nil + os.exit = nil + os.getenv = nil + os.remove = nil + os.rename = nil + os.tmpname = nil + os.setlocale = nil + end + """) -def _registerHelpers( - lua, -) -> None: - """ - Inject Date / Time helpers as pure Lua functions. + @staticmethod + def _registerHelpers( + lua, + ) -> None: - Date values are os.time timestamps (seconds since epoch). - Time values are minutes since midnight (0-1439). + lua.execute(""" + function date(y, m, d) + return os.time({year = y, month = m, day = d}) + end - This keeps Date / Time as native Lua numbers during script execution, - enabling type-safe arithmetic (+, -) and comparisons (<, <=, ==, ~=). - """ + function time(h, m) + return h * 60 + m + end - lua.execute(""" - function date(y, m, d) - return os.time({year = y, month = m, day = d}) - end + function datenow() + local now = os.date("*t") + return os.time({year = now.year, month = now.month, day = now.day}) + end - function time(h, m) - return h * 60 + m - end + function timenow() + local now = os.date("*t") + return now.hour * 60 + now.min + end - function CURRENT_DATE() - local now = os.date("*t") - return os.time({year = now.year, month = now.month, day = now.day}) - end + function dateadd(date_val, n) + return date_val + n * 86400 + end - function CURRENT_TIME() - local now = os.date("*t") - return now.hour * 60 + now.min - end + function timeadd(time_val, n) + return (time_val + n * 60) % 1440 + end - function date_add(date_val, n) - return date_val + n * 86400 - end + function strtodate(iso_str) + local y, m, d = iso_str:match("(%d+)-(%d+)-(%d+)") + return os.time({year = y, month = m, day = d}) + end - function time_add(time_val, n) - return (time_val + n * 60) % 1440 - end + function strtotime(hm_str) + local h, m = hm_str:match("(%d+):(%d+)") + return h * 60 + m + end - -- push helpers: string -> native type - function _to_date(iso_str) - local y, m, d = iso_str:match("(%d+)-(%d+)-(%d+)") - return os.time({year = y, month = m, day = d}) - end + function datetostr(ts) + return os.date("%Y-%m-%d", ts) + end - function _to_time(hm_str) - local h, m = hm_str:match("(%d+):(%d+)") - return h * 60 + m - end + function timetostr(m) + return string.format("%02d:%02d", math.floor(m / 60), m % 60) + end + """) - -- pull helpers: native type -> string - function _from_date(ts) - return os.date("%Y-%m-%d", ts) - end + def __init__( + self, + targetVars: list[tuple] = None, + ): - function _from_time(m) - return string.format("%02d:%02d", math.floor(m / 60), m % 60) - end - """) + self._targetVars: dict[str, dict] = {} + self._lua = None -def _navigatePath( - data: dict, - key_path: list, - default = None, -): - """ - Walk *key_path* into *data* and return the value at the leaf. - """ + if targetVars: + for item in targetVars: + name, varType, keyPath = item[0], item[1], item[2] + self.addTargetVar(name, varType, keyPath) - d = data - for key in key_path[:-1]: - d = d.get(key, {}) - if not isinstance(d, dict): - return default - return d.get(key_path[-1], default) + def _getLua( + self, + ): -def _assignPath( - data: dict, - key_path: list, - value, -) -> None: - """ - Walk *key_path* into *data* and set *value* at the leaf. - """ + if self._lua is None: + self._lua = _LuaRuntime(unpack_returned_tuples=True) + self._sandbox(self._lua) + self._registerHelpers(self._lua) + return self._lua - d = data - for key in key_path[:-1]: - d = d.setdefault(key, {}) - d[key_path[-1]] = value + def _push( + self, + targetData: dict, + ) -> None: -def _pyTypeToASType( - value -) -> str: - """ - Map a Python runtime value to its AutoScript type name. - """ + lua = self._getLua() + g = lua.globals() + strToDate = g["strtodate"] + strToTime = g["strtotime"] - if isinstance(value, bool): - return "Boolean" - if isinstance(value, int): - return "Int" - if isinstance(value, float): - return "Float" - if isinstance(value, str): - return "String" - return "Unknown" + for varName, info in self._targetVars.items(): + keyPath = info["keyPath"] + vt = info["type"] + raw = _navigatePath(targetData, keyPath) + if vt == "Date": + if not isinstance(raw, str) or not raw.strip(): + raise ValueError( + f"Date 类型变量 '{varName}' 对应的数据为空或不是字符串类型," + f"请检查路径 {keyPath} 的值是否为合法的日期字符串 (YYYY-MM-DD)" + ) + raw = raw.strip() + _checkDateFormat(raw, varName) + g[varName] = strToDate(raw) + elif vt == "Time": + if not isinstance(raw, str) or not raw.strip(): + raise ValueError( + f"Time 类型变量 '{varName}' 对应的数据为空或不是字符串类型," + f"请检查路径 {keyPath} 的值是否为合法的时间字符串 (HH:MM)" + ) + raw = raw.strip() + _checkTimeFormat(raw, varName) + g[varName] = strToTime(raw) + else: + if raw is None: + raw = _TYPE_DEFAULT_VAR.get(vt, False) + g[varName] = raw -def _checkDateFormat( - date_str: str, - var_name: str = "", -) -> None: - """ - Validate that *date_str* is in YYYY-MM-DD format. - Raises ValueError with a descriptive message on failure. - """ + def _pull( + self, + targetData: dict, + ) -> None: - prefix = f"Date 类型变量 '{var_name}' 的" if var_name else "" - try: - date.fromisoformat(date_str) - except ValueError: - raise ValueError( - f"{prefix}值 '{date_str}' 不是合法的日期格式," - f"应为 YYYY-MM-DD" - ) + lua = self._getLua() + g = lua.globals() + dateToStr = g["datetostr"] + timeToStr = g["timetostr"] -def _checkTimeFormat( - time_str: str, - var_name: str = "", -) -> None: - """ - Validate that *time_str* is in HH:MM format. - Raises ValueError with a descriptive message on failure. - """ + for varName, info in self._targetVars.items(): + try: + luaVal = g[varName] + except KeyError: + continue + vt = info["type"] + if vt == "Date": + luaVal = dateToStr(luaVal) + elif vt == "Time": + luaVal = timeToStr(luaVal) + elif vt == "Float" and isinstance(luaVal, int) and not isinstance(luaVal, bool): + luaVal = float(luaVal) + _checkType(varName, vt, luaVal) + _assignPath(targetData, info["keyPath"], luaVal) - prefix = f"Time 类型变量 '{var_name}' 的" if var_name else "" - try: - datetime.strptime(time_str, "%H:%M") - except ValueError: - raise ValueError( - f"{prefix}值 '{time_str}' 不是合法的时间格式," - f"应为 HH:MM" - ) + def addTargetVar( + self, + name: str, + varType: str, + keyPath: list, + ) -> None: -def _checkType( - var_name: str, - var_type: str, - value, -) -> None: - """ - Validate that *value* matches the declared variable type. + upperName = name.upper().strip() + self._targetVars[upperName] = { + "type": varType, + "keyPath": keyPath, + } - Date / Time values arrive as ISO / HH:MM strings (already converted - from Lua native types during the pull phase). - Int / Float / Boolean / String check Python type identity. - Int -> Float widening is allowed. - """ + def execute( + self, + scriptText: str, + targetData: dict, + ) -> None: - if var_type == "Date": - if not isinstance(value, str): - raise ValueError( - f"Date 类型变量 '{var_name}' 只能接受日期字符串," - f"不能接受 {_pyTypeToASType(value)} 类型" - ) - _checkDateFormat(value, var_name) - return - if var_type == "Time": - if not isinstance(value, str): - raise ValueError( - f"Time 类型变量 '{var_name}' 只能接受时间字符串," - f"不能接受 {_pyTypeToASType(value)} 类型" - ) - _checkTimeFormat(value, var_name) - return - if var_type == "Int": - if isinstance(value, bool): - raise ValueError( - f"Int 类型变量 '{var_name}' 不能接受 Boolean 类型的值" - ) - if not isinstance(value, int) and not (isinstance(value, float) and value == int(value)): - raise ValueError( - f"Int 类型变量 '{var_name}' 不能接受 {_pyTypeToASType(value)} 类型的值" - ) - return - if var_type == "Float": - if isinstance(value, bool): - raise ValueError( - f"Float 类型变量 '{var_name}' 不能接受 Boolean 类型的值" - ) - if not isinstance(value, (int, float)): - raise ValueError( - f"Float 类型变量 '{var_name}' 不能接受 {_pyTypeToASType(value)} 类型的值" - ) - return - if var_type == "Boolean": - if not isinstance(value, bool): - raise ValueError( - f"Boolean 类型变量 '{var_name}' 不能接受 {_pyTypeToASType(value)} 类型的值" - ) - return - if var_type == "String": - if not isinstance(value, str): - raise ValueError( - f"String 类型变量 '{var_name}' 不能接受 {_pyTypeToASType(value)} 类型的值" - ) - return - -def addTargetVar( - name: str, - var_type: str, - key_path: list, - _display_name: str = None, -) -> None: - """ - Register a new target variable bound to a path in the application data dict. - - Args: - name (str): The canonical variable name (e.g. "RESERVE_DATE"). - var_type (str): "Int" | "Float" | "Boolean" | "Date" | "Time" | "String". - key_path (list): Nested path into target_data, e.g. ["reserve_info", "date"]. - """ - - upper_name = name.upper().strip() - _TARGET_VARS[upper_name] = { - "type": var_type, - "key_path": key_path, - } - -def resetEngine( -) -> None: - """ - Reset the engine to its initial state: clear all target variables - and release the Lua runtime. - """ - - global _TARGET_VARS, _lua - _TARGET_VARS = {} - _lua = None - -def _push( - target_data: dict, -) -> None: - """ - Push target_data values into Lua globals. - Date / Time strings are converted to native Lua types (timestamp / minutes). - - Raises ValueError for missing / malformed Date or Time values so that - execute() can surface them as user-visible AutoScript execution errors. - """ - - lua = _getLua() - g = lua.globals() - _toDate = g["_to_date"] - _toTime = g["_to_time"] - - for var_name, info in _TARGET_VARS.items(): - key_path = info["key_path"] - vt = info["type"] - raw = _navigatePath(target_data, key_path) - if vt == "Date": - if not isinstance(raw, str) or not raw.strip(): - raise ValueError( - f"Date 类型变量 '{var_name}' 对应的数据为空或不是字符串类型," - f"请检查路径 {key_path} 的值是否为合法的日期字符串 (YYYY-MM-DD)" - ) - raw = raw.strip() - _checkDateFormat(raw, var_name) - g[var_name] = _toDate(raw) - elif vt == "Time": - if not isinstance(raw, str) or not raw.strip(): - raise ValueError( - f"Time 类型变量 '{var_name}' 对应的数据为空或不是字符串类型," - f"请检查路径 {key_path} 的值是否为合法的时间字符串 (HH:MM)" - ) - raw = raw.strip() - _checkTimeFormat(raw, var_name) - g[var_name] = _toTime(raw) - else: - if raw is None: - raw = _DEFAULT_BY_TYPE.get(vt, False) - g[var_name] = raw - -def _pull( - target_data: dict, -) -> None: - """ - Pull Lua global values back into target_data. - Date / Time native types are converted back to ISO / HH:MM strings. - """ - - lua = _getLua() - g = lua.globals() - _fromDate = g["_from_date"] - _fromTime = g["_from_time"] - - for var_name, info in _TARGET_VARS.items(): + if not scriptText or not scriptText.strip(): + return try: - lua_val = g[var_name] - except KeyError: - continue - vt = info["type"] - if vt == "Date": - lua_val = _fromDate(lua_val) - elif vt == "Time": - lua_val = _fromTime(lua_val) - elif vt == "Float" and isinstance(lua_val, int) and not isinstance(lua_val, bool): - lua_val = float(lua_val) - _checkType(var_name, vt, lua_val) - _assignPath(target_data, info["key_path"], lua_val) + self._push(targetData) + self._getLua().execute(scriptText) + self._pull(targetData) + except _LuaSyntaxError as e: + raise ValueError( + f"AutoScript 语法错误: {_cleanLuaError(str(e))}" + ) + except _LuaError as e: + raise ValueError( + f"AutoScript 运行时错误: {_cleanLuaError(str(e))}" + ) + except ValueError as e: + raise ValueError(f"AutoScript 数据错误: {e}") + except Exception as e: + raise ValueError(f"AutoScript 未知错误: {e}") -def _cleanLuaError( - raw_msg: str -) -> str: - """ - Strip internal source prefix and stack traceback from a Lua error message. - """ + def reset( + self, + ) -> None: - msg = raw_msg.replace('[string ""]:', "").strip() - stack_idx = msg.find("stack traceback:") - if stack_idx != -1: - msg = msg[:stack_idx].strip() - return msg - -def execute( - script_text: str, - target_data: dict, -) -> None: - """ - Execute an AutoScript (Lua) on the given target data. - - The script runs in a sandboxed Lua environment with target variables - exposed as globals. The following helpers are available as Lua functions: - - date(y, m, d) -> timestamp (os.time seconds) - time(h, m) -> minutes since midnight (0-1439) - CURRENT_DATE() -> today's timestamp - CURRENT_TIME() -> current minutes since midnight - date_add(ts, n) -> ts + n * 86400 - time_add(m, n) -> (m + n * 60) % 1440 - - Date and Time values are native Lua numbers during execution. - Arithmetic (+, -) and comparisons (<, <=, ==, ~=, >, >=) work - with strong type safety — no implicit string coercion. - - Raises: - ValueError: On Lua compilation/runtime errors or type mismatches. - """ - - if not script_text or not script_text.strip(): - return - try: - _push(target_data) - _getLua().execute(script_text) - _pull(target_data) - except _LuaSyntaxError as e: - raise ValueError( - f"AutoScript 语法错误: {_cleanLuaError(str(e))}" - ) - except _LuaError as e: - raise ValueError( - f"AutoScript 运行时错误: {_cleanLuaError(str(e))}" - ) - except ValueError as e: - raise ValueError(f"AutoScript 数据错误: {e}") - except Exception as e: - raise ValueError(f"AutoScript 未知错误: {e}") + self._targetVars = {} + self._lua = None diff --git a/src/autoscript/__init__.py b/src/autoscript/__init__.py index 2315f78..a3397b9 100644 --- a/src/autoscript/__init__.py +++ b/src/autoscript/__init__.py @@ -7,45 +7,25 @@ This software is provided "as is", without any warranty of any kind. You may use, modify, and distribute this file under the terms of the MIT License. See the LICENSE file for details. """ -from autoscript.ASEngine import ( - execute, - addTargetVar, - resetEngine, - META_VARS, -) +from autoscript.ASEngine import ASEngine __all__ = [ - "execute", - "addTargetVar", - "resetEngine", - "registerDefaultTargetVars", - "buildMockTargetData", - "META_VARS", - "ALL_VARIABLES", - "_TARGET_VAR_DEFS", - "_MOCK_TYPE_VALUES", + "ASEngine", + "createEngine", + "createMockTargetData", + "createAllVariablesTable", + "createTargetVarDefs", ] -# Key paths into target_data dict for each target variable. -# (name, type, key_path, display_name) _TARGET_VAR_DEFS = [ - ("USERNAME", "String", ["username"], "用户名"), - ("USER_ENABLE", "Boolean",["enabled"], "用户启用"), - ("RESERVE_DATE", "Date", ["reserve_info", "date"], "预约日期"), - ("RESERVE_BEGIN_TIME", "Time", ["reserve_info", "begin_time", "time"], "预约开始时间"), - ("RESERVE_END_TIME", "Time", ["reserve_info", "end_time", "time"], "预约结束时间"), + ("USERNAME", "String", ["username"], "用户名"), + ("USER_ENABLE", "Boolean", ["enabled"], "用户启用"), + ("RESERVE_DATE", "Date", ["reserve_info", "date"], "预约日期"), + ("RESERVE_BEGIN_TIME", "Time", ["reserve_info", "begin_time", "time"], "预约开始时间"), + ("RESERVE_END_TIME", "Time", ["reserve_info", "end_time", "time"], "预约结束时间"), ] - -# All variables (display_name -> (name, type)), derived from target vars + meta vars. -ALL_VARIABLES = { - display_name: (name, var_type) - for name, var_type, _, display_name in _TARGET_VAR_DEFS -} | { - v["display"]: (v["name"], v["type"]) - for v in META_VARS.values() -} _MOCK_TYPE_VALUES = { "String": "__mock__", "Boolean": True, @@ -55,26 +35,32 @@ _MOCK_TYPE_VALUES = { "Float": 0.0, } -def buildMockTargetData( + +def createAllVariablesTable( ) -> dict: - """ - Build a target_data dict filled with type-appropriate mock values - for all registered target variables. - """ + + return { + displayName: (name, varType) + for name, varType, _, displayName in _TARGET_VAR_DEFS + } + +def createTargetVarDefs( +) -> list: + + return list(_TARGET_VAR_DEFS) + +def createMockTargetData( +) -> dict: + data = {} - for _, var_type, key_path, _ in _TARGET_VAR_DEFS: + for _, varType, keyPath, _ in _TARGET_VAR_DEFS: d = data - for key in key_path[:-1]: + for key in keyPath[:-1]: d = d.setdefault(key, {}) - d[key_path[-1]] = _MOCK_TYPE_VALUES.get(var_type, "") + d[keyPath[-1]] = _MOCK_TYPE_VALUES.get(varType, "") return data -def registerDefaultTargetVars( -) -> None: - """ - Register all built-in target variables with the engine. - This must be called before any script execution. - Calling multiple times is idempotent (re-registers same keys). - """ - for name, var_type, key_path, display_name in _TARGET_VAR_DEFS: - addTargetVar(name, var_type, key_path, display_name) +def createEngine( +) -> ASEngine: + + return ASEngine(_TARGET_VAR_DEFS) diff --git a/src/autoscript/_helpers.py b/src/autoscript/_helpers.py new file mode 100644 index 0000000..2b7fc27 --- /dev/null +++ b/src/autoscript/_helpers.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +""" +Copyright (c) 2026 KenanZhu. +All rights reserved. + +This software is provided "as is", without any warranty of any kind. +You may use, modify, and distribute this file under the terms of the MIT License. +See the LICENSE file for details. +""" +from datetime import ( + date, + datetime, +) + + +_TYPE_DEFAULT_VAR: dict[str, str | int | float | bool] = { + "String": "", + "Int": 0, + "Float": 0.0, + "Boolean": False, +} + + +def _navigatePath( + data: dict, + keyPath: list, + default=None, +): + + d = data + for key in keyPath[:-1]: + d = d.get(key, {}) + if not isinstance(d, dict): + return default + return d.get(keyPath[-1], default) + +def _assignPath( + data: dict, + keyPath: list, + value, +) -> None: + + d = data + for key in keyPath[:-1]: + d = d.setdefault(key, {}) + d[keyPath[-1]] = value + +def _checkDateFormat( + dateStr: str, + varName: str = "", +) -> None: + + prefix = f"Date 类型变量 '{varName}' 的" if varName else "" + try: + date.fromisoformat(dateStr) + except ValueError: + raise ValueError( + f"{prefix}值 '{dateStr}' 不是合法的日期格式," + f"应为 YYYY-MM-DD" + ) + +def _checkTimeFormat( + timeStr: str, + varName: str = "", +) -> None: + + prefix = f"Time 类型变量 '{varName}' 的" if varName else "" + try: + datetime.strptime(timeStr, "%H:%M") + except ValueError: + raise ValueError( + f"{prefix}值 '{timeStr}' 不是合法的时间格式," + f"应为 HH:MM" + ) + +def _checkType( + varName: str, + varType: str, + value, +) -> None: + + if varType == "Date": + if not isinstance(value, str): + raise ValueError( + f"Date 类型变量 '{varName}' 只能接受日期字符串," + f"不能接受 {_pyTypeToASType(value)} 类型" + ) + _checkDateFormat(value, varName) + return + if varType == "Time": + if not isinstance(value, str): + raise ValueError( + f"Time 类型变量 '{varName}' 只能接受时间字符串," + f"不能接受 {_pyTypeToASType(value)} 类型" + ) + _checkTimeFormat(value, varName) + return + if varType == "Int": + if isinstance(value, bool): + raise ValueError( + f"Int 类型变量 '{varName}' 不能接受 Boolean 类型的值" + ) + if not isinstance(value, int) and not (isinstance(value, float) and value == int(value)): + raise ValueError( + f"Int 类型变量 '{varName}' 不能接受 {_pyTypeToASType(value)} 类型的值" + ) + return + if varType == "Float": + if isinstance(value, bool): + raise ValueError( + f"Float 类型变量 '{varName}' 不能接受 Boolean 类型的值" + ) + if not isinstance(value, (int, float)): + raise ValueError( + f"Float 类型变量 '{varName}' 不能接受 {_pyTypeToASType(value)} 类型的值" + ) + return + if varType == "Boolean": + if not isinstance(value, bool): + raise ValueError( + f"Boolean 类型变量 '{varName}' 不能接受 {_pyTypeToASType(value)} 类型的值" + ) + return + if varType == "String": + if not isinstance(value, str): + raise ValueError( + f"String 类型变量 '{varName}' 不能接受 {_pyTypeToASType(value)} 类型的值" + ) + return + +def _pyTypeToASType( + value, +) -> str: + + if isinstance(value, bool): + return "Boolean" + if isinstance(value, int): + return "Int" + if isinstance(value, float): + return "Float" + if isinstance(value, str): + return "String" + return "Unknown" + +def _cleanLuaError( + rawMsg: str, +) -> str: + + msg = rawMsg.replace('[string ""]:', "").strip() + stackIdx = msg.find("stack traceback:") + if stackIdx != -1: + msg = msg[:stackIdx].strip() + return msg diff --git a/src/gui/ALAboutDialog.py b/src/gui/ALAboutDialog.py index 4441403..94c9c7d 100644 --- a/src/gui/ALAboutDialog.py +++ b/src/gui/ALAboutDialog.py @@ -13,10 +13,7 @@ from PySide6.QtCore import ( Qt, QTimer ) -from PySide6.QtGui import ( - QFont, - QIcon -) +from PySide6.QtGui import QIcon from PySide6.QtWidgets import ( QApplication, QDialog diff --git a/src/gui/ALAutoScriptEditDialog.py b/src/gui/ALAutoScriptEditDialog.py index 86f5a03..82b7fe9 100644 --- a/src/gui/ALAutoScriptEditDialog.py +++ b/src/gui/ALAutoScriptEditDialog.py @@ -9,10 +9,18 @@ See the LICENSE file for details. """ from copy import deepcopy -from PySide6.QtCore import QDate, Qt, QTime, QTimer, Slot +from PySide6.QtCore import ( + QDate, + QSize, + Qt, + QTime, + QTimer, + Slot +) from PySide6.QtGui import ( QColor, QFont, + QIcon, QSyntaxHighlighter, QTextCharFormat, ) @@ -46,11 +54,10 @@ from PySide6.QtWidgets import ( ) from autoscript import ( - ALL_VARIABLES, - _MOCK_TYPE_VALUES, - _TARGET_VAR_DEFS, - execute, - registerDefaultTargetVars, + createAllVariablesTable, + createMockTargetData, + createTargetVarDefs, + createEngine, ) @@ -94,12 +101,12 @@ class ALScriptHighlighter(QSyntaxHighlighter): funcFmt = QTextCharFormat() funcFmt.setForeground(QColor("#DCDCAA")) funcFmt.setFontWeight(QFont.Weight.Normal) - for fn in ["CURRENT_DATE", "CURRENT_TIME", "date_add", "time_add"]: + for fn in [ "time", "date", "datenow", "timenow", "dateadd", "timeadd"]: self._rules.append((r"\b" + fn + r"\b", funcFmt)) varFmt = QTextCharFormat() varFmt.setForeground(QColor("#9CDCFE")) varFmt.setFontWeight(QFont.Weight.Normal) - var_names = [name for _, (name, _) in ALL_VARIABLES.items()] + var_names = [name for _, (name, _) in createAllVariablesTable().items()] for var in var_names: self._rules.append((r"\b" + var + r"\b", varFmt)) strFmt = QTextCharFormat() @@ -158,6 +165,19 @@ class _DebugResultDialog(QDialog): layout.addWidget(btnBox) +class _TabToSpacesEditor(QPlainTextEdit): + + def keyPressEvent( + self, + event + ): + + if event.key() == Qt.Key.Key_Tab: + self.insertPlainText(" ") + return + super().keyPressEvent(event) + + class ALAutoScriptEditDialog(QDialog): def __init__( @@ -194,11 +214,9 @@ class ALAutoScriptEditDialog(QDialog): self.zoomInBtn.setFixedSize(25, 25) self.zoomOutBtn = QPushButton("-") self.zoomOutBtn.setFixedSize(25, 25) - self.zoomResetBtn = QPushButton( - QApplication.style().standardIcon( - QStyle.StandardPixmap.SP_BrowserReload - ), "" - ) + self.zoomResetBtn = QPushButton("") + self.zoomResetBtn.setIcon(QIcon(":/res/icons/Reset.svg")) + self.zoomResetBtn.setIconSize(QSize(20, 20)) self.zoomResetBtn.setFixedSize(25, 25) self.zoomResetBtn.setToolTip("重置缩放") self.zoomLabel = QLabel(f"{self._fontSize}px") @@ -221,16 +239,15 @@ class ALAutoScriptEditDialog(QDialog): toolbarLayout.addWidget(self.zoomResetBtn) toolbarLayout.addWidget(self.zoomLabel) toolbarLayout.addStretch() - self.copyBtn = QPushButton( - QApplication.style().standardIcon( - QStyle.StandardPixmap.SP_FileDialogDetailedView - ), "" - ) + self.copyBtn = QPushButton("") + self.copyBtn.setIcon(QIcon(":/res/icons/Copy.svg")) + self.copyBtn.setIconSize(QSize(20, 20)) self.copyBtn.setFixedSize(25, 25) self.copyBtn.setToolTip("复制脚本") toolbarLayout.addWidget(self.copyBtn) layout.addLayout(toolbarLayout) - self.textEdit = QPlainTextEdit(self) + self.textEdit = _TabToSpacesEditor(self) + self.textEdit.setTabStopDistance(40) self.textEdit.setLineWrapMode( QPlainTextEdit.LineWrapMode.NoWrap ) @@ -329,10 +346,30 @@ class ALAutoScriptEditDialog(QDialog): varLayout.setContentsMargins(4, 4, 4, 4) varLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop) varButtons = [ - (display_name, name) for display_name, (name, _) in ALL_VARIABLES.items() + (display_name, name) for display_name, (name, _) in createAllVariablesTable().items() ] self.addButtonsToGrid(varLayout, varButtons, 0, 0, 3) tabWidget.addTab(varWidget, "变量") + funcWidget = QWidget() + funcLayout = QGridLayout(funcWidget) + funcLayout.setSpacing(4) + funcLayout.setContentsMargins(4, 4, 4, 4) + funcLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop) + funcButtons = [ + ("datenow()", "datenow()", "返回当前日期的 Unix 时间戳"), + ("timenow()", "timenow()", "返回当前时间在一天中的分钟数"), + ("dateadd(d, n)", "dateadd(, )", "日期偏移: dateadd(日期时间戳, 天数)"), + ("timeadd(t, n)", "timeadd(, )", "时间偏移: timeadd(分钟数, 分钟数)"), + ] + for i, (text, template, tooltip) in enumerate(funcButtons): + btn = QPushButton(text) + btn.setProperty("template", template) + btn.clicked.connect(self.insertTemplate) + btn.setFixedWidth(100) + btn.setFixedHeight(25) + btn.setToolTip(tooltip) + funcLayout.addWidget(btn, i // 2, i % 2) + tabWidget.addTab(funcWidget, "工具函数") mockPanel = self.createMockPanel() mockPanel.setMinimumWidth(260) splitter.addWidget(tabWidget) @@ -376,8 +413,12 @@ class ALAutoScriptEditDialog(QDialog): form.setSpacing(4) form.setContentsMargins(5, 10, 5, 5) self._mockWidgets = {} - for name, var_type, key_path, display_name in _TARGET_VAR_DEFS: - default = _MOCK_TYPE_VALUES.get(var_type, "") + mockData = createMockTargetData() + for name, var_type, key_path, display_name in createTargetVarDefs(): + d = mockData + for key in key_path: + d = d[key] + default = d widget = self.makeMockInput(var_type, default) label = QLabel(f"{display_name}: {name}({var_type})") form.addRow(label, widget) @@ -432,7 +473,7 @@ class ALAutoScriptEditDialog(QDialog): ) -> dict: data = {} - for name, var_type, key_path, display_name in _TARGET_VAR_DEFS: + for name, var_type, key_path, display_name in createTargetVarDefs(): widget, _, _ = self._mockWidgets[name] value = self.getMockValue(widget, var_type) d = data @@ -448,7 +489,7 @@ class ALAutoScriptEditDialog(QDialog): if not data: return - for name, var_type, key_path, display_name in _TARGET_VAR_DEFS: + for name, var_type, key_path, display_name in createTargetVarDefs(): d = data try: for key in key_path: @@ -572,11 +613,8 @@ class ALAutoScriptEditDialog(QDialog): clipboard = QApplication.clipboard() clipboard.setText(self.textEdit.toPlainText()) - original = self.copyBtn.text() - self.copyBtn.setText("已复制") self.copyBtn.setEnabled(False) QTimer.singleShot(2000, lambda: ( - self.copyBtn.setText(original), self.copyBtn.setEnabled(True) )) @@ -606,13 +644,13 @@ class ALAutoScriptEditDialog(QDialog): target_data = self.getMockData() before = deepcopy(target_data) try: - registerDefaultTargetVars() - execute(script, target_data) + engine = createEngine() + engine.execute(script, target_data) except ValueError as e: QMessageBox.warning(self, "运行错误", str(e)) return changes = [] - for name, var_type, key_path, display_name in _TARGET_VAR_DEFS: + for name, var_type, key_path, display_name in createTargetVarDefs(): before_val = before after_val = target_data try: diff --git a/src/gui/ALAutoScriptOrchDialog/_helpers.py b/src/gui/ALAutoScriptOrchDialog/_helpers.py index bec9ee6..e287560 100644 --- a/src/gui/ALAutoScriptOrchDialog/_helpers.py +++ b/src/gui/ALAutoScriptOrchDialog/_helpers.py @@ -12,11 +12,7 @@ See the LICENSE file for details. """ import re -from PySide6.QtCore import ( - QObject, - QDate, - QTime -) +from PySide6.QtCore import QObject from PySide6.QtWidgets import ( QComboBox, QDateEdit, @@ -31,61 +27,66 @@ from PySide6.QtWidgets import ( QWidget, ) -from autoscript import ( - ALL_VARIABLES, -) +from autoscript import createAllVariablesTable -# Types that support arithmetic operations (add/sub) -ARITH_TYPES = {"Date", "Time", "Int", "Float"} -VAR_TYPE_ORDER = [ - "String", - "Int", - "Float", - "Boolean", - "Date", - "Time" +VARTYPE_INFOS = [ + # varType, isArithType + ("String", False), + ("Int", True), + ("Float", True), + ("Boolean", False), + ("Date", True), + ("Time", True), ] -PRESET_VARIABLES = [ - { - "name": name.upper(), - "type": vtype, - "display": display - } - for display, (name, vtype) in ALL_VARIABLES.items() + + +def getTypeOrder( +) -> list: + + return [t for t, _ in VARTYPE_INFOS] + +def getArithType( + varType: str +) -> bool: + + for t, a in VARTYPE_INFOS: + if t == varType: + return a + +def getPresetVars( +) -> list: + + return [ + {"name": name.upper(), "type": vtype, "display": display} + for display, (name, vtype) in createAllVariablesTable().items() + ] + + +COMPARE_OPTIONS = [ + ("等于", "=="), + ("不等于", "~="), + ("大于", ">"), + ("小于", "<"), + ("大于等于", ">="), + ("小于等于", "<="), ] -PRESET_NAMES = { - p["name"] for p in PRESET_VARIABLES -} -# Operator display names (UI-specific), using Lua operator symbols -_COMPARE_DISPLAY_MAP = { - "==": "等于", - "~=": "不等于", - ">": "大于", - "<": "小于", - ">=": "大于等于", - "<=": "小于等于", -} -COMPARE_OPERATORS = sorted( - [(name, op) for op, name in _COMPARE_DISPLAY_MAP.items()], - key=lambda x: len(x[1]), reverse=True -) -LOGIC_OPERATORS = [ +LOGIC_OPTIONS = [ ("并且 (and)", "and"), ("或者 (or)", "or"), ] -ACTION_TYPES = [ +ACTION_OPTIONS = [ ("设置为", "set"), ("增加", "add"), ("减少", "sub"), ] -DATE_RELATIVE_OPTIONS = [ +DATE_OPTIONS = [ ("前天", "day_before_yesterday"), ("昨天", "yesterday"), ("今天", "today"), ("明天", "tomorrow"), ("后天", "day_after_tomorrow") ] -DATE_OFFSET_UNITS = [ +DATE_OFFSET_OPTIONS = [ ("天", "days"), ("周", "weeks"), # NOTE: "月" and "年" use fixed day counts (30 / 365), not calendar months/years, @@ -103,7 +104,6 @@ class _DateInputContainer(QWidget): ): super().__init__(parent) - self._dynamicItems = {} # index -> raw expression, for one-way parsed items self.setupUi() def setupUi( @@ -119,7 +119,7 @@ class _DateInputContainer(QWidget): self._modeCombo.setFixedHeight(25) self._stack = QStackedWidget(self) self._relCombo = QComboBox(self) - for display, data in DATE_RELATIVE_OPTIONS: + for display, data in DATE_OPTIONS: self._relCombo.addItem(display, data) self._relCombo.setFixedHeight(25) self._stack.addWidget(self._relCombo) @@ -135,69 +135,15 @@ class _DateInputContainer(QWidget): layout.addWidget(self._stack) layout.addStretch() - _RE_DATE_ADD_CURRENT = re.compile( - r'^date_add\(CURRENT_DATE\(\),\s*(-?\d+)\)$', re.IGNORECASE - ) - def getValue( self ) -> str: mode = self._modeCombo.currentData() if mode == "relative": - idx = self._relCombo.currentIndex() - if idx in self._dynamicItems: - return self._dynamicItems[idx] return self._relCombo.currentText() return self._dateEdit.date().toString("yyyy-MM-dd") - def setValue( - self, - expr: str - ): - - s = expr.strip() - up = s.upper() - if up == "CURRENT_DATE()": - self._modeCombo.setCurrentIndex(0) - self._relCombo.setCurrentIndex(2) - return - m_add = self._RE_DATE_ADD_CURRENT.match(up) - if m_add: - n = int(m_add.group(1)) - _OFFSET_IDX = {-2: 0, -1: 1, 0: 2, 1: 3, 2: 4} - idx = _OFFSET_IDX.get(n) - if idx is not None: - self._modeCombo.setCurrentIndex(0) - self._relCombo.setCurrentIndex(idx) - return - label = f"{n}天后" if n >= 0 else f"{-n}天前" - raw = f"CURRENT_DATE {'+' if n >= 0 else '-'} {abs(n)}" - self._modeCombo.setCurrentIndex(0) - for ci in range(self._relCombo.count()): - if ci in self._dynamicItems and self._dynamicItems[ci] == raw: - self._relCombo.setCurrentIndex(ci) - return - idx = self._relCombo.count() - self._relCombo.addItem(label) - self._dynamicItems[idx] = raw - self._relCombo.setCurrentIndex(idx) - return - m_date_ctor = re.match(r"^DATE\((\d+),\s*(\d+),\s*(\d+)\)$", up) - if m_date_ctor: - self._modeCombo.setCurrentIndex(1) - self._dateEdit.setDate(QDate( - int(m_date_ctor.group(1)), - int(m_date_ctor.group(2)), - int(m_date_ctor.group(3)), - )) - return - m_date = re.match(r'^"(\d{4}-\d{2}-\d{2})"$', s) - if m_date: - self._modeCombo.setCurrentIndex(1) - parts = m_date.group(1).split("-") - self._dateEdit.setDate(QDate(int(parts[0]), int(parts[1]), int(parts[2]))) - class _TimeInputContainer(QWidget): @@ -221,25 +167,6 @@ class _TimeInputContainer(QWidget): return self._timeEdit.time().toString("HH:mm") - def setValue( - self, - expr: str - ): - - s = expr.strip() - up = s.upper() - m_time_ctor = re.match(r"^TIME\((\d+),\s*(\d+)\)$", up) - if m_time_ctor: - self._timeEdit.setTime(QTime( - int(m_time_ctor.group(1)), - int(m_time_ctor.group(2)), - )) - return - m = re.match(r'^"(\d{1,2}:\d{2})"$', s) - if m: - parts = m.group(1).split(":") - self._timeEdit.setTime(QTime(int(parts[0]), int(parts[1]))) - class _DateOffsetContainer(QWidget): @@ -253,7 +180,7 @@ class _DateOffsetContainer(QWidget): self._spinBox.setRange(0, 99999) self._spinBox.setFixedHeight(25) self._unitCombo = QComboBox(self) - for display, data in DATE_OFFSET_UNITS: + for display, data in DATE_OFFSET_OPTIONS: self._unitCombo.addItem(display, data) self._unitCombo.setFixedHeight(25) @@ -270,17 +197,6 @@ class _DateOffsetContainer(QWidget): return str(self.getOffsetDays()) - def setValue( - self, - expr: str - ): - - s = expr.strip().lstrip("+") - try: - self._spinBox.setValue(int(s)) - except ValueError: - pass - def getOffsetDays( self ) -> int: @@ -295,12 +211,6 @@ class _DateOffsetContainer(QWidget): return val * 365 return val - def getRawValue( - self - ) -> str: - - return str(self._spinBox.value()) - class _TimeOffsetContainer(QWidget): @@ -325,29 +235,12 @@ class _TimeOffsetContainer(QWidget): return str(self.getOffsetHours()) - def setValue( - self, - expr: str - ): - - s = expr.strip().lstrip("+") - try: - self._spinBox.setValue(int(s)) - except ValueError: - pass - def getOffsetHours( self ) -> int: return self._spinBox.value() - def getRawValue( - self - ) -> str: - - return str(self._spinBox.value()) - class VariableManager(QObject): @@ -360,13 +253,13 @@ class VariableManager(QObject): self._vars = [] self._nameMap = {} - self._initPresetVars() + self.initPresetVars() - def _initPresetVars( + def initPresetVars( self ): - for p in PRESET_VARIABLES: + for p in getPresetVars(): entry = {"name": p["name"], "type": p["type"], "display": p["display"]} self._vars.append(entry) self._nameMap[p["name"]] = entry @@ -399,19 +292,6 @@ class VariableManager(QObject): break combo.blockSignals(False) - def findExactNameEntry( - self, - combo: QComboBox, - name: str - ) -> int: - - name = name.upper().strip() - for i in range(combo.count()): - d = combo.itemData(i) - if d and len(d) >= 1 and d[0].upper().strip() == name: - return i - return -1 - def makeValueWidget( var_type: str, @@ -535,68 +415,6 @@ def getValueFromWidget( return w.text() return "" -def setValueToWidget( - w: QWidget, - var_type: str, - expr: str -): - """ - Set a widget's value from a Lua script expression. - """ - - if hasattr(w, "setValue"): - w.setValue(expr) - return - s = expr.strip() - up = s.upper() - if isinstance(w, QTimeEdit): - m_time_ctor = re.match(r"^TIME\((\d+),\s*(\d+)\)$", up) - if m_time_ctor: - w.setTime(QTime(int(m_time_ctor.group(1)), int(m_time_ctor.group(2)))) - else: - m = re.match(r'^"(\d{1,2}:\d{2})"$', s) - if m: - parts = m.group(1).split(":") - w.setTime(QTime(int(parts[0]), int(parts[1]))) - elif isinstance(w, QDateEdit): - m_date_ctor = re.match(r"^DATE\((\d+),\s*(\d+),\s*(\d+)\)$", up) - if m_date_ctor: - w.setDate(QDate( - int(m_date_ctor.group(1)), - int(m_date_ctor.group(2)), - int(m_date_ctor.group(3)), - )) - else: - m = re.match(r'^"(\d{4}-\d{2}-\d{2})"$', s) - if m: - parts = m.group(1).split("-") - w.setDate(QDate(int(parts[0]), int(parts[1]), int(parts[2]))) - elif isinstance(w, QComboBox): - for i in range(w.count()): - d = w.itemData(i) - if d is not None: - if str(d).upper() == up: - w.setCurrentIndex(i) - return - if w.itemText(i).upper() == up: - w.setCurrentIndex(i) - return - elif isinstance(w, QSpinBox): - try: - w.setValue(int(expr)) - except ValueError: - pass - elif isinstance(w, QDoubleSpinBox): - try: - w.setValue(float(expr)) - except ValueError: - pass - elif isinstance(w, QLineEdit): - inner = expr.strip() - if inner.startswith('"') and inner.endswith('"'): - inner = inner[1:-1].replace('\\"', '"') - w.setText(inner) - def encodeValueStr( raw_value: str, var_type: str @@ -683,23 +501,6 @@ def encodeDateOrTime( return s return f'"{s}"' -def stripOuterParens( - s: str -) -> str: - - s = s.strip() - if s.startswith("(") and s.endswith(")"): - depth = 0 - for i, ch in enumerate(s): - if ch == "(": - depth += 1 - elif ch == ")": - depth -= 1 - if depth == 0 and i < len(s) - 1: - return s - return s[1:-1].strip() - return s - # Pre-compiled patterns for detecting arithmetic expressions (A + B / A - B) _RE_ARITH_SPACED = re.compile(r'^(.+?)\s+([+-])\s+(.+)$') _RE_ARITH_NOSPACE = re.compile(r'^([A-Za-z_]\w*)([+-])(\d+|[A-Za-z_]\w*)$') @@ -713,59 +514,3 @@ def isArithExpr( s = expr.strip() return bool(_RE_ARITH_SPACED.match(s) or _RE_ARITH_NOSPACE.match(s)) - -def isVarReference( - expr: str -) -> bool: - """ - Return True if *expr* looks like a variable name reference - (as opposed to a literal value or function call). - """ - - s = expr.strip() - up = s.upper() - if up in ("TRUE", "FALSE"): - return False - if re.match(r"^DATE\(|^TIME\(|^DATE_ADD\(|^TIME_ADD\(|^CURRENT_DATE\(|^CURRENT_TIME\(|^CURRENT_", up): - return False - if up.startswith('"') or up.startswith("'"): - return False - if re.match(r"^[+-]?\d", s): - return False - if isArithExpr(s): - return False - return bool(re.match(r"^[A-Z_][A-Z0-9_]*$", up)) - -def isInsideLiteral( - text: str, - pos: int -) -> bool: - - in_single = False - in_double = False - for i, ch in enumerate(text): - if i >= pos: - break - if ch == "'" and not in_double: - in_single = not in_single - elif ch == '"' and not in_single: - in_double = not in_double - return in_single or in_double - -def findOperatorIn( - text: str, - operators: list -) -> tuple[int, str] | None: - - for op in operators: - op_upper = op.upper() - start = 0 - while True: - idx = text.upper().find(op_upper, start) - if idx < 0: - break - if isInsideLiteral(text, idx): - start = idx + 1 - continue - return (idx, op) - return None diff --git a/src/gui/ALAutoScriptOrchDialog/_widgets.py b/src/gui/ALAutoScriptOrchDialog/_widgets.py index daa5710..f29f82f 100644 --- a/src/gui/ALAutoScriptOrchDialog/_widgets.py +++ b/src/gui/ALAutoScriptOrchDialog/_widgets.py @@ -22,14 +22,14 @@ from PySide6.QtWidgets import ( ) from gui.ALAutoScriptOrchDialog._helpers import ( - ACTION_TYPES, - ARITH_TYPES, - COMPARE_OPERATORS, - LOGIC_OPERATORS, - PRESET_VARIABLES, - VAR_TYPE_ORDER, + ACTION_OPTIONS, + COMPARE_OPTIONS, + LOGIC_OPTIONS, encodeValueStr, + getPresetVars, + getTypeOrder, getValueFromWidget, + getArithType, makeComboWidget, makeLabel, makeOffsetWidget, @@ -72,7 +72,7 @@ class ConditionRowFrame(QFrame): if self._isFirst: self.logicCombo = None else: - self.logicCombo = makeComboWidget(LOGIC_OPERATORS, min_width=110, parent=self) + self.logicCombo = makeComboWidget(LOGIC_OPTIONS, min_width=110, parent=self) layout.addWidget(self.logicCombo) self.leftVarCombo = QComboBox(self) self.leftVarCombo.setFixedHeight(25) @@ -80,7 +80,7 @@ class ConditionRowFrame(QFrame): self.leftVarCombo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.populateLeftVarCombo() layout.addWidget(self.leftVarCombo) - self.opCombo = makeComboWidget(COMPARE_OPERATORS, min_width=80, parent=self) + self.opCombo = makeComboWidget(COMPARE_OPTIONS, min_width=80, parent=self) layout.addWidget(self.opCombo) self._compTypeCombo = makeComboWidget([ ("特定值", "literal"), @@ -139,7 +139,7 @@ class ConditionRowFrame(QFrame): self.literalStack = QStackedWidget(self) self.literalStack.setFixedHeight(25) self._literalWidgets = {} - for vt in VAR_TYPE_ORDER: + for vt in getTypeOrder(): w = makeValueWidget(vt, self.literalStack) self._literalWidgets[vt] = w self.literalStack.addWidget(w) @@ -272,7 +272,7 @@ class ActionStepFrame(QFrame): layout = QHBoxLayout(self) layout.setContentsMargins(2, 2, 2, 2) layout.setSpacing(4) - self.opTypeCombo = makeComboWidget(ACTION_TYPES, min_width=70, parent=self) + self.opTypeCombo = makeComboWidget(ACTION_OPTIONS, min_width=70, parent=self) layout.addWidget(self.opTypeCombo) layout.addWidget(makeLabel("设置", self)) self.targetCombo = QComboBox(self) @@ -305,7 +305,7 @@ class ActionStepFrame(QFrame): self.targetCombo.blockSignals(True) self.targetCombo.clear() - for p in PRESET_VARIABLES: + for p in getPresetVars(): if p["name"] in ("CURRENT_TIME", "CURRENT_DATE"): continue info = self._varMgr.getInfoByName(p["name"]) @@ -322,10 +322,10 @@ class ActionStepFrame(QFrame): self._literalWidgets = {} self._offsetWidgets = {} - for vt in VAR_TYPE_ORDER: + for vt in getTypeOrder(): self._literalWidgets[vt] = makeValueWidget(vt, self.valueStack) self.valueStack.addWidget(self._literalWidgets[vt]) - if vt in ARITH_TYPES: + if getArithType(vt): self._offsetWidgets[vt] = makeOffsetWidget(vt, self.valueStack) self.valueStack.addWidget(self._offsetWidgets[vt]) else: diff --git a/src/gui/ALMainWorkers.py b/src/gui/ALMainWorkers.py index e4f08d8..9f684fa 100644 --- a/src/gui/ALMainWorkers.py +++ b/src/gui/ALMainWorkers.py @@ -18,7 +18,7 @@ from PySide6.QtCore import ( from base.MsgBase import MsgBase from operators.AutoLib import AutoLib from utils.JSONReader import JSONReader -from autoscript import execute, registerDefaultTargetVars +from autoscript import createEngine class AutoLibWorker(MsgBase, QThread): @@ -219,8 +219,8 @@ class TimerTaskWorker(AutoLibWorker): continue for user in group.get("users", []): try: - registerDefaultTargetVars() - execute(auto_script, user) + engine = createEngine() + engine.execute(auto_script, user) affected_count += 1 except ValueError as e: self._showTrace( diff --git a/src/gui/ALTimerTaskAddDialog.py b/src/gui/ALTimerTaskAddDialog.py index 1d0b600..2252909 100644 --- a/src/gui/ALTimerTaskAddDialog.py +++ b/src/gui/ALTimerTaskAddDialog.py @@ -108,7 +108,7 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog): self.AutoScriptHelpButton = QPushButton("?") self.AutoScriptHelpButton.setFixedSize(20, 20) self.AutoScriptHelpButton.setToolTip( - "AutoScript 是一种轻量级 DSL\n" + "AutoScript 是一种轻量级 DSL 语言,基于 Lua 实现。\n" "用于在重复定时任务执行前,对用户的预约数据进行预处理\n" "\n" "点击查看完整在线文档" diff --git a/src/gui/ALWebDriverDownloadDialog.py b/src/gui/ALWebDriverDownloadDialog.py index e8cb0d9..43a40d8 100644 --- a/src/gui/ALWebDriverDownloadDialog.py +++ b/src/gui/ALWebDriverDownloadDialog.py @@ -31,7 +31,7 @@ from PySide6.QtWidgets import ( from PySide6.QtGui import QCloseEvent from managers.driver.WebDriverManager import ( - instance as webdriverManagerInstance, + instance as webdriverInstance, WebDriverManager, WebDriverInfo, WebDriverType, @@ -261,7 +261,7 @@ class ALWebDriverDownloadDialog(QDialog): ): try: - self.__driver_manager = webdriverManagerInstance(self.__driver_dir) + self.__driver_manager = webdriverInstance(self.__driver_dir) except ValueError as e: QMessageBox.warning(self, "初始化失败", f"WebDriverManager 初始化失败:\n{str(e)}") self.reject() diff --git a/src/gui/resources/ALResource.qrc b/src/gui/resources/ALResource.qrc index 6fad8c5..a66d94c 100644 --- a/src/gui/resources/ALResource.qrc +++ b/src/gui/resources/ALResource.qrc @@ -3,6 +3,9 @@ icons/AutoLibrary_Logo_64.svg icons/AutoLibrary_Logo_128.svg + icons/Copy.svg + icons/Reset.svg + translators/qtbase_zh_CN.qm diff --git a/src/gui/resources/icons/Copy.svg b/src/gui/resources/icons/Copy.svg new file mode 100644 index 0000000..aa98267 --- /dev/null +++ b/src/gui/resources/icons/Copy.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/gui/resources/icons/Reset.svg b/src/gui/resources/icons/Reset.svg new file mode 100644 index 0000000..da2b511 --- /dev/null +++ b/src/gui/resources/icons/Reset.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file