模組:PythonScript/Librarys/StdLogics
外观
-- Module:PythonScript/Librarys/StdLogics
-- 1.typing
-- 2.contextlib
-- 3.difflib
-- 塞不下了。
local StdLogics = {}
-- ==========================================
-- 1. typing 模組 (型別提示與泛型支援)
-- ==========================================
function StdLogics.typing()
local typing_helper = {
initialized = false,
cache = {}
}
function typing_helper.ensure_init()
if typing_helper.initialized then return end
typing_helper.initialized = true
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local B = Runtime.builtins
local EvalUtils = require("Module:PythonScript/Evaluator/Utils")
local Evaluator = require("Module:PythonScript/Evaluator")
local C = typing_helper.cache
-- ==========================================
-- 1. 核心泛型別名 (GenericAlias)
-- ==========================================
local GenericAlias_dict = {
__init__ = { __type = "builtin_method", __call = function(a)
a[1].__dict.__origin__ = a[2]
a[1].__dict.__args__ = a[3]
a[1].__dict._is_typing = true
return B.None
end },
__repr__ = { __type = "builtin_method", __call = function(a)
local origin = a[1].__dict.__origin__
local args = a[1].__dict.__args__
local oname = (type(origin) == "table" and origin.__dict and origin.__dict._name) or
(type(origin) == "table" and origin.__name) or tostring(origin)
oname = string.gsub(oname, "^typing%.", "")
local p = {}
if type(args) == "table" and args.__type == "tuple" then
for _, v in ipairs(args.elements) do
local v_name = (type(v) == "table" and v.__dict and v.__dict._name) or B.repr({v})
table.insert(p, v_name)
end
else
local v_name = (type(args) == "table" and args.__dict and args.__dict._name) or B.repr({args})
table.insert(p, v_name)
end
return "typing." .. oname .. "[" .. table.concat(p, ", ") .. "]"
end },
__eq__ = { __type = "builtin_method", __call = function(a)
local self, other = a[1], a[2]
if type(other) == "table" and other.__class == C._GenericAlias then
return EvalUtils.py_eq(self.__dict.__origin__, other.__dict.__origin__) and
EvalUtils.py_eq(self.__dict.__args__, other.__dict.__args__)
end
return false
end },
__hash__ = { __type = "builtin_method", __call = function(a)
return B.hash({ a[1].__dict.__origin__ })
end },
__call__ = { __type = "builtin_method", __call = function(a, kw)
local origin = a[1].__dict.__origin__
local call_args = {}
for i = 2, #a do call_args[#call_args + 1] = a[i] end
if type(origin) == "table" then
if origin.__type == "class" then
return Evaluator.invoke(origin, call_args, kw)
elseif origin.__dict and origin.__dict._name then
local map = { List = B.list, Dict = B.dict, Tuple = B.tuple, Set = B.set, FrozenSet = B.frozenset }
local target = map[origin.__dict._name]
if target then return Evaluator.invoke(target, call_args, kw) end
end
end
return B.NotImplemented
end }
}
C._GenericAlias = PyClass.make_class("_GenericAlias", {}, GenericAlias_dict)
C._GenericAlias.__dict["__module__"] = "typing"
-- ==========================================
-- 2. 特殊標記物件 (_SpecialForm)
-- ==========================================
local SpecialForm_dict = {
__init__ = { __type = "builtin_method", __call = function(a)
a[1].__dict._name = a[2]
a[1].__dict._is_typing = true
return B.None
end },
__repr__ = { __type = "builtin_method", __call = function(a)
return "typing." .. a[1].__dict._name
end },
-- 【修復核心】:__getitem__ 必須放在類別上,並委派給實例自定義的函式
__getitem__ = { __type = "builtin_method", __call = function(a)
local self, slice = a[1], a[2]
local getitem_fn = self.__dict._getitem_fn
if getitem_fn then return getitem_fn(self, slice) end
Runtime.raise("TypeError", self.__dict._name .. " is not subscriptable")
end }
}
C._SpecialForm = PyClass.make_class("_SpecialForm", {}, SpecialForm_dict)
C._SpecialForm.__dict["__module__"] = "typing"
local function default_alias_getitem(self, slice)
local inst = PyClass.make_instance(C._GenericAlias)
inst.__dict.__origin__ = self
inst.__dict.__args__ = slice
inst.__dict._is_typing = true
return inst
end
local function make_special_form(name, getitem_fn)
local inst = PyClass.make_instance(C._SpecialForm)
inst.__dict._name = name
inst.__dict._is_typing = true
if getitem_fn then
inst.__dict._getitem_fn = getitem_fn
end
return inst
end
C.Any = make_special_form("Any")
C.Any.__dict._is_any = true
C.NoReturn = make_special_form("NoReturn")
C.Never = make_special_form("Never")
C.Self = make_special_form("Self")
C.Literal = make_special_form("Literal", default_alias_getitem)
C.ClassVar = make_special_form("ClassVar", default_alias_getitem)
C.Final = make_special_form("Final", default_alias_getitem)
C.Annotated = make_special_form("Annotated", default_alias_getitem)
C.Callable = make_special_form("Callable", default_alias_getitem)
C.TypeGuard = make_special_form("TypeGuard", default_alias_getitem)
C.TypeIs = make_special_form("TypeIs", default_alias_getitem)
local common_aliases = {
"List", "Dict", "Tuple", "Set", "FrozenSet", "Type",
"Iterable", "Iterator", "Generator", "Sequence", "Mapping",
"AbstractSet", "MutableSet", "MutableMapping", "MutableSequence",
"Coroutine", "AsyncGenerator", "AsyncIterable", "AsyncIterator", "Awaitable",
"Hashable", "Sized", "Container", "Collection", "Reversible",
"ContextManager", "AsyncContextManager",
"Text", "ByteString", "Match", "Pattern", "ForwardRef",
"OrderedDict", "ChainMap", "Counter", "Deque"
}
for _, name in ipairs(common_aliases) do
C[name] = make_special_form(name, default_alias_getitem)
end
C.Union = make_special_form("Union", function(self, slice)
local items = {}
if type(slice) == "table" and slice.__type == "tuple" then items = slice.elements
else items = {slice} end
return EvalUtils.make_union_type(items)
end)
C.Optional = make_special_form("Optional", function(self, slice)
return EvalUtils.make_union_type({slice, B.NoneType})
end)
-- ==========================================
-- 3. TypeVar 家族
-- ==========================================
local function make_type_var(cls_name)
local dict = {
__init__ = { __type = "builtin_method", __call = function(a, kw)
a[1].__dict.__name__ = a[2]
a[1].__dict._is_typing = true
a[1].__dict.__covariant__ = kw and kw.covariant or false
a[1].__dict.__contravariant__ = kw and kw.contravariant or false
return B.None
end },
__repr__ = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
local p = ""
if d.__covariant__ then p = "+" elseif d.__contravariant__ then p = "-" end
return "~" .. p .. d.__name__
end }
}
local cls = PyClass.make_class(cls_name, {}, dict)
cls.__dict["__module__"] = "typing"
return cls
end
C.TypeVar = make_type_var("TypeVar")
C.ParamSpec = make_type_var("ParamSpec")
C.TypeVarTuple = make_type_var("TypeVarTuple")
-- ==========================================
-- 4. Generic 與 Protocol 基底類別
-- ==========================================
local Generic_dict = {
__class_getitem__ = { __type = "classmethod", __func = { __type = "builtin_method", __call = function(a)
local inst = PyClass.make_instance(C._GenericAlias)
inst.__dict.__origin__ = a[1]
inst.__dict.__args__ = a[2]
inst.__dict._is_typing = true
return inst
end } }
}
C.Generic = PyClass.make_class("Generic", {}, Generic_dict)
C.Generic.__dict["__module__"] = "typing"
C.Protocol = PyClass.make_class("Protocol", { C.Generic }, {})
C.Protocol.__dict["__module__"] = "typing"
-- ==========================================
-- 5. Type Hint 工廠函式 (NewType, cast)
-- ==========================================
C.cast = { __type = "builtin_method", __name = "cast", __call = function(a)
-- 【修復】:非 bound method 參數從 a[1] 開始
return a[2]
end }
C.NewType = { __type = "builtin_method", __name = "NewType", __call = function(a)
local name = a[1] or "NewType"
local tp = a[2]
local nt = { __type = "builtin_method", __name = name, __call = function(args) return args[1] end }
nt.__dict = { __name__ = name, __supertype__ = tp, _is_typing = true }
return nt
end }
-- ==========================================
-- 6. 動態資料類別工廠 (NamedTuple, TypedDict)
-- ==========================================
local NamedTuple_dict = {
__new__ = { __type = "builtin_method", __call = function(a, kw)
local cls = a[1]
-- 模式一:函式呼叫工廠 NamedTuple("Point", [("x", int)])
if cls == C.NamedTuple and #a >= 2 and type(a[2]) == "string" then
local typename = a[2]
local fields = a[3]
local col_mod = require("Module:PythonScript/Librarys").get_module("collections")
local nt_fn = PyClass.get_attr(col_mod, "namedtuple")
local field_names = {}
local ann_pairs = {}
if type(fields) == "table" and (fields.__type == "list" or fields.__type == "tuple") then
for _, f in ipairs(fields.elements) do
if type(f) == "table" and f.__type == "tuple" and #f.elements >= 2 then
table.insert(field_names, f.elements[1])
table.insert(ann_pairs, {key = f.elements[1], value = f.elements[2]})
else
table.insert(field_names, f)
end
end
end
local new_cls = Evaluator.invoke(nt_fn, {typename, {__type="list", elements=field_names}}, {})
new_cls.__dict["__annotations__"] = {__type="dict", pairs=ann_pairs}
return new_cls
end
-- 模式二:類別繼承 class Point(NamedTuple): ...
if cls ~= C.NamedTuple then
if not cls.__dict._nt_class then
local ann = cls.__dict["__annotations__"]
local keys = {}
if ann and type(ann) == "table" and ann.__type == "dict" then
for _, p in ipairs(ann.pairs) do table.insert(keys, p.key) end
end
local col_mod = require("Module:PythonScript/Librarys").get_module("collections")
local nt_fn = PyClass.get_attr(col_mod, "namedtuple")
local list_keys = {__type="list", elements=keys}
cls.__dict._nt_class = Evaluator.invoke(nt_fn, {cls.__name, list_keys}, {})
table.insert(cls.__dict._nt_class.__mro, cls)
end
local nt_args = {}
for i = 2, #a do table.insert(nt_args, a[i]) end
local inst = Evaluator.invoke(cls.__dict._nt_class, nt_args, kw)
if type(inst) == "table" and inst.__type == "instance" then
local elts = {}
local ann = cls.__dict["__annotations__"]
if ann and type(ann) == "table" and ann.__type == "dict" then
for _, p in ipairs(ann.pairs) do
table.insert(elts, require("Module:PythonScript/Class").get_attr(inst, p.key))
end
end
inst.__builtin_value = { __type = "tuple", elements = elts }
end
return inst
end
return B.NotImplemented
end }
}
C.NamedTuple = PyClass.make_class("NamedTuple", { B.object }, NamedTuple_dict)
C.NamedTuple.__dict["__module__"] = "typing"
local TypedDict_dict = {
__new__ = { __type = "builtin_method", __call = function(a, kw)
local cls = a[1]
-- 模式一:函式呼叫工廠 TypedDict("User", {"name": str})
if cls == C.TypedDict and #a >= 2 and type(a[2]) == "string" then
local typename = a[2]
local fields = a[3]
local new_cls = PyClass.make_class(typename, { C.TypedDict }, {})
new_cls.__dict["__module__"] = "typing"
local ann_pairs = {}
if type(fields) == "table" and fields.__type == "dict" then
for _, p in ipairs(fields.pairs) do
table.insert(ann_pairs, {key = p.key, value = p.value})
end
end
new_cls.__dict["__annotations__"] = {__type="dict", pairs=ann_pairs}
return new_cls
end
-- 模式二:類別繼承 class HTTPResponse(TypedDict): ...
-- 執行個體化時,依照 CPython 規範,直接回傳純種 dict 實體!
local dict_args = {}
for i = 2, #a do table.insert(dict_args, a[i]) end
return B.dict.__call(dict_args, kw)
end }
}
C.TypedDict = PyClass.make_class("TypedDict", { B.dict }, TypedDict_dict)
C.TypedDict.__dict["__module__"] = "typing"
end
function typing_helper.get_module()
typing_helper.ensure_init()
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local mod_dict = {
__getattr__ = {
__type = "builtin_method",
__call = function(args)
local self, attr = args[1], args[2]
local val = typing_helper.cache[attr]
if val ~= nil then
self.__dict[attr] = val
return val
end
Runtime.raise("AttributeError", "module 'typing' has no attribute '" .. tostring(attr) .. "'")
end
}
}
local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
mod.__dict["__name__"] = "typing"
mod.__dict["__all__"] = {
__type = "list",
elements = {
"Any", "Literal", "Self", "NoReturn", "Never", "ClassVar", "Final",
"Annotated", "Union", "Optional", "TypeVar", "Generic", "ParamSpec",
"TypeVarTuple", "Protocol", "TypedDict", "NamedTuple", "Callable",
"TypeGuard", "TypeIs", "NewType", "cast",
"List", "Dict", "Tuple", "Set", "FrozenSet", "Type",
"Iterable", "Iterator", "Generator", "Sequence", "Mapping",
"AbstractSet", "MutableSet", "MutableMapping", "MutableSequence",
"Coroutine", "AsyncGenerator", "AsyncIterable", "AsyncIterator", "Awaitable",
"Hashable", "Sized", "Container", "Collection", "Reversible",
"ContextManager", "AsyncContextManager",
"Text", "ByteString", "Match", "Pattern", "ForwardRef",
"OrderedDict", "ChainMap", "Counter", "Deque"
}
}
return mod
end
return typing_helper
end
-- ==========================================
-- 2. contextlib 模組
-- ==========================================
function StdLogics.contextlib()
local contextlib_helper = {
initialized = false,
cache = {}
}
function contextlib_helper.is_none(v)
return v == nil or (type(v) == "table" and v.__class and v.__class.__name == "NoneType")
end
function contextlib_helper.ensure_init()
if contextlib_helper.initialized then return end
contextlib_helper.initialized = true
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local B = Runtime.builtins
local Evaluator = require("Module:PythonScript/Evaluator")
local C = contextlib_helper.cache
-- 輔助:在 with 語句外手動執行 context manager 完整生命週期
local function run_with_cm(cm, body_fn)
local enter_m = PyClass.get_attr(cm, "__enter__")
local exit_m = PyClass.get_attr(cm, "__exit__")
Evaluator.invoke(enter_m, {}, {})
local prev_stack_len = #Runtime.Traceback.stack
local prev_depth = Runtime.Traceback.eval_depth
local prev_file_idx = Runtime.Traceback.current_file_idx
local prev_func_name = Runtime.Traceback.current_func_name
local ok, res = pcall(body_fn)
Runtime.Traceback.eval_depth = prev_depth
if ok then
Evaluator.invoke(exit_m, { B.None, B.None, B.None }, {})
return res
else
while #Runtime.Traceback.stack > prev_stack_len do table.remove(Runtime.Traceback.stack) end
Runtime.Traceback.current_file_idx = prev_file_idx
Runtime.Traceback.current_func_name = prev_func_name
local exc_type = (type(res) == "table" and res.__class) or B.Exception
local tb = (type(res) == "table" and res.__dict and res.__dict.__traceback_info__) or B.None
local suppress = Evaluator.invoke(exit_m, { exc_type, res, tb }, {})
if B.bool.__call({suppress}) then
return B.None
end
error(res, 0)
end
end
-- ==========================================
-- 1. AbstractContextManager & ContextDecorator
-- ==========================================
C.AbstractContextManager = PyClass.make_class("AbstractContextManager", {}, {
__enter__ = { __type = "builtin_method", __name = "__enter__", __call = function(a) return a[1] end },
__exit__ = { __type = "builtin_method", __name = "__exit__", __call = function() return B.None end }
})
C.AbstractContextManager.__dict["__module__"] = "contextlib"
C.AbstractAsyncContextManager = PyClass.make_class("AbstractAsyncContextManager", {}, {
__aenter__ = { __type = "builtin_method", __name = "__aenter__", __call = function(a) return a[1] end },
__aexit__ = { __type = "builtin_method", __name = "__aexit__", __call = function() return B.None end }
})
C.AbstractAsyncContextManager.__dict["__module__"] = "contextlib"
C.ContextDecorator = PyClass.make_class("ContextDecorator", {}, {
_recreate_cm = { __type = "builtin_method", __name = "_recreate_cm", __call = function(a) return a[1] end },
__call__ = { __type = "builtin_method", __name = "__call__", __call = function(a)
local self, func = a[1], a[2]
local wrapper = {
__type = "builtin_method",
__name = (type(func) == "table" and (func.__name or func.__qualname__)) or "<wrapped>",
__dict = { __wrapped__ = func },
__call = function(f_args, f_kwargs)
local rec_m = PyClass.get_attr(self, "_recreate_cm")
local cm = Evaluator.invoke(rec_m, {}, {})
return run_with_cm(cm, function()
return Evaluator.invoke(func, f_args, f_kwargs)
end)
end
}
return wrapper
end }
})
C.ContextDecorator.__dict["__module__"] = "contextlib"
-- ==========================================
-- 2. @contextmanager & @asynccontextmanager
-- ==========================================
local gcm_dict = {
__init__ = { __type = "builtin_method", __call = function(a)
local self = a[1]
self.__dict._func = a[2]
self.__dict._args = a[3] or {}
self.__dict._kwds = a[4] or {}
self.__dict.gen = nil
return nil
end },
_recreate_cm = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
local copy = PyClass.make_instance(a[1].__class)
copy.__dict._func = d._func
copy.__dict._args = d._args
copy.__dict._kwds = d._kwds
return copy
end },
__enter__ = { __type = "builtin_method", __call = function(a)
local self = a[1]
local d = self.__dict
d.gen = Evaluator.invoke(d._func, d._args, d._kwds)
local ok, res = pcall(B.next, { d.gen })
if not ok then
if type(res) == "table" and res.__class and res.__class.__name == "StopIteration" then
Runtime.raise("RuntimeError", "generator didn't yield")
end
error(res, 0)
end
return res
end },
__exit__ = { __type = "builtin_method", __call = function(a)
local self, typ, val, tb = a[1], a[2], a[3], a[4]
local gen = self.__dict.gen
if not gen then return false end
if contextlib_helper.is_none(typ) then
local ok, res = pcall(B.next, { gen })
if not ok then
if type(res) == "table" and res.__class and res.__class.__name == "StopIteration" then
return false
end
error(res, 0)
end
Runtime.raise("RuntimeError", "generator didn't stop")
else
if contextlib_helper.is_none(val) then
val = (type(typ) == "table" and typ.__type == "class") and Evaluator.invoke(typ, {}, {}) or typ
end
local has_throw, throw_m = pcall(PyClass.get_attr, gen, "throw")
if has_throw and throw_m then
local ok, res = pcall(Evaluator.invoke, throw_m, { typ, val, tb }, {})
if not ok then
if type(res) == "table" and res.__class and res.__class.__name == "StopIteration" then
return true
end
if res == val then return false end
error(res, 0)
end
Runtime.raise("RuntimeError", "generator didn't stop after throw()")
end
if gen.__dict then gen.__dict.is_finished = true end
return false
end
end }
}
gcm_dict.__aenter__ = gcm_dict.__enter__
gcm_dict.__aexit__ = gcm_dict.__exit__
local GeneratorContextManager = PyClass.make_class("_GeneratorContextManager", { C.ContextDecorator, C.AbstractContextManager }, gcm_dict)
GeneratorContextManager.__dict["__module__"] = "contextlib"
C.contextmanager = {
__type = "builtin_method", __name = "contextmanager",
__call = function(a)
local func = a and a[1]
if not func then Runtime.raise("TypeError", "contextmanager() missing required argument 'func'") end
return {
__type = "builtin_method",
__name = (type(func) == "table" and (func.__name or func.__qualname__)) or "<contextmanager>",
__dict = { __wrapped__ = func },
__call = function(f_args, f_kwargs)
local inst = PyClass.make_instance(GeneratorContextManager)
inst.__dict._func = func
inst.__dict._args = f_args or {}
inst.__dict._kwds = f_kwargs or {}
return inst
end
}
end
}
C.asynccontextmanager = C.contextmanager
-- ==========================================
-- 3. suppress, nullcontext, closing, aclosing
-- ==========================================
local suppress_dict = {
__init__ = { __type = "builtin_method", __call = function(a)
local self = a[1]
local excs = {}
for i = 2, #a do excs[#excs + 1] = a[i] end
self.__dict._exceptions = excs
return nil
end },
__enter__ = { __type = "builtin_method", __call = function() return B.None end },
__exit__ = { __type = "builtin_method", __call = function(a)
local self, exctype, excinst = a[1], a[2], a[3]
if contextlib_helper.is_none(exctype) then return false end
for _, target in ipairs(self.__dict._exceptions) do
if PyClass.is_subclass(exctype, target) or (not contextlib_helper.is_none(excinst) and PyClass.is_instance(excinst, target)) then
return true
end
end
return false
end }
}
C.suppress = PyClass.make_class("suppress", { C.AbstractContextManager }, suppress_dict)
C.suppress.__dict["__module__"] = "contextlib"
local nullcontext_dict = {
__init__ = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
local er = B.None
if #a >= 2 then er = a[2]
elseif kw and kw.enter_result ~= nil then er = kw.enter_result end
self.__dict.enter_result = er
return nil
end },
__enter__ = { __type = "builtin_method", __call = function(a) return a[1].__dict.enter_result end },
__exit__ = { __type = "builtin_method", __call = function() return false end }
}
nullcontext_dict.__aenter__ = nullcontext_dict.__enter__
nullcontext_dict.__aexit__ = nullcontext_dict.__exit__
C.nullcontext = PyClass.make_class("nullcontext", { C.AbstractContextManager }, nullcontext_dict)
C.nullcontext.__dict["__module__"] = "contextlib"
local closing_dict = {
__init__ = { __type = "builtin_method", __call = function(a)
if #a < 2 then Runtime.raise("TypeError", "closing() missing required argument 'thing'") end
a[1].__dict.thing = a[2]
return nil
end },
__enter__ = { __type = "builtin_method", __call = function(a) return a[1].__dict.thing end },
__exit__ = { __type = "builtin_method", __call = function(a)
local close_m = PyClass.get_attr(a[1].__dict.thing, "close")
Evaluator.invoke(close_m, {}, {})
return false
end }
}
closing_dict.__aenter__ = closing_dict.__enter__
closing_dict.__aexit__ = { __type = "builtin_method", __call = function(a)
local thing = a[1].__dict.thing
local ok, aclose_m = pcall(PyClass.get_attr, thing, "aclose")
if ok and aclose_m then Evaluator.invoke(aclose_m, {}, {})
else Evaluator.invoke(PyClass.get_attr(thing, "close"), {}, {}) end
return false
end }
C.closing = PyClass.make_class("closing", { C.AbstractContextManager }, closing_dict)
C.closing.__dict["__module__"] = "contextlib"
C.aclosing = PyClass.make_class("aclosing", { C.AbstractAsyncContextManager }, closing_dict)
C.aclosing.__dict["__module__"] = "contextlib"
-- ==========================================
-- 4. redirect_stdout & redirect_stderr
-- ==========================================
local function make_redirect_class(cls_name, stream_attr, intercept_print)
local r_dict = {
__init__ = { __type = "builtin_method", __call = function(a)
a[1].__dict._new_target = a[2]
a[1].__dict._old_targets = {}
return nil
end },
__enter__ = { __type = "builtin_method", __call = function(a)
local self = a[1]
local target = self.__dict._new_target
local Sys = require("Module:PythonScript/Librarys/Argparse").Sys
local sys_mod = Sys.modules_registry["sys"] or Sys.get_module()
Sys.modules_registry["sys"] = sys_mod
local old_stream = PyClass.get_attr(sys_mod, stream_attr)
local old_print = B.print
table.insert(self.__dict._old_targets, { stream = old_stream, print_fn = old_print })
PyClass.set_attr(sys_mod, stream_attr, target)
if intercept_print then
B.print = function(p_args, p_kwargs)
local file_arg = p_kwargs and p_kwargs.file
local active_out = (not contextlib_helper.is_none(file_arg)) and file_arg or target
if contextlib_helper.is_none(active_out) then
return B.None
end
local sep = " "
if p_kwargs and not contextlib_helper.is_none(p_kwargs.sep) then
if type(p_kwargs.sep) ~= "string" then Runtime.raise("TypeError", "sep must be None or a string") end
sep = p_kwargs.sep
end
local end_str = "\n"
local raw_end = p_kwargs and (p_kwargs["end"] ~= nil and p_kwargs["end"] or p_kwargs.end_)
if not contextlib_helper.is_none(raw_end) then
if type(raw_end) ~= "string" then Runtime.raise("TypeError", "end must be None or a string") end
end_str = raw_end
end
local parts = {}
for i, v in ipairs(p_args or {}) do parts[i] = B.str.__call({v}) end
local out_str = table.concat(parts, sep) .. end_str
local write_m = PyClass.get_attr(active_out, "write")
Evaluator.invoke(write_m, { out_str }, {})
return B.None
end
end
return target
end },
__exit__ = { __type = "builtin_method", __call = function(a)
local self = a[1]
local saved = table.remove(self.__dict._old_targets)
if saved then
local Sys = require("Module:PythonScript/Librarys/Argparse").Sys
local sys_mod = Sys.modules_registry["sys"]
if sys_mod then PyClass.set_attr(sys_mod, stream_attr, saved.stream) end
if intercept_print then B.print = saved.print_fn end
end
return false
end }
}
local cls = PyClass.make_class(cls_name, { C.AbstractContextManager }, r_dict)
cls.__dict["__module__"] = "contextlib"
return cls
end
C.redirect_stdout = make_redirect_class("redirect_stdout", "stdout", true)
C.redirect_stderr = make_redirect_class("redirect_stderr", "stderr", false)
-- ==========================================
-- 5. ExitStack 動態上下文堆疊
-- ==========================================
local exit_stack_dict
exit_stack_dict = {
__init__ = { __type = "builtin_method", __call = function(a)
a[1].__dict._exit_callbacks = {}
return nil
end },
__enter__ = { __type = "builtin_method", __call = function(a) return a[1] end },
enter_context = { __type = "builtin_method", __call = function(a)
local self, cm = a[1], a[2]
local enter_m = PyClass.get_attr(cm, "__enter__")
local exit_m = PyClass.get_attr(cm, "__exit__")
local res = Evaluator.invoke(enter_m, {}, {})
table.insert(self.__dict._exit_callbacks, { is_sync = true, fn = exit_m })
return res
end },
push = { __type = "builtin_method", __call = function(a)
local self, exit_obj = a[1], a[2]
local ok, exit_m = pcall(PyClass.get_attr, exit_obj, "__exit__")
if ok and exit_m and type(exit_obj) == "table" and (exit_obj.__type == "instance" or exit_obj.__type == "class") then
table.insert(self.__dict._exit_callbacks, { is_sync = true, fn = exit_m })
else
table.insert(self.__dict._exit_callbacks, { is_sync = true, fn = exit_obj })
end
return exit_obj
end },
callback = { __type = "builtin_method", __call = function(a, kw)
local self, cb = a[1], a[2]
if not cb then Runtime.raise("TypeError", "callback() missing required argument 'callback'") end
local cb_args = {}
for i = 3, #a do cb_args[#cb_args + 1] = a[i] end
local cb_kw = {}
for k, v in pairs(kw or {}) do cb_kw[k] = v end
table.insert(self.__dict._exit_callbacks, { is_cb = true, fn = cb, args = cb_args, kw = cb_kw })
return cb
end },
pop_all = { __type = "builtin_method", __call = function(a)
local self = a[1]
local new_stack = PyClass.make_instance(self.__class)
new_stack.__dict._exit_callbacks = self.__dict._exit_callbacks
self.__dict._exit_callbacks = {}
return new_stack
end },
close = { __type = "builtin_method", __call = function(a)
exit_stack_dict.__exit__.__call({ a[1], B.None, B.None, B.None })
return B.None
end },
__exit__ = { __type = "builtin_method", __call = function(a)
local self = a[1]
local cur_type = a[2] or B.None
local cur_val = a[3] or B.None
local cur_tb = a[4] or B.None
local has_exc = not contextlib_helper.is_none(cur_type)
local suppressed = false
local callbacks = self.__dict._exit_callbacks
self.__dict._exit_callbacks = {}
for i = #callbacks, 1, -1 do
local item = callbacks[i]
local prev_stack_len = #Runtime.Traceback.stack
local prev_depth = Runtime.Traceback.eval_depth
local prev_file_idx = Runtime.Traceback.current_file_idx
local prev_func_name = Runtime.Traceback.current_func_name
local ok, res = pcall(function()
if item.is_cb then
Evaluator.invoke(item.fn, item.args, item.kw)
return false
else
return Evaluator.invoke(item.fn, { cur_type, cur_val, cur_tb }, {})
end
end)
Runtime.Traceback.eval_depth = prev_depth
if ok then
if has_exc and B.bool.__call({res}) then
suppressed = true
has_exc = false
cur_type, cur_val, cur_tb = B.None, B.None, B.None
end
else
while #Runtime.Traceback.stack > prev_stack_len do table.remove(Runtime.Traceback.stack) end
Runtime.Traceback.current_file_idx = prev_file_idx
Runtime.Traceback.current_func_name = prev_func_name
if has_exc and type(res) == "table" and res.__dict and res ~= cur_val then
res.__dict.__context__ = cur_val
end
has_exc = true
suppressed = false
cur_val = res
cur_type = (type(res) == "table" and res.__class) or B.Exception
cur_tb = (type(res) == "table" and res.__dict and res.__dict.__traceback_info__) or B.None
end
end
if has_exc then
if cur_val ~= a[3] then error(cur_val, 0) end
return false
end
return suppressed
end }
}
C.ExitStack = PyClass.make_class("ExitStack", { C.AbstractContextManager }, exit_stack_dict)
C.ExitStack.__dict["__module__"] = "contextlib"
C.AsyncExitStack = C.ExitStack
end
function contextlib_helper.get_module()
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local mod_dict = {
__getattr__ = {
__type = "builtin_method",
__call = function(args)
local self, attr = args[1], args[2]
contextlib_helper.ensure_init()
local val = contextlib_helper.cache[attr]
if val ~= nil then
self.__dict[attr] = val
return val
end
Runtime.raise("AttributeError", "module 'contextlib' has no attribute '" .. tostring(attr) .. "'")
end
}
}
local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
mod.__dict["__name__"] = "contextlib"
mod.__dict["__all__"] = {
__type = "list",
elements = {
"asynccontextmanager", "contextmanager", "closing", "aclosing",
"nullcontext", "AbstractContextManager", "AbstractAsyncContextManager",
"AsyncExitStack", "ContextDecorator", "ExitStack",
"redirect_stdout", "redirect_stderr", "suppress"
}
}
return mod
end
return contextlib_helper
end
-- ==========================================
-- 3. difflib 模組
-- ==========================================
function StdLogics.difflib()
local difflib_helper = {
initialized = false,
cache = {}
}
-- ==========================================
-- 1. 基礎型別、序列拆解與雜湊鍵輔助函式
-- ==========================================
function difflib_helper.is_none(v)
return v == nil or (type(v) == "table" and v.__class and v.__class.__name == "NoneType")
end
function difflib_helper.to_bool(v)
return require("Module:PythonScript/Runtime").builtins.bool.__call({v})
end
function difflib_helper.call_junk(fn, elem)
if difflib_helper.is_none(fn) or fn == false then return false end
local Evaluator = require("Module:PythonScript/Evaluator")
return difflib_helper.to_bool(Evaluator.invoke(fn, {elem}, {}))
end
-- 將可雜湊之 Python 物件轉為 Lua table 索引鍵 (供 b2j 字典高速查找與相等比對)
function difflib_helper.make_key(val)
local t = type(val)
if t == "string" or t == "number" or t == "boolean" then return val end
if t == "table" then
if val.__class and val.__class.__name == "NoneType" then
return "N:None"
elseif val.__type == "int" and val.is_big then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
return "I:" .. MathCore.BigInt.to_string(val.sign, val.limbs)
elseif val.__type == "bytes" or val.__type == "bytearray" or val.__type == "memoryview" then
return "B:" .. tostring(val._data)
elseif val.__type == "tuple" then
local parts = {}
for i = 1, #val.elements do
parts[i] = tostring(difflib_helper.make_key(val.elements[i]))
end
return "T:(" .. table.concat(parts, ",") .. ")"
elseif val.__type == "frozenset" then
local parts = {}
for i = 1, #val.elements do
parts[i] = tostring(difflib_helper.make_key(val.elements[i]))
end
table.sort(parts)
return "FS:{" .. table.concat(parts, ",") .. "}"
elseif val.__type == "instance" then
local PyClass = require("Module:PythonScript/Class")
-- 1. 針對 @dataclass 實例:直接提取 compare=True 的欄位生成結構鍵
if val.__class and val.__class.__dict and val.__class.__dict["__dataclass_fields__"] then
local f_dict = val.__class.__dict["__dataclass_fields__"]
local parts = {}
for _, p in ipairs(f_dict.pairs) do
local f = p.value
if f.__dict.compare then
local ok, fv = pcall(PyClass.get_attr, val, p.key)
parts[#parts + 1] = tostring(p.key) .. "=" .. tostring(difflib_helper.make_key(ok and fv or nil))
end
end
return "DC:" .. tostring(val.__class.__name) .. "(" .. table.concat(parts, ",") .. ")"
end
-- 2. 針對其他自定義實例 (Fraction, Decimal, NamedTuple 等)
local B = require("Module:PythonScript/Runtime").builtins
local ok_r, r = pcall(B.repr, {val})
if ok_r and type(r) == "string" and not r:match("^<") then
return "R:" .. tostring(val.__class and val.__class.__name or "inst") .. ":" .. r
end
if val.__dict then
local keys = {}
for k in pairs(val.__dict) do
if type(k) == "string" and not k:match("^__") then keys[#keys + 1] = k end
end
table.sort(keys)
local parts = {}
for _, k in ipairs(keys) do
parts[#parts + 1] = k .. "=" .. tostring(difflib_helper.make_key(val.__dict[k]))
end
return "INST:" .. tostring(val.__class and val.__class.__name or "inst") .. "(" .. table.concat(parts, ",") .. ")"
end
end
end
return val
end
-- 將 Python 序列 (str, bytes, list, tuple, iterable) 轉為 1-based Lua 陣列
function difflib_helper.to_seq(seq)
if difflib_helper.is_none(seq) then return {} end
if type(seq) == "string" then
local arr = {}
if seq:find("[\128-\255]") then
local len = mw.ustring.len(seq)
for i = 1, len do arr[i] = mw.ustring.sub(seq, i, i) end
else
for i = 1, #seq do arr[i] = string.sub(seq, i, i) end
end
return arr
elseif type(seq) == "table" then
local pt = seq.__type
if pt == "list" or pt == "tuple" then
local arr = {}
for i = 1, #seq.elements do arr[i] = seq.elements[i] end
return arr
elseif pt == "bytes" or pt == "bytearray" or pt == "memoryview" then
local arr, d = {}, seq._data
for i = 1, #d do arr[i] = string.byte(d, i) end
return arr
elseif not pt and not seq.__class then
-- 【關鍵修復】:如果它已經是被解包過的純 Lua 陣列,直接回傳
return seq
end
end
local B = require("Module:PythonScript/Runtime").builtins
return B.list.__call({seq}).elements
end
function difflib_helper.calculate_ratio(matches, length)
if length == 0 then return 1.0 end
return 2.0 * matches / length
end
function difflib_helper.format_range_unified(start_idx, stop_idx)
local beginning = start_idx + 1
local length = stop_idx - start_idx
if length == 1 then return tostring(beginning) end
if length == 0 then beginning = beginning - 1 end
return tostring(beginning) .. "," .. tostring(length)
end
function difflib_helper.format_range_context(start_idx, stop_idx)
local beginning = start_idx + 1
local length = stop_idx - start_idx
if length == 0 then beginning = beginning - 1 end
if length <= 1 then return tostring(beginning) end
return tostring(beginning) .. "," .. tostring(beginning + length - 1)
end
-- ==========================================
-- 2. SequenceMatcher 核心演算法
-- ==========================================
function difflib_helper.chain_b(self)
local d = self.__dict
local b = d._b_seq
local b2j, bjunk, bpopular = {}, {}, {}
d.b2j = b2j
d.bjunk = bjunk
d.bpopular = bpopular
for i = 1, #b do
local elt = b[i]
local k = difflib_helper.make_key(elt)
local indices = b2j[k]
if not indices then
indices = {}
b2j[k] = indices
end
indices[#indices + 1] = i - 1 -- 儲存 0-based 索引
end
local isjunk = d.isjunk
if not difflib_helper.is_none(isjunk) and isjunk ~= false then
local checked = {}
for i = 1, #b do
local elt = b[i]
local k = difflib_helper.make_key(elt)
if not checked[k] then
checked[k] = true
if difflib_helper.call_junk(isjunk, elt) then
bjunk[k] = true
b2j[k] = nil
end
end
end
end
local n = #b
if d.autojunk and n >= 200 then
local ntest = math.floor(n / 100) + 1
for k, idxs in pairs(b2j) do
if #idxs > ntest then
bpopular[k] = true
end
end
for k, _ in pairs(bpopular) do
b2j[k] = nil
end
end
end
-- 在 a[alo:ahi] 與 b[blo:bhi] (0-based) 中尋找最長連續匹配區塊
function difflib_helper.find_longest_match_raw(self, alo, ahi, blo, bhi)
local d = self.__dict
local a, b, b2j, bjunk = d._a_seq, d._b_seq, d.b2j, d.bjunk
alo = alo or 0
ahi = ahi or #a
blo = blo or 0
bhi = bhi or #b
local besti, bestj, bestsize = alo, blo, 0
local j2len = {}
for i = alo, ahi - 1 do
local newj2len = {}
local k = difflib_helper.make_key(a[i + 1])
local j_list = b2j[k]
if j_list then
for idx = 1, #j_list do
local j = j_list[idx]
if j >= blo then
if j >= bhi then break end
local klen = (j2len[j - 1] or 0) + 1
newj2len[j] = klen
if klen > bestsize then
besti = i - klen + 1
bestj = j - klen + 1
bestsize = klen
end
end
end
end
j2len = newj2len
end
-- 延伸匹配:先延伸非 junk 的熱門元素,再延伸 junk 元素
for pass = 1, 2 do
local allow_junk = (pass == 2)
while besti > alo and bestj > blo do
local ka = difflib_helper.make_key(a[besti])
local kb = difflib_helper.make_key(b[bestj])
local is_j = (bjunk[kb] == true)
if (allow_junk == is_j) and (ka == kb) then
besti = besti - 1
bestj = bestj - 1
bestsize = bestsize + 1
else
break
end
end
while (besti + bestsize) < ahi and (bestj + bestsize) < bhi do
local ka = difflib_helper.make_key(a[besti + bestsize + 1])
local kb = difflib_helper.make_key(b[bestj + bestsize + 1])
local is_j = (bjunk[kb] == true)
if (allow_junk == is_j) and (ka == kb) then
bestsize = bestsize + 1
else
break
end
end
end
return besti, bestj, bestsize
end
function difflib_helper.get_matching_blocks_raw(self)
local d = self.__dict
if d._matching_blocks_raw then return d._matching_blocks_raw end
local la, lb = #d._a_seq, #d._b_seq
local queue = { { 0, la, 0, lb } }
local matching_blocks = {}
while #queue > 0 do
local frame = table.remove(queue)
local alo, ahi, blo, bhi = frame[1], frame[2], frame[3], frame[4]
local i, j, k = difflib_helper.find_longest_match_raw(self, alo, ahi, blo, bhi)
if k > 0 then
matching_blocks[#matching_blocks + 1] = { i, j, k }
if alo < i and blo < j then
queue[#queue + 1] = { alo, i, blo, j }
end
if (i + k) < ahi and (j + k) < bhi then
queue[#queue + 1] = { i + k, ahi, j + k, bhi }
end
end
end
table.sort(matching_blocks, function(x, y)
if x[1] ~= y[1] then return x[1] < y[1] end
return x[2] < y[2]
end)
-- 合併相鄰的區塊
local non_adjacent = {}
local i1, j1, k1 = 0, 0, 0
for _, blk in ipairs(matching_blocks) do
local i2, j2, k2 = blk[1], blk[2], blk[3]
if (i1 + k1 == i2) and (j1 + k1 == j2) then
k1 = k1 + k2
else
if k1 > 0 then non_adjacent[#non_adjacent + 1] = { i1, j1, k1 } end
i1, j1, k1 = i2, j2, k2
end
end
if k1 > 0 then non_adjacent[#non_adjacent + 1] = { i1, j1, k1 } end
non_adjacent[#non_adjacent + 1] = { la, lb, 0 }
d._matching_blocks_raw = non_adjacent
return non_adjacent
end
function difflib_helper.get_opcodes_raw(self)
local d = self.__dict
if d._opcodes_raw then return d._opcodes_raw end
local i, j = 0, 0
local answer = {}
local blocks = difflib_helper.get_matching_blocks_raw(self)
for _, blk in ipairs(blocks) do
local ai, bj, size = blk[1], blk[2], blk[3]
local tag = nil
if i < ai and j < bj then tag = "replace"
elseif i < ai then tag = "delete"
elseif j < bj then tag = "insert" end
if tag then
answer[#answer + 1] = { tag, i, ai, j, bj }
end
i, j = ai + size, bj + size
if size > 0 then
answer[#answer + 1] = { "equal", ai, i, bj, j }
end
end
d._opcodes_raw = answer
return answer
end
function difflib_helper.get_grouped_opcodes_raw(self, n)
n = n or 3
local raw_codes = difflib_helper.get_opcodes_raw(self)
local codes = {}
for idx = 1, #raw_codes do
local c = raw_codes[idx]
codes[idx] = { c[1], c[2], c[3], c[4], c[5] }
end
if #codes == 0 then
codes = { { "equal", 0, 1, 0, 1 } }
end
if codes[1][1] == "equal" then
local c = codes[1]
codes[1] = { c[1], math.max(c[2], c[3] - n), c[3], math.max(c[4], c[5] - n), c[5] }
end
if codes[#codes][1] == "equal" then
local c = codes[#codes]
codes[#codes] = { c[1], c[2], math.min(c[3], c[2] + n), c[4], math.min(c[5], c[4] + n) }
end
local nn = n + n
local groups, group = {}, {}
for _, c in ipairs(codes) do
local tag, i1, i2, j1, j2 = c[1], c[2], c[3], c[4], c[5]
if tag == "equal" and (i2 - i1) > nn then
group[#group + 1] = { tag, i1, math.min(i2, i1 + n), j1, math.min(j2, j1 + n) }
groups[#groups + 1] = group
group = {}
i1 = math.max(i1, i2 - n)
j1 = math.max(j1, j2 - n)
end
group[#group + 1] = { tag, i1, i2, j1, j2 }
end
if #group > 0 and not (#group == 1 and group[1][1] == "equal") then
groups[#groups + 1] = group
end
return groups
end
function difflib_helper.ratio_raw(self)
local blocks = difflib_helper.get_matching_blocks_raw(self)
local matches = 0
for i = 1, #blocks do matches = matches + blocks[i][3] end
return difflib_helper.calculate_ratio(matches, #self.__dict._a_seq + #self.__dict._b_seq)
end
function difflib_helper.quick_ratio_raw(self)
local d = self.__dict
local a, b = d._a_seq, d._b_seq
if not d._fullbcount then
local fc = {}
for i = 1, #b do
local k = difflib_helper.make_key(b[i])
fc[k] = (fc[k] or 0) + 1
end
d._fullbcount = fc
end
local fullbcount = d._fullbcount
local avail = {}
local matches = 0
for i = 1, #a do
local k = difflib_helper.make_key(a[i])
local numb = avail[k]
if numb == nil then numb = fullbcount[k] or 0 end
avail[k] = numb - 1
if numb > 0 then matches = matches + 1 end
end
return difflib_helper.calculate_ratio(matches, #a + #b)
end
function difflib_helper.real_quick_ratio_raw(self)
local la, lb = #self.__dict._a_seq, #self.__dict._b_seq
return difflib_helper.calculate_ratio(math.min(la, lb), la + lb)
end
-- ==========================================
-- 3. Differ 行內比對與差異輸出演算法
-- ==========================================
function difflib_helper.qformat(aline, bline, atags, btags)
local res = {}
local function rstrip_nl(s) return (s:gsub("[\r\n]+$", "")) end
res[#res + 1] = "- " .. aline
atags = rstrip_nl(atags)
if atags ~= "" then res[#res + 1] = "? " .. atags .. "\n" end
res[#res + 1] = "+ " .. bline
btags = rstrip_nl(btags)
if btags ~= "" then res[#res + 1] = "? " .. btags .. "\n" end
return res
end
function difflib_helper.fancy_helper(a, alo, ahi, b, blo, bhi, out)
if alo < ahi then
if blo < bhi then
local sub_res = difflib_helper.fancy_replace(a, alo, ahi, b, blo, bhi, out._charjunk)
for _, line in ipairs(sub_res) do out[#out + 1] = line end
else
for i = alo + 1, ahi do out[#out + 1] = "- " .. tostring(a[i]) end
end
elseif blo < bhi then
for j = blo + 1, bhi do out[#out + 1] = "+ " .. tostring(b[j]) end
end
end
function difflib_helper.fancy_replace(a, alo, ahi, b, blo, bhi, charjunk)
local best_ratio, cutoff = 0.74, 0.75
local eqi, eqj, best_i, best_j = nil, nil, nil, nil
local cruncher = difflib_helper.make_sm_internal(charjunk, "", "", true)
for j = blo, bhi - 1 do
local bj = tostring(b[j + 1])
difflib_helper.sm_set_seq2(cruncher, bj)
for i = alo, ahi - 1 do
local ai = tostring(a[i + 1])
if ai == bj then
if not eqi then eqi, eqj = i, j end
else
difflib_helper.sm_set_seq1(cruncher, ai)
if difflib_helper.real_quick_ratio_raw(cruncher) > best_ratio
and difflib_helper.quick_ratio_raw(cruncher) > best_ratio then
local r = difflib_helper.ratio_raw(cruncher)
if r > best_ratio then
best_ratio, best_i, best_j = r, i, j
end
end
end
end
end
local out = { _charjunk = charjunk }
if best_ratio < cutoff then
if not eqi then
for i = alo + 1, ahi do out[#out + 1] = "- " .. tostring(a[i]) end
for j = blo + 1, bhi do out[#out + 1] = "+ " .. tostring(b[j]) end
return out
end
best_i, best_j, best_ratio = eqi, eqj, 1.0
else
eqi = nil
end
difflib_helper.fancy_helper(a, alo, best_i, b, blo, best_j, out)
local aelt, belt = tostring(a[best_i + 1]), tostring(b[best_j + 1])
if not eqi then
local atags, btags = {}, {}
difflib_helper.sm_set_seqs(cruncher, aelt, belt)
for _, op in ipairs(difflib_helper.get_opcodes_raw(cruncher)) do
local tag, ai1, ai2, bj1, bj2 = op[1], op[2], op[3], op[4], op[5]
local la, lb = ai2 - ai1, bj2 - bj1
if tag == "replace" then
atags[#atags + 1] = string.rep("^", la)
btags[#btags + 1] = string.rep("^", lb)
elseif tag == "delete" then
atags[#atags + 1] = string.rep("-", la)
elseif tag == "insert" then
btags[#btags + 1] = string.rep("+", lb)
elseif tag == "equal" then
atags[#atags + 1] = string.rep(" ", la)
btags[#btags + 1] = string.rep(" ", lb)
end
end
for _, line in ipairs(difflib_helper.qformat(aelt, belt, table.concat(atags), table.concat(btags))) do
out[#out + 1] = line
end
else
out[#out + 1] = " " .. aelt
end
difflib_helper.fancy_helper(a, best_i + 1, ahi, b, best_j + 1, bhi, out)
return out
end
function difflib_helper.differ_compare_raw(a_seq, b_seq, linejunk, charjunk)
local cruncher = difflib_helper.make_sm_internal(linejunk, a_seq, b_seq, true)
local out = {}
for _, op in ipairs(difflib_helper.get_opcodes_raw(cruncher)) do
local tag, alo, ahi, blo, bhi = op[1], op[2], op[3], op[4], op[5]
if tag == "replace" then
local fancy = difflib_helper.fancy_replace(a_seq, alo, ahi, b_seq, blo, bhi, charjunk)
for _, line in ipairs(fancy) do out[#out + 1] = line end
elseif tag == "delete" then
for i = alo + 1, ahi do out[#out + 1] = "- " .. tostring(a_seq[i]) end
elseif tag == "insert" then
for j = blo + 1, bhi do out[#out + 1] = "+ " .. tostring(b_seq[j]) end
elseif tag == "equal" then
for i = alo + 1, ahi do out[#out + 1] = " " .. tostring(a_seq[i]) end
end
end
return out
end
-- 內部快速建構與重設 SequenceMatcher 狀態
function difflib_helper.sm_set_seq1(sm, a)
local d = sm.__dict
d.a = a
d._a_seq = difflib_helper.to_seq(a)
d._matching_blocks_raw = nil
d._opcodes_raw = nil
end
function difflib_helper.sm_set_seq2(sm, b)
local d = sm.__dict
d.b = b
d._b_seq = difflib_helper.to_seq(b)
d._matching_blocks_raw = nil
d._opcodes_raw = nil
d._fullbcount = nil
difflib_helper.chain_b(sm)
end
function difflib_helper.sm_set_seqs(sm, a, b)
difflib_helper.sm_set_seq1(sm, a)
difflib_helper.sm_set_seq2(sm, b)
end
function difflib_helper.make_sm_internal(isjunk, a, b, autojunk)
difflib_helper.ensure_init()
local inst = require("Module:PythonScript/Class").make_instance(difflib_helper.cache.SequenceMatcher)
inst.__dict.isjunk = isjunk
inst.__dict.autojunk = (autojunk == nil) and true or difflib_helper.to_bool(autojunk)
difflib_helper.sm_set_seqs(inst, a or "", b or "")
return inst
end
-- ==========================================
-- 4. 延遲初始化所有類別與函式
-- ==========================================
function difflib_helper.ensure_init()
if difflib_helper.initialized then return end
difflib_helper.initialized = true
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local B = Runtime.builtins
local C = difflib_helper.cache
-- [A] IS_LINE_JUNK 與 IS_CHARACTER_JUNK
C.IS_LINE_JUNK = {
__type = "builtin_method", __name = "IS_LINE_JUNK",
__call = function(a, kw)
local line = tostring(a[1] or "")
local pat = (a[2] ~= nil and a[2]) or (kw and kw.pat)
if not difflib_helper.is_none(pat) then
local Evaluator = require("Module:PythonScript/Evaluator")
local res = Evaluator.invoke(pat, {line}, {})
return not difflib_helper.is_none(res) and difflib_helper.to_bool(res)
end
local stripped = line:match("^%s*(.-)%s*$")
return stripped == "" or stripped == "#"
end
}
C.IS_CHARACTER_JUNK = {
__type = "builtin_method", __name = "IS_CHARACTER_JUNK",
__call = function(a, kw)
local ch = tostring(a[1] or "")
local ws = (a[2] ~= nil and a[2]) or (kw and kw.ws) or " \t"
if ch == "" then return false end
return string.find(ws, ch, 1, true) ~= nil
end
}
-- [B] Match namedtuple 類別 (Match(a, b, size))
local match_dict = {
__init__ = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
local va = (a[2] ~= nil and a[2]) or (kw and kw.a) or 0
local vb = (a[3] ~= nil and a[3]) or (kw and kw.b) or 0
local vs = (a[4] ~= nil and a[4]) or (kw and kw.size) or 0
self.__dict.a = va
self.__dict.b = vb
self.__dict.size = vs
self.__dict._elts = { va, vb, vs }
self.__builtin_value = { __type = "tuple", elements = { va, vb, vs } }
return nil
end },
__getitem__ = { __type = "builtin_method", __call = function(a)
local self, idx = a[1], a[2]
if type(idx) == "number" then
if idx < 0 then idx = 3 + idx end
local v = self.__dict._elts[idx + 1]
if v ~= nil then return v end
end
Runtime.raise("IndexError", "tuple index out of range")
end },
__len__ = { __type = "builtin_method", __call = function() return 3 end },
__iter__ = { __type = "builtin_method", __call = function(a)
return B.iter({ { __type = "tuple", elements = a[1].__dict._elts } })
end },
__eq__ = { __type = "builtin_method", __call = function(a)
local self, other = a[1], a[2]
local o_elts = (type(other) == "table") and (other.elements or (other.__dict and other.__dict._elts))
if not o_elts or #o_elts ~= 3 then return false end
local s_elts = self.__dict._elts
return s_elts[1] == o_elts[1] and s_elts[2] == o_elts[2] and s_elts[3] == o_elts[3]
end },
__repr__ = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
return string.format("Match(a=%s, b=%s, size=%s)", tostring(d.a), tostring(d.b), tostring(d.size))
end }
}
match_dict.__str__ = match_dict.__repr__
C.Match = PyClass.make_class("Match", {}, match_dict)
C.Match.__dict["__module__"] = "difflib"
local function make_match_inst(va, vb, vs)
local inst = PyClass.make_instance(C.Match)
inst.__dict.a = va; inst.__dict.b = vb; inst.__dict.size = vs
inst.__dict._elts = { va, vb, vs }
inst.__builtin_value = { __type = "tuple", elements = { va, vb, vs } }
return inst
end
-- [C] SequenceMatcher 類別
local sm_dict = {
__init__ = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
kw = kw or {}
local isjunk = (a[2] ~= nil and a[2]) or kw.isjunk or B.None
local seq1 = (a[3] ~= nil and a[3]) or kw.a or ""
local seq2 = (a[4] ~= nil and a[4]) or kw.b or ""
local autojunk = true
if a[5] ~= nil then autojunk = difflib_helper.to_bool(a[5])
elseif kw.autojunk ~= nil then autojunk = difflib_helper.to_bool(kw.autojunk) end
self.__dict.isjunk = isjunk
self.__dict.autojunk = autojunk
difflib_helper.sm_set_seqs(self, seq1, seq2)
return nil
end },
set_seqs = { __type = "builtin_method", __call = function(a)
difflib_helper.sm_set_seqs(a[1], a[2], a[3])
return B.None
end },
set_seq1 = { __type = "builtin_method", __call = function(a)
difflib_helper.sm_set_seq1(a[1], a[2])
return B.None
end },
set_seq2 = { __type = "builtin_method", __call = function(a)
difflib_helper.sm_set_seq2(a[1], a[2])
return B.None
end },
find_longest_match = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
local alo = (a[2] ~= nil and a[2]) or (kw and kw.alo) or 0
local ahi = (a[3] ~= nil and a[3]) or (kw and kw.ahi) or #self.__dict._a_seq
local blo = (a[4] ~= nil and a[4]) or (kw and kw.blo) or 0
local bhi = (a[5] ~= nil and a[5]) or (kw and kw.bhi) or #self.__dict._b_seq
local bi, bj, bs = difflib_helper.find_longest_match_raw(self, alo, ahi, blo, bhi)
return make_match_inst(bi, bj, bs)
end },
get_matching_blocks = { __type = "builtin_method", __call = function(a)
local raw = difflib_helper.get_matching_blocks_raw(a[1])
local elts = {}
for i = 1, #raw do
local blk = raw[i]
elts[i] = make_match_inst(blk[1], blk[2], blk[3])
end
return { __type = "list", elements = elts }
end },
get_opcodes = { __type = "builtin_method", __call = function(a)
local raw = difflib_helper.get_opcodes_raw(a[1])
local elts = {}
for i = 1, #raw do
local op = raw[i]
elts[i] = { __type = "tuple", elements = { op[1], op[2], op[3], op[4], op[5] } }
end
return { __type = "list", elements = elts }
end },
get_grouped_opcodes = { __type = "builtin_method", __call = function(a, kw)
local n = (not difflib_helper.is_none(a[2]) and a[2]) or (kw and not difflib_helper.is_none(kw.n) and kw.n) or 3
local raw_groups = difflib_helper.get_grouped_opcodes_raw(a[1], n)
local py_groups = {}
for i = 1, #raw_groups do
local g = raw_groups[i]
local g_elts = {}
for j = 1, #g do
local op = g[j]
g_elts[j] = { __type = "tuple", elements = { op[1], op[2], op[3], op[4], op[5] } }
end
py_groups[i] = { __type = "list", elements = g_elts }
end
return { __type = "list", elements = py_groups }
end },
ratio = { __type = "builtin_method", __call = function(a) return difflib_helper.ratio_raw(a[1]) end },
quick_ratio = { __type = "builtin_method", __call = function(a) return difflib_helper.quick_ratio_raw(a[1]) end },
real_quick_ratio = { __type = "builtin_method", __call = function(a) return difflib_helper.real_quick_ratio_raw(a[1]) end }
}
C.SequenceMatcher = PyClass.make_class("SequenceMatcher", {}, sm_dict)
C.SequenceMatcher.__dict["__module__"] = "difflib"
-- [D] Differ 類別
local differ_dict = {
__init__ = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
kw = kw or {}
self.__dict.linejunk = (a[2] ~= nil and a[2]) or kw.linejunk or B.None
self.__dict.charjunk = (a[3] ~= nil and a[3]) or kw.charjunk or B.None
return nil
end },
compare = { __type = "builtin_method", __call = function(a)
local self = a[1]
local a_seq = difflib_helper.to_seq(a[2])
local b_seq = difflib_helper.to_seq(a[3])
local lines = difflib_helper.differ_compare_raw(a_seq, b_seq, self.__dict.linejunk, self.__dict.charjunk)
return { __type = "list", elements = lines }
end }
}
C.Differ = PyClass.make_class("Differ", {}, differ_dict)
C.Differ.__dict["__module__"] = "difflib"
-- [E] HtmlDiff 類別
local function html_escape(s)
return (tostring(s):gsub("&", "&"):gsub("<", "<"):gsub(">", ">"):gsub(" ", " "))
end
local htmldiff_dict
htmldiff_dict = {
__init__ = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
kw = kw or {}
self.__dict._tabsize = (a[2] ~= nil and a[2]) or kw.tabsize or 8
self.__dict._wrapcolumn = (a[3] ~= nil and a[3]) or kw.wrapcolumn or B.None
self.__dict._linejunk = (a[4] ~= nil and a[4]) or kw.linejunk or B.None
self.__dict._charjunk = (a[5] ~= nil and a[5]) or kw.charjunk or C.IS_CHARACTER_JUNK
return nil
end },
make_table = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
kw = kw or {}
local fromlines = difflib_helper.to_seq(a[2] or kw.fromlines)
local tolines = difflib_helper.to_seq(a[3] or kw.tolines)
local fromdesc = tostring((a[4] ~= nil and a[4]) or kw.fromdesc or "")
local todesc = tostring((a[5] ~= nil and a[5]) or kw.todesc or "")
local context = difflib_helper.to_bool((a[6] ~= nil and a[6]) or kw.context or false)
local numlines = tonumber((a[7] ~= nil and a[7]) or kw.numlines) or 5
local sm = difflib_helper.make_sm_internal(self.__dict._linejunk, fromlines, tolines, true)
local groups = context and difflib_helper.get_grouped_opcodes_raw(sm, numlines)
or { difflib_helper.get_opcodes_raw(sm) }
local rows = {}
rows[#rows + 1] = '<table class="diff" id="difflib_chg_to0__top" cellspacing="0" cellpadding="0" rules="groups" >'
rows[#rows + 1] = '<colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup>'
rows[#rows + 1] = '<colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup>'
if fromdesc ~= "" or todesc ~= "" then
rows[#rows + 1] = string.format('<thead><tr><th class="diff_next"><br /></th><th colspan="2" class="diff_header">%s</th><th class="diff_next"><br /></th><th colspan="2" class="diff_header">%s</th></tr></thead>', html_escape(fromdesc), html_escape(todesc))
end
rows[#rows + 1] = '<tbody>'
for g_idx, group in ipairs(groups) do
if g_idx > 1 then
rows[#rows + 1] = '</tbody><tbody>'
end
for _, op in ipairs(group) do
local tag, i1, i2, j1, j2 = op[1], op[2], op[3], op[4], op[5]
if tag == "equal" then
for k = 0, (i2 - i1 - 1) do
local lno1, lno2 = i1 + k + 1, j1 + k + 1
local txt1 = html_escape((tostring(fromlines[lno1] or ""):gsub("[\r\n]+$", "")))
local txt2 = html_escape((tostring(tolines[lno2] or ""):gsub("[\r\n]+$", "")))
rows[#rows + 1] = string.format('<tr><td class="diff_next"></td><td class="diff_header" id="from0_%d">%d</td><td nowrap="nowrap">%s</td><td class="diff_next"></td><td class="diff_header" id="to0_%d">%d</td><td nowrap="nowrap">%s</td></tr>', lno1, lno1, txt1, lno2, lno2, txt2)
end
else
local max_k = math.max(i2 - i1, j2 - j1)
local css_l = (tag == "delete" and "diff_sub") or (tag == "replace" and "diff_chg") or ""
local css_r = (tag == "insert" and "diff_add") or (tag == "replace" and "diff_chg") or ""
for k = 0, max_k - 1 do
local has_l = (i1 + k) < i2
local has_r = (j1 + k) < j2
local lno1 = has_l and tostring(i1 + k + 1) or ""
local lno2 = has_r and tostring(j1 + k + 1) or ""
local txt1 = has_l and html_escape((tostring(fromlines[i1 + k + 1] or ""):gsub("[\r\n]+$", ""))) or ""
local txt2 = has_r and html_escape((tostring(tolines[j1 + k + 1] or ""):gsub("[\r\n]+$", ""))) or ""
if has_l and css_l ~= "" then txt1 = '<span class="' .. css_l .. '">' .. txt1 .. '</span>' end
if has_r and css_r ~= "" then txt2 = '<span class="' .. css_r .. '">' .. txt2 .. '</span>' end
rows[#rows + 1] = string.format('<tr><td class="diff_next"></td><td class="diff_header">%s</td><td nowrap="nowrap">%s</td><td class="diff_next"></td><td class="diff_header">%s</td><td nowrap="nowrap">%s</td></tr>', lno1, txt1, lno2, txt2)
end
end
end
end
rows[#rows + 1] = '</tbody></table>'
return table.concat(rows, "\n")
end },
make_file = { __type = "builtin_method", __call = function(a, kw)
local tbl_html = htmldiff_dict.make_table.__call(a, kw)
local charset = (kw and kw.charset) or "utf-8"
return string.format(
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' ..
'<html>\n<head>\n<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' ..
'<title></title>\n<style type="text/css">\n' ..
'table.diff {font-family:Courier; border:medium;}\n' ..
'.diff_header {background-color:#e0e0e0}\n' ..
'td.diff_header {text-align:right}\n' ..
'.diff_next {background-color:#c0c0c0}\n' ..
'.diff_add {background-color:#aaffaa}\n' ..
'.diff_chg {background-color:#ffff77}\n' ..
'.diff_sub {background-color:#ffaaaa}\n' ..
'</style>\n</head>\n<body>\n%s\n</body>\n</html>',
tostring(charset), tbl_html
)
end }
}
C.HtmlDiff = PyClass.make_class("HtmlDiff", {}, htmldiff_dict)
C.HtmlDiff.__dict["__module__"] = "difflib"
-- [F] 模組頂層函式:get_close_matches, unified_diff, context_diff, ndiff, restore, diff_bytes
C.get_close_matches = {
__type = "builtin_method", __name = "get_close_matches",
__call = function(a, kw)
kw = kw or {}
local word = a[1] or kw.word
local possibilities = difflib_helper.to_seq(a[2] or kw.possibilities)
local n = tonumber((a[3] ~= nil and a[3]) or kw.n) or 3
local cutoff = tonumber((a[4] ~= nil and a[4]) or kw.cutoff) or 0.6
if n <= 0 then Runtime.raise("ValueError", "n must be > 0: " .. tostring(n)) end
if cutoff < 0.0 or cutoff > 1.0 then Runtime.raise("ValueError", "cutoff must be in [0.0, 1.0]: " .. tostring(cutoff)) end
local result = {}
local s = difflib_helper.make_sm_internal(B.None, "", word, true)
for idx, x in ipairs(possibilities) do
difflib_helper.sm_set_seq1(s, x)
if difflib_helper.real_quick_ratio_raw(s) >= cutoff
and difflib_helper.quick_ratio_raw(s) >= cutoff then
local score = difflib_helper.ratio_raw(s)
if score >= cutoff then
result[#result + 1] = { score = score, x = x, idx = idx }
end
end
end
table.sort(result, function(p1, p2)
if p1.score ~= p2.score then return p1.score > p2.score end
if type(p1.x) == "string" and type(p2.x) == "string" and p1.x ~= p2.x then
return p1.x > p2.x
end
return p1.idx < p2.idx
end)
local out = {}
for i = 1, math.min(n, #result) do out[i] = result[i].x end
return { __type = "list", elements = out }
end
}
C.unified_diff = {
__type = "builtin_method", __name = "unified_diff",
__call = function(a, kw)
kw = kw or {}
local seq_a = difflib_helper.to_seq(a[1] or kw.a)
local seq_b = difflib_helper.to_seq(a[2] or kw.b)
local fromfile = tostring((a[3] ~= nil and a[3]) or kw.fromfile or "")
local tofile = tostring((a[4] ~= nil and a[4]) or kw.tofile or "")
local fromdate = tostring((a[5] ~= nil and a[5]) or kw.fromfiledate or "")
local todate = tostring((a[6] ~= nil and a[6]) or kw.tofiledate or "")
local n = tonumber((a[7] ~= nil and a[7]) or kw.n) or 3
local lineterm = (a[8] ~= nil and a[8]) or kw.lineterm
if lineterm == nil then lineterm = "\n" else lineterm = tostring(lineterm) end
local started = false
local out = {}
local sm = difflib_helper.make_sm_internal(B.None, seq_a, seq_b, true)
for _, group in ipairs(difflib_helper.get_grouped_opcodes_raw(sm, n)) do
if not started then
started = true
local from_t = (fromdate ~= "") and ("\t" .. fromdate) or ""
local to_t = (todate ~= "") and ("\t" .. todate) or ""
out[#out + 1] = "--- " .. fromfile .. from_t .. lineterm
out[#out + 1] = "+++ " .. tofile .. to_t .. lineterm
end
local first, last = group[1], group[#group]
local file1_range = difflib_helper.format_range_unified(first[2], last[3])
local file2_range = difflib_helper.format_range_unified(first[4], last[5])
out[#out + 1] = "@@ -" .. file1_range .. " +" .. file2_range .. " @@" .. lineterm
for _, op in ipairs(group) do
local tag, i1, i2, j1, j2 = op[1], op[2], op[3], op[4], op[5]
if tag == "equal" then
for i = i1 + 1, i2 do out[#out + 1] = " " .. tostring(seq_a[i]) end
else
if tag == "replace" or tag == "delete" then
for i = i1 + 1, i2 do out[#out + 1] = "-" .. tostring(seq_a[i]) end
end
if tag == "replace" or tag == "insert" then
for j = j1 + 1, j2 do out[#out + 1] = "+" .. tostring(seq_b[j]) end
end
end
end
end
return { __type = "list", elements = out }
end
}
C.context_diff = {
__type = "builtin_method", __name = "context_diff",
__call = function(a, kw)
kw = kw or {}
local seq_a = difflib_helper.to_seq(a[1] or kw.a)
local seq_b = difflib_helper.to_seq(a[2] or kw.b)
local fromfile = tostring((a[3] ~= nil and a[3]) or kw.fromfile or "")
local tofile = tostring((a[4] ~= nil and a[4]) or kw.tofile or "")
local fromdate = tostring((a[5] ~= nil and a[5]) or kw.fromfiledate or "")
local todate = tostring((a[6] ~= nil and a[6]) or kw.tofiledate or "")
local n = tonumber((a[7] ~= nil and a[7]) or kw.n) or 3
local lineterm = (a[8] ~= nil and a[8]) or kw.lineterm
if lineterm == nil then lineterm = "\n" else lineterm = tostring(lineterm) end
local prefix = { insert = "+ ", delete = "- ", replace = "! ", equal = " " }
local started = false
local out = {}
local sm = difflib_helper.make_sm_internal(B.None, seq_a, seq_b, true)
for _, group in ipairs(difflib_helper.get_grouped_opcodes_raw(sm, n)) do
if not started then
started = true
local from_t = (fromdate ~= "") and ("\t" .. fromdate) or ""
local to_t = (todate ~= "") and ("\t" .. todate) or ""
out[#out + 1] = "*** " .. fromfile .. from_t .. lineterm
out[#out + 1] = "--- " .. tofile .. to_t .. lineterm
end
local first, last = group[1], group[#group]
out[#out + 1] = "***************" .. lineterm
local file1_range = difflib_helper.format_range_context(first[2], last[3])
out[#out + 1] = "*** " .. file1_range .. " ****" .. lineterm
local has_a_chg, has_b_chg = false, false
for _, op in ipairs(group) do
if op[1] == "replace" or op[1] == "delete" then has_a_chg = true end
if op[1] == "replace" or op[1] == "insert" then has_b_chg = true end
end
if has_a_chg then
for _, op in ipairs(group) do
local tag, i1, i2 = op[1], op[2], op[3]
if tag ~= "insert" then
for i = i1 + 1, i2 do out[#out + 1] = prefix[tag] .. tostring(seq_a[i]) end
end
end
end
local file2_range = difflib_helper.format_range_context(first[4], last[5])
out[#out + 1] = "--- " .. file2_range .. " ----" .. lineterm
if has_b_chg then
for _, op in ipairs(group) do
local tag, j1, j2 = op[1], op[4], op[5]
if tag ~= "delete" then
for j = j1 + 1, j2 do out[#out + 1] = prefix[tag] .. tostring(seq_b[j]) end
end
end
end
end
return { __type = "list", elements = out }
end
}
C.ndiff = {
__type = "builtin_method", __name = "ndiff",
__call = function(a, kw)
kw = kw or {}
local seq_a = difflib_helper.to_seq(a[1] or kw.a)
local seq_b = difflib_helper.to_seq(a[2] or kw.b)
local lj = (a[3] ~= nil and a[3]) or kw.linejunk or B.None
local cj = (a[4] ~= nil and a[4]) or kw.charjunk or C.IS_CHARACTER_JUNK
local lines = difflib_helper.differ_compare_raw(seq_a, seq_b, lj, cj)
return { __type = "list", elements = lines }
end
}
C.restore = {
__type = "builtin_method", __name = "restore",
__call = function(a, kw)
kw = kw or {}
local delta = difflib_helper.to_seq(a[1] or kw.delta)
local which = tonumber((a[2] ~= nil and a[2]) or kw.which)
if which ~= 1 and which ~= 2 then
Runtime.raise("ValueError", "unknown delta choice (must be 1 or 2): " .. tostring(which))
end
local tag = (which == 1) and "- " or "+ "
local out = {}
for _, line in ipairs(delta) do
local s = tostring(line)
local pfx = s:sub(1, 2)
if pfx == " " or pfx == tag then
out[#out + 1] = s:sub(3)
end
end
return { __type = "list", elements = out }
end
}
C.diff_bytes = {
__type = "builtin_method", __name = "diff_bytes",
__call = function(a, kw)
kw = kw or {}
local dfunc = a[1] or kw.dfunc
local function b_to_s(b_obj, def)
if difflib_helper.is_none(b_obj) then return def end
if type(b_obj) == "table" and (b_obj.__type == "bytes" or b_obj.__type == "bytearray" or b_obj.__type == "memoryview") then
return b_obj._data
end
Runtime.raise("TypeError", "diff_bytes() arguments must be bytes")
end
local function b_list_to_s(seq)
local elts = difflib_helper.to_seq(seq)
local res = {}
for i = 1, #elts do res[i] = b_to_s(elts[i], "") end
return { __type = "list", elements = res }
end
local sa = b_list_to_s(a[2] or kw.a)
local sb = b_list_to_s(a[3] or kw.b)
local ff = b_to_s(a[4] or kw.fromfile, "")
local tf = b_to_s(a[5] or kw.tofile, "")
local fd = b_to_s(a[6] or kw.fromfiledate, "")
local td = b_to_s(a[7] or kw.tofiledate, "")
local n = (a[8] ~= nil and a[8]) or kw.n or 3
local lt = b_to_s(a[9] or kw.lineterm, "\n")
local Evaluator = require("Module:PythonScript/Evaluator")
local str_lines = Evaluator.invoke(dfunc, { sa, sb, ff, tf, fd, td, n, lt }, {})
local elts = difflib_helper.to_seq(str_lines)
local b_out = {}
for i = 1, #elts do
b_out[i] = { __type = "bytes", _data = tostring(elts[i]) }
end
return { __type = "list", elements = b_out }
end
}
end
-- ==========================================
-- 5. 模組匯出工廠
-- ==========================================
function difflib_helper.get_module()
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local mod_dict = {
__getattr__ = {
__type = "builtin_method",
__call = function(args)
local self, attr = args[1], args[2]
difflib_helper.ensure_init()
local val = difflib_helper.cache[attr]
if val ~= nil then
self.__dict[attr] = val
return val
end
Runtime.raise("AttributeError", "module 'difflib' has no attribute '" .. tostring(attr) .. "'")
end
}
}
local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
mod.__dict["__name__"] = "difflib"
mod.__dict["__all__"] = {
__type = "list",
elements = {
"get_close_matches", "ndiff", "restore", "SequenceMatcher",
"Differ", "IS_CHARACTER_JUNK", "IS_LINE_JUNK", "context_diff",
"unified_diff", "diff_bytes", "HtmlDiff", "Match"
}
}
return mod
end
return difflib_helper
end
return StdLogics