模組:沙盒/a2569875/PythonScript/Librarys/Pathlib
外观
-- Module:PythonScript/Librarys/Pathlib
-- 純 Lua 實作的 Python pathlib 標準庫 (延遲載入與 MediaWiki 頁面系統橋接版)
local PathlibMod = {}
local PyClass = require("Module:沙盒/a2569875/PythonScript/Class")
local Runtime = require("Module:沙盒/a2569875/PythonScript/Runtime")
-- 集中管理內部狀態與輔助函式,嚴格控制 Lua 5.1 local 變數數量
local S = {
classes = nil,
stat_class = nil,
file_class = nil
}
function S.is_none(v)
return v == nil or (type(v) == "table" and v.__class and v.__class.__name == "NoneType")
end
-- 提取字串或 os.PathLike 物件的路徑字串
function S.extract_fspath(obj)
if type(obj) == "string" then return obj end
if type(obj) == "table" then
if obj.__type == "instance" then
if obj.__dict and type(obj.__dict._path_str) == "string" then
return obj.__dict._path_str
end
if obj.__builtin_value and type(obj.__builtin_value) == "string" then
return obj.__builtin_value
end
local ok, fspath_m = pcall(PyClass.get_attr, obj, "__fspath__")
if ok and fspath_m then
local Evaluator = require("Module:沙盒/a2569875/PythonScript/Evaluator")
local res = Evaluator.invoke(fspath_m, {}, {})
if type(res) == "string" then return res end
Runtime.raise("TypeError", "expected __fspath__() to return str")
end
end
end
local tname = type(obj) == "table" and (obj.__class and obj.__class.__name or obj.__type) or type(obj)
Runtime.raise("TypeError", "argument should be a str object or an os.PathLike object returning str, not '" .. tostring(tname) .. "'")
end
-- 解析單一段 Windows 路徑之 drive, root 與相對區段
function S.parse_win_single(raw)
local s = raw:gsub("/", "\\")
local drive, root, rest = "", "", s
local unc_host, unc_share, unc_slash, unc_rem = s:match("^\\\\([^\\]+)\\([^\\]+)(\\?)(.*)$")
if unc_host then
drive = "\\\\" .. unc_host .. "\\" .. unc_share
root = "\\"
rest = unc_rem
else
local d_letter, d_slash, d_rem = s:match("^([a-zA-Z]:)(\\?)(.*)$")
if d_letter then
drive = d_letter
root = (d_slash ~= "") and "\\" or ""
rest = d_rem
elseif s:sub(1, 1) == "\\" then
root = "\\"
rest = s:gsub("^\\+", "")
end
end
local segs = {}
for part in rest:gmatch("[^\\]+") do
if part ~= "." then table.insert(segs, part) end
end
return drive, root, segs
end
-- 正規化並合併多段路徑參數
function S.parse_segments(raw_list, is_win)
if not is_win then
local root = ""
local segs = {}
for _, raw in ipairs(raw_list) do
if raw ~= "" then
if raw:sub(1, 1) == "/" then
if raw:sub(1, 2) == "//" and raw:sub(3, 3) ~= "/" then
root = "//"
else
root = "/"
end
segs = {}
end
for part in raw:gmatch("[^/]+") do
if part ~= "." then table.insert(segs, part) end
end
end
end
return "", root, segs
else
local drive, root, segs = "", "", {}
for _, raw in ipairs(raw_list) do
if raw ~= "" then
local d, r, p_segs = S.parse_win_single(raw)
if d ~= "" and r ~= "" then
drive, root, segs = d, r, p_segs
elseif r ~= "" then
root, segs = r, p_segs
elseif d ~= "" then
if d:lower() ~= drive:lower() then
drive, root, segs = d, "", p_segs
else
for _, seg in ipairs(p_segs) do table.insert(segs, seg) end
end
else
for _, seg in ipairs(p_segs) do table.insert(segs, seg) end
end
end
end
return drive, root, segs
end
end
-- 由內部狀態建構 Path 實例
function S.build_from_parsed(cls, drive, root, segs, is_win)
local inst = PyClass.make_instance(cls)
local sep = is_win and "\\" or "/"
local anchor = drive .. root
local parts = {}
if anchor ~= "" then table.insert(parts, anchor) end
for i = 1, #segs do table.insert(parts, segs[i]) end
local path_str
if #parts == 0 then
path_str = "."
elseif anchor ~= "" then
path_str = anchor .. table.concat(segs, sep)
else
path_str = table.concat(segs, sep)
end
inst.__dict._drive = drive
inst.__dict._root = root
inst.__dict._segs = segs
inst.__dict._parts = parts
inst.__dict._is_win = is_win and true or false
inst.__dict._path_str = path_str
inst.__dict._norm_case = is_win and path_str:lower() or path_str
return inst
end
function S.construct_path(cls, raw_args, is_win)
local strs = {}
for _, a in ipairs(raw_args or {}) do
table.insert(strs, S.extract_fspath(a))
end
local drive, root, segs = S.parse_segments(strs, is_win)
return S.build_from_parsed(cls, drive, root, segs, is_win)
end
-- 計算檔名、副檔名與主檔名
function S.get_name(self)
local segs = self.__dict._segs
return (#segs > 0) and segs[#segs] or ""
end
function S.get_suffix(self)
local name = S.get_name(self)
if name == "" or name == "." or name == ".." or name:sub(-1) == "." then return "" end
local dot_idx = name:match(".*()%.")
if not dot_idx or dot_idx == 1 then return "" end
return name:sub(dot_idx)
end
function S.get_suffixes(self)
local name = S.get_name(self)
if name == "" or name == "." or name == ".." or name:sub(-1) == "." then return {} end
local stripped = name:gsub("^%.+", "")
local res = {}
for ext in stripped:gmatch("%.[^%.]+") do
table.insert(res, ext)
end
return res
end
function S.get_stem(self)
local name = S.get_name(self)
local suf = S.get_suffix(self)
if suf == "" then return name end
return name:sub(1, #name - #suf)
end
-- 轉換萬用字元 glob 為 Lua pattern
function S.glob_seg_to_lua(pat, is_win)
local p = is_win and pat:lower() or pat
local out = { "^" }
local i, n = 1, #p
while i <= n do
local c = p:sub(i, i)
if c == "*" then table.insert(out, ".*")
elseif c == "?" then table.insert(out, ".")
elseif c == "[" then
local close_idx = p:find("]", i + 1, true)
if close_idx then
local inner = p:sub(i + 1, close_idx - 1)
if inner:sub(1, 1) == "!" then inner = "^" .. inner:sub(2) end
table.insert(out, "[" .. inner .. "]")
i = close_idx
else
table.insert(out, "%[")
end
else
if c:match("[%^%$%(%)%%%.%[%]%*%+%-%?]") then
table.insert(out, "%" .. c)
else
table.insert(out, c)
end
end
i = i + 1
end
table.insert(out, "$")
return table.concat(out)
end
-- MediaWiki Title 解析器 (自動相容前導斜線與重定向)
function S.resolve_mw_title(self, must_exist)
local p = self.__dict._path_str
if p == "." or p == "/" then
local ok, cur = pcall(mw.title.getCurrentTitle)
if ok and cur then return cur end
end
local ok, t = pcall(mw.title.new, p)
if (not ok or not t or not (t.exists or (t.file and t.file.exists))) and p:sub(1, 1) == "/" then
local clean = p:gsub("^/+", "")
if clean ~= "" then
local ok2, t2 = pcall(mw.title.new, clean)
if ok2 and t2 then t = t2 end
end
end
local exists = (t ~= nil) and (t.exists or (t.file and t.file.exists) or false)
if must_exist and not exists then
Runtime.raise("FileNotFoundError", "[Errno 2] No such file or directory: '" .. tostring(p) .. "'")
end
return t, exists
end
-- 唯讀環境攔截器與目錄列舉攔截器
function S.readonly_guard()
Runtime.raise("OSError", "[Errno 30] Read-only file system: write/modify operations are not supported in MediaWiki environment")
end
function S.glob_guard()
Runtime.raise("NotImplementedError", "directory listing and globbing (iterdir/glob/rglob) are not supported in MediaWiki environment")
end
-- 建立虛擬檔案物件 (供 Path.open 使用,支援 with 與 for 迴圈迭代)
function S.make_file_object(path_str, content, is_binary)
if not S.file_class then
local f_dict = {
read = { __type = "builtin_method", __name = "read", __call = function(args)
local self = args[1]
if self.__dict.closed then Runtime.raise("ValueError", "I/O operation on closed file.") end
local n = args[2]
local data = self.__dict._content
local pos = self.__dict._pos
local res
if S.is_none(n) or (type(n) == "number" and n < 0) then
res = data:sub(pos)
self.__dict._pos = #data + 1
else
res = data:sub(pos, pos + n - 1)
self.__dict._pos = math.min(#data + 1, pos + n)
end
if self.__dict._binary then return { __type = "bytes", _data = res } end
return res
end },
readline = { __type = "builtin_method", __name = "readline", __call = function(args)
local self = args[1]
if self.__dict.closed then Runtime.raise("ValueError", "I/O operation on closed file.") end
local data = self.__dict._content
local pos = self.__dict._pos
if pos > #data then
return self.__dict._binary and { __type = "bytes", _data = "" } or ""
end
local nl = data:find("\n", pos, true)
local res
if nl then
res = data:sub(pos, nl)
self.__dict._pos = nl + 1
else
res = data:sub(pos)
self.__dict._pos = #data + 1
end
if self.__dict._binary then return { __type = "bytes", _data = res } end
return res
end },
readlines = { __type = "builtin_method", __name = "readlines", __call = function(args)
local self = args[1]
local lines = {}
local rl = S.file_class.__dict.readline.__call
while true do
local line = rl({self})
local is_empty = (type(line) == "string" and line == "") or (type(line) == "table" and line._data == "")
if is_empty then break end
table.insert(lines, line)
end
return { __type = "list", elements = lines }
end },
seek = { __type = "builtin_method", __name = "seek", __call = function(args)
local self, offset, whence = args[1], args[2] or 0, args[3] or 0
if self.__dict.closed then Runtime.raise("ValueError", "I/O operation on closed file.") end
local len = #self.__dict._content
local target = 0
if whence == 0 then target = offset
elseif whence == 1 then target = (self.__dict._pos - 1) + offset
elseif whence == 2 then target = len + offset
else Runtime.raise("ValueError", "invalid whence") end
target = math.max(0, target)
self.__dict._pos = target + 1
return target
end },
tell = { __type = "builtin_method", __name = "tell", __call = function(args)
local self = args[1]
if self.__dict.closed then Runtime.raise("ValueError", "I/O operation on closed file.") end
return self.__dict._pos - 1
end },
close = { __type = "builtin_method", __name = "close", __call = function(args)
args[1].__dict.closed = true
return Runtime.builtins.None
end },
__enter__ = { __type = "builtin_method", __name = "__enter__", __call = function(args)
if args[1].__dict.closed then Runtime.raise("ValueError", "I/O operation on closed file.") end
return args[1]
end },
__exit__ = { __type = "builtin_method", __name = "__exit__", __call = function(args)
args[1].__dict.closed = true
return false
end },
__iter__ = { __type = "builtin_method", __name = "__iter__", __call = function(args)
return args[1]
end },
__next__ = { __type = "builtin_method", __name = "__next__", __call = function(args)
local line = S.file_class.__dict.readline.__call({args[1]})
local is_empty = (type(line) == "string" and line == "") or (type(line) == "table" and line._data == "")
if is_empty then Runtime.raise("StopIteration") end
return line
end },
__repr__ = { __type = "builtin_method", __name = "__repr__", __call = function(args)
return "<_io.WikiFile name='" .. tostring(args[1].__dict.name) .. "' mode='" .. tostring(args[1].__dict.mode) .. "'>"
end }
}
f_dict.__str__ = f_dict.__repr__
S.file_class = PyClass.make_class("WikiFileWrapper", {}, f_dict)
end
local inst = PyClass.make_instance(S.file_class)
inst.__dict.name = path_str
inst.__dict.mode = is_binary and "rb" or "r"
inst.__dict.closed = false
inst.__dict._content = content
inst.__dict._pos = 1
inst.__dict._binary = is_binary
return inst
end
-- 延遲初始化 6 大 Path 類別階層
function S.ensure_classes()
if S.classes then return S.classes end
local C = {}
local function make_prop(fn)
return { __type = "property", fget = { __type = "builtin_method", __call = fn } }
end
local function new_pure_path(args)
local cls = args and args[1]
local target_cls = cls
if cls == C.PurePath then target_cls = C.PurePosixPath end
local raw_args = {}
for i = 2, #(args or {}) do table.insert(raw_args, args[i]) end
local is_win = PyClass.is_subclass(target_cls, C.PureWindowsPath)
return S.construct_path(target_cls, raw_args, is_win)
end
local function new_concrete_path(args)
local cls = args and args[1]
if cls == C.WindowsPath or PyClass.is_subclass(cls, C.WindowsPath) then
Runtime.raise("NotImplementedError", "cannot instantiate 'WindowsPath' on your system")
end
local target_cls = (cls == C.Path) and C.PosixPath or cls
local raw_args = {}
for i = 2, #(args or {}) do table.insert(raw_args, args[i]) end
return S.construct_path(target_cls, raw_args, false)
end
local pure_dict = {
__new__ = { __type = "staticmethod", __func = { __type = "builtin_method", __name = "__new__", __call = new_pure_path } },
__init__ = { __type = "builtin_method", __name = "__init__", __call = function() return nil end },
__str__ = { __type = "builtin_method", __name = "__str__", __call = function(a) return a[1].__dict._path_str end },
__fspath__ = { __type = "builtin_method", __name = "__fspath__", __call = function(a) return a[1].__dict._path_str end },
__bytes__ = { __type = "builtin_method", __name = "__bytes__", __call = function(a)
return { __type = "bytes", _data = a[1].__dict._path_str }
end },
__repr__ = { __type = "builtin_method", __name = "__repr__", __call = function(a)
local self = a[1]
local posix_s = self.__dict._is_win and self.__dict._path_str:gsub("\\", "/") or self.__dict._path_str
return self.__class.__name .. "(" .. Runtime.builtins.repr({posix_s}) .. ")"
end },
__hash__ = { __type = "builtin_method", __name = "__hash__", __call = function(a)
return Runtime.builtins.hash({a[1].__dict._norm_case})
end },
__eq__ = { __type = "builtin_method", __name = "__eq__", __call = function(a)
local self, other = a[1], a[2]
if type(other) ~= "table" or other.__type ~= "instance" or other.__dict._is_win == nil then
return Runtime.builtins.NotImplemented
end
if self.__dict._is_win ~= other.__dict._is_win then return false end
return self.__dict._norm_case == other.__dict._norm_case
end },
__ne__ = { __type = "builtin_method", __name = "__ne__", __call = function(a)
local self, other = a[1], a[2]
if type(other) ~= "table" or other.__type ~= "instance" or other.__dict._is_win == nil then
return Runtime.builtins.NotImplemented
end
if self.__dict._is_win ~= other.__dict._is_win then return true end
return self.__dict._norm_case ~= other.__dict._norm_case
end },
__lt__ = { __type = "builtin_method", __name = "__lt__", __call = function(a)
local self, other = a[1], a[2]
if type(other) ~= "table" or other.__type ~= "instance" or self.__dict._is_win ~= other.__dict._is_win then
Runtime.raise("TypeError", "'<' not supported between instances of '" .. self.__class.__name .. "' and '" .. (type(other) == "table" and other.__class and other.__class.__name or type(other)) .. "'")
end
return self.__dict._norm_case < other.__dict._norm_case
end },
__le__ = { __type = "builtin_method", __name = "__le__", __call = function(a)
local self, other = a[1], a[2]
if type(other) ~= "table" or other.__type ~= "instance" or self.__dict._is_win ~= other.__dict._is_win then
Runtime.raise("TypeError", "'<=' not supported between instances")
end
return self.__dict._norm_case <= other.__dict._norm_case
end },
__gt__ = { __type = "builtin_method", __name = "__gt__", __call = function(a)
local self, other = a[1], a[2]
if type(other) ~= "table" or other.__type ~= "instance" or self.__dict._is_win ~= other.__dict._is_win then
Runtime.raise("TypeError", "'>' not supported between instances")
end
return self.__dict._norm_case > other.__dict._norm_case
end },
__ge__ = { __type = "builtin_method", __name = "__ge__", __call = function(a)
local self, other = a[1], a[2]
if type(other) ~= "table" or other.__type ~= "instance" or self.__dict._is_win ~= other.__dict._is_win then
Runtime.raise("TypeError", "'>=' not supported between instances")
end
return self.__dict._norm_case >= other.__dict._norm_case
end },
__truediv__ = { __type = "builtin_method", __name = "__truediv__", __call = function(a)
local self, other = a[1], a[2]
local ok, other_str = pcall(S.extract_fspath, other)
if not ok then return Runtime.builtins.NotImplemented end
return S.construct_path(self.__class, { self.__dict._path_str, other_str }, self.__dict._is_win)
end },
__rtruediv__ = { __type = "builtin_method", __name = "__rtruediv__", __call = function(a)
local self, left = a[1], a[2]
local ok, left_str = pcall(S.extract_fspath, left)
if not ok then return Runtime.builtins.NotImplemented end
return S.construct_path(self.__class, { left_str, self.__dict._path_str }, self.__dict._is_win)
end },
-- 屬性 (Properties)
parts = make_prop(function(a)
local copy = {}
for i, v in ipairs(a[1].__dict._parts) do copy[i] = v end
return { __type = "tuple", elements = copy }
end),
drive = make_prop(function(a) return a[1].__dict._drive end),
root = make_prop(function(a) return a[1].__dict._root end),
anchor = make_prop(function(a) return a[1].__dict._drive .. a[1].__dict._root end),
name = make_prop(function(a) return S.get_name(a[1]) end),
suffix = make_prop(function(a) return S.get_suffix(a[1]) end),
suffixes = make_prop(function(a) return { __type = "list", elements = S.get_suffixes(a[1]) } end),
stem = make_prop(function(a) return S.get_stem(a[1]) end),
parent = make_prop(function(a)
local self = a[1]
local segs = self.__dict._segs
if #segs == 0 then return self end
local p_segs = {}
for i = 1, #segs - 1 do p_segs[i] = segs[i] end
return S.build_from_parsed(self.__class, self.__dict._drive, self.__dict._root, p_segs, self.__dict._is_win)
end),
parents = make_prop(function(a)
local self = a[1]
local segs = self.__dict._segs
local list = {}
for len = #segs - 1, 0, -1 do
local sub_segs = {}
for i = 1, len do sub_segs[i] = segs[i] end
table.insert(list, S.build_from_parsed(self.__class, self.__dict._drive, self.__dict._root, sub_segs, self.__dict._is_win))
end
return { __type = "tuple", elements = list }
end),
-- 純路徑方法 (PurePath Methods)
as_posix = { __type = "builtin_method", __name = "as_posix", __call = function(a)
local self = a[1]
if self.__dict._is_win then return (self.__dict._path_str:gsub("\\", "/")) end
return self.__dict._path_str
end },
is_absolute = { __type = "builtin_method", __name = "is_absolute", __call = function(a)
local self = a[1]
if self.__dict._is_win then return self.__dict._drive ~= "" and self.__dict._root ~= "" end
return self.__dict._root ~= ""
end },
as_uri = { __type = "builtin_method", __name = "as_uri", __call = function(a)
local self = a[1]
if not ((self.__dict._is_win and self.__dict._drive ~= "" and self.__dict._root ~= "") or (not self.__dict._is_win and self.__dict._root ~= "")) then
Runtime.raise("ValueError", "relative path can't be expressed as a file URI")
end
local posix = self.__dict._is_win and ("/" .. self.__dict._path_str:gsub("\\", "/")) or self.__dict._path_str
return "file://" .. posix
end },
is_reserved = { __type = "builtin_method", __name = "is_reserved", __call = function(a)
local self = a[1]
if not self.__dict._is_win then return false end
local stem = S.get_stem(self):upper()
local reserved = { CON=true, PRN=true, AUX=true, NUL=true, COM1=true, COM2=true, COM3=true, COM4=true, LPT1=true, LPT2=true, LPT3=true }
return reserved[stem] == true
end },
joinpath = { __type = "builtin_method", __name = "joinpath", __call = function(a)
local self = a[1]
local raw = { self.__dict._path_str }
for i = 2, #a do table.insert(raw, S.extract_fspath(a[i])) end
return S.construct_path(self.__class, raw, self.__dict._is_win)
end },
with_name = { __type = "builtin_method", __name = "with_name", __call = function(a)
local self, new_name = a[1], a[2]
if S.get_name(self) == "" then
Runtime.raise("ValueError", tostring(self.__dict._path_str) .. " has an empty name")
end
if type(new_name) ~= "string" then Runtime.raise("TypeError", "expected str") end
local has_sep = new_name:find("/", 1, true) or (self.__dict._is_win and new_name:find("\\", 1, true))
if new_name == "" or new_name == "." or new_name == ".." or has_sep then
Runtime.raise("ValueError", "Invalid name '" .. new_name .. "'")
end
local new_segs = {}
for i = 1, #self.__dict._segs - 1 do new_segs[i] = self.__dict._segs[i] end
table.insert(new_segs, new_name)
return S.build_from_parsed(self.__class, self.__dict._drive, self.__dict._root, new_segs, self.__dict._is_win)
end },
with_stem = { __type = "builtin_method", __name = "with_stem", __call = function(a)
local self, new_stem = a[1], a[2]
if type(new_stem) ~= "string" then Runtime.raise("TypeError", "expected str") end
local suf = S.get_suffix(self)
return C.PurePath.__dict.with_name.__call({ self, new_stem .. suf })
end },
with_suffix = { __type = "builtin_method", __name = "with_suffix", __call = function(a)
local self, new_suf = a[1], a[2]
if type(new_suf) ~= "string" then Runtime.raise("TypeError", "expected str") end
local has_sep = new_suf:find("/", 1, true) or (self.__dict._is_win and new_suf:find("\\", 1, true))
if has_sep or (new_suf ~= "" and (new_suf:sub(1, 1) ~= "." or new_suf == ".")) then
Runtime.raise("ValueError", "Invalid suffix '" .. new_suf .. "'")
end
if S.get_name(self) == "" then
Runtime.raise("ValueError", tostring(self.__dict._path_str) .. " has an empty name")
end
local stem = S.get_stem(self)
return C.PurePath.__dict.with_name.__call({ self, stem .. new_suf })
end },
relative_to = { __type = "builtin_method", __name = "relative_to", __call = function(a)
local self = a[1]
if #a < 2 then Runtime.raise("TypeError", "relative_to() takes at least one argument") end
local other_args = {}
for i = 2, #a do table.insert(other_args, a[i]) end
local other = S.construct_path(self.__class, other_args, self.__dict._is_win)
local is_win = self.__dict._is_win
local d1 = is_win and self.__dict._drive:lower() or self.__dict._drive
local d2 = is_win and other.__dict._drive:lower() or other.__dict._drive
local r1, r2 = self.__dict._root, other.__dict._root
local s1, s2 = self.__dict._segs, other.__dict._segs
local match = (d1 == d2) and (r1 == r2) and (#s1 >= #s2)
if match then
for i = 1, #s2 do
local seg1 = is_win and s1[i]:lower() or s1[i]
local seg2 = is_win and s2[i]:lower() or s2[i]
if seg1 ~= seg2 then match = false; break end
end
end
if not match then
Runtime.raise("ValueError", "'" .. self.__dict._path_str .. "' is not in the subpath of '" .. other.__dict._path_str .. "'")
end
local rel_segs = {}
for i = #s2 + 1, #s1 do table.insert(rel_segs, s1[i]) end
return S.build_from_parsed(self.__class, "", "", rel_segs, is_win)
end },
is_relative_to = { __type = "builtin_method", __name = "is_relative_to", __call = function(a)
local ok = pcall(C.PurePath.__dict.relative_to.__call, a)
return ok
end },
match = { __type = "builtin_method", __name = "match", __call = function(a)
local self, pat_raw = a[1], a[2]
local pat_str = S.extract_fspath(pat_raw)
if pat_str == "" then Runtime.raise("ValueError", "empty pattern") end
local is_win = self.__dict._is_win
local p_drive, p_root, p_segs = S.parse_segments({ pat_str }, is_win)
local p_parts = {}
if (p_drive .. p_root) ~= "" then table.insert(p_parts, p_drive .. p_root) end
for _, v in ipairs(p_segs) do table.insert(p_parts, v) end
local s_parts = self.__dict._parts
if #p_parts == 0 or #p_parts > #s_parts then return false end
if (p_drive ~= "" or p_root ~= "") and #p_parts ~= #s_parts then return false end
local offset = #s_parts - #p_parts
for i = 1, #p_parts do
local s_part = is_win and s_parts[offset + i]:lower() or s_parts[offset + i]
local lua_pat = S.glob_seg_to_lua(p_parts[i], is_win)
if not s_part:match(lua_pat) then return false end
end
return true
end }
}
C.PurePath = PyClass.make_class("PurePath", {}, pure_dict)
C.PurePosixPath = PyClass.make_class("PurePosixPath", { C.PurePath }, {})
C.PureWindowsPath = PyClass.make_class("PureWindowsPath", { C.PurePath }, {})
-- 具象 Path 方法字典 (結合 MediaWiki API)
local path_dict = {
__new__ = { __type = "staticmethod", __func = { __type = "builtin_method", __name = "__new__", __call = new_concrete_path } },
cwd = { __type = "classmethod", __func = { __type = "builtin_method", __name = "cwd", __call = function(a)
local ok, t = pcall(mw.title.getCurrentTitle)
local title_str = (ok and t and t.prefixedText) or "/"
return S.construct_path(a[1] == C.Path and C.PosixPath or a[1], { title_str }, false)
end } },
home = { __type = "classmethod", __func = { __type = "builtin_method", __name = "home", __call = function(a)
local ok, t = pcall(mw.title.getCurrentTitle)
local title_str = (ok and t and t.prefixedText) or "/"
return S.construct_path(a[1] == C.Path and C.PosixPath or a[1], { title_str }, false)
end } },
expanduser = { __type = "builtin_method", __name = "expanduser", __call = function(a) return a[1] end },
absolute = { __type = "builtin_method", __name = "absolute", __call = function(a) return a[1] end },
exists = { __type = "builtin_method", __name = "exists", __call = function(a)
local _, ex = S.resolve_mw_title(a[1], false)
return ex
end },
is_file = { __type = "builtin_method", __name = "is_file", __call = function(a)
local _, ex = S.resolve_mw_title(a[1], false)
return ex
end },
is_dir = { __type = "builtin_method", __name = "is_dir", __call = function(a)
local p = a[1].__dict._path_str
return p == "." or p == "/"
end },
is_symlink = { __type = "builtin_method", __name = "is_symlink", __call = function(a)
local t, ex = S.resolve_mw_title(a[1], false)
return ex and (t.isRedirect == true) or false
end },
is_mount = { __type = "builtin_method", __name = "is_mount", __call = function() return false end },
is_socket = { __type = "builtin_method", __name = "is_socket", __call = function() return false end },
is_fifo = { __type = "builtin_method", __name = "is_fifo", __call = function() return false end },
is_block_device = { __type = "builtin_method", __name = "is_block_device", __call = function() return false end },
is_char_device = { __type = "builtin_method", __name = "is_char_device", __call = function() return false end },
read_text = { __type = "builtin_method", __name = "read_text", __call = function(a)
local t = S.resolve_mw_title(a[1], true)
t = t.redirectTarget or t
return t:getContent() or ""
end },
read_bytes = { __type = "builtin_method", __name = "read_bytes", __call = function(a)
local t = S.resolve_mw_title(a[1], true)
t = t.redirectTarget or t
return { __type = "bytes", _data = t:getContent() or "" }
end },
open = { __type = "builtin_method", __name = "open", __call = function(a, kw)
local self = a[1]
local mode = (a[2] ~= nil and a[2]) or (kw and kw.mode) or "r"
if type(mode) ~= "string" then Runtime.raise("TypeError", "mode must be a string") end
if mode:find("[wax%+]") then S.readonly_guard() end
local is_bin = mode:find("b", 1, true) ~= nil
local t = S.resolve_mw_title(self, true)
t = t.redirectTarget or t
local content = t:getContent() or ""
return S.make_file_object(self.__dict._path_str, content, is_bin)
end },
resolve = { __type = "builtin_method", __name = "resolve", __call = function(a, kw)
local self = a[1]
local strict = (a[2] ~= nil and a[2]) or (kw and kw.strict) or false
strict = Runtime.builtins.bool.__call({strict})
local t, ex = S.resolve_mw_title(self, strict)
if ex and t then
local target = t.redirectTarget or t
if target and target.prefixedText then
return S.construct_path(self.__class, { target.prefixedText }, false)
end
end
return self
end },
readlink = { __type = "builtin_method", __name = "readlink", __call = function(a)
local self = a[1]
local t = S.resolve_mw_title(self, true)
if not t.isRedirect or not t.redirectTarget then
Runtime.raise("OSError", "[Errno 22] Invalid argument: '" .. self.__dict._path_str .. "' is not a redirect/symlink")
end
return S.construct_path(self.__class, { t.redirectTarget.prefixedText }, false)
end },
samefile = { __type = "builtin_method", __name = "samefile", __call = function(a)
local self, other = a[1], a[2]
local other_p = (type(other) == "table" and other.__type == "instance" and other.__dict._path_str)
and other or S.construct_path(self.__class, { S.extract_fspath(other) }, false)
local t1 = S.resolve_mw_title(self, true)
local t2 = S.resolve_mw_title(other_p, true)
t1 = t1.redirectTarget or t1
t2 = t2.redirectTarget or t2
return t1.prefixedText == t2.prefixedText
end },
stat = { __type = "builtin_method", __name = "stat", __call = function(a)
local self = a[1]
local t = S.resolve_mw_title(self, true)
local size = 0
if t.file and t.file.exists then
size = t.file.size or 0
else
local c = t:getContent()
size = c and #c or 0
end
local mtime = os.time()
local ok_f, frame = pcall(mw.getCurrentFrame)
if ok_f and frame then
local ok_ts, ts = pcall(function() return frame:callParserFunction("REVISIONTIMESTAMP", t.prefixedText) end)
if ok_ts and type(ts) == "string" and #ts >= 14 then
local y, mo, d, h, mi, sc = ts:match("^(%d%d%d%d)(%d%d)(%d%d)(%d%d)(%d%d)(%d%d)")
if y then
mtime = os.time({ year=tonumber(y), month=tonumber(mo), day=tonumber(d), hour=tonumber(h), min=tonumber(mi), sec=tonumber(sc) })
end
end
end
if not S.stat_class then
local st_dict = {
__repr__ = { __type = "builtin_method", __call = function(sa)
local d = sa[1].__dict
return string.format("os.stat_result(st_mode=%d, st_size=%d, st_mtime=%d)", d.st_mode, d.st_size, d.st_mtime)
end }
}
st_dict.__str__ = st_dict.__repr__
S.stat_class = PyClass.make_class("stat_result", {}, st_dict)
end
local st = PyClass.make_instance(S.stat_class)
st.__dict.st_mode = 33188
st.__dict.st_size = size
st.__dict.st_mtime = mtime
st.__dict.st_atime = mtime
st.__dict.st_ctime = mtime
return st
end },
lstat = { __type = "builtin_method", __name = "lstat", __call = function(a)
return C.Path.__dict.stat.__call(a)
end },
owner = { __type = "builtin_method", __name = "owner", __call = function() return "WikiUser" end },
group = { __type = "builtin_method", __name = "group", __call = function() return "MediaWiki" end },
-- 唯讀沙盒寫入防護與目錄列舉防護
write_text = { __type = "builtin_method", __name = "write_text", __call = S.readonly_guard },
write_bytes = { __type = "builtin_method", __name = "write_bytes", __call = S.readonly_guard },
mkdir = { __type = "builtin_method", __name = "mkdir", __call = S.readonly_guard },
rmdir = { __type = "builtin_method", __name = "rmdir", __call = S.readonly_guard },
unlink = { __type = "builtin_method", __name = "unlink", __call = S.readonly_guard },
rename = { __type = "builtin_method", __name = "rename", __call = S.readonly_guard },
replace = { __type = "builtin_method", __name = "replace", __call = S.readonly_guard },
touch = { __type = "builtin_method", __name = "touch", __call = S.readonly_guard },
chmod = { __type = "builtin_method", __name = "chmod", __call = S.readonly_guard },
lchmod = { __type = "builtin_method", __name = "lchmod", __call = S.readonly_guard },
symlink_to = { __type = "builtin_method", __name = "symlink_to", __call = S.readonly_guard },
hardlink_to = { __type = "builtin_method", __name = "hardlink_to", __call = S.readonly_guard },
link_to = { __type = "builtin_method", __name = "link_to", __call = S.readonly_guard },
iterdir = { __type = "builtin_method", __name = "iterdir", __call = S.glob_guard },
glob = { __type = "builtin_method", __name = "glob", __call = S.glob_guard },
rglob = { __type = "builtin_method", __name = "rglob", __call = S.glob_guard }
}
C.Path = PyClass.make_class("Path", { C.PurePath }, path_dict)
C.PosixPath = PyClass.make_class("PosixPath", { C.Path, C.PurePosixPath }, {})
C.WindowsPath = PyClass.make_class("WindowsPath", { C.Path, C.PureWindowsPath }, {})
for _, cls in pairs(C) do
cls.__dict["__module__"] = "pathlib"
end
S.classes = C
return C
end
function PathlibMod.get_module()
local mod_dict = {
__getattr__ = {
__type = "builtin_method",
__call = function(args)
local self, attr = args[1], args[2]
local C = S.ensure_classes()
if C[attr] then
self.__dict[attr] = C[attr]
return C[attr]
end
Runtime.raise("AttributeError", "module 'pathlib' has no attribute '" .. tostring(attr) .. "'")
end
}
}
local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
mod.__dict["__name__"] = "pathlib"
mod.__dict["__all__"] = {
__type = "list",
elements = { "PurePath", "PurePosixPath", "PureWindowsPath", "Path", "PosixPath", "WindowsPath" }
}
return mod
end
-- ==========================================
-- urllbi庫
-- ==========================================
local Urllib = {}
function Urllib.get_module(module_name, env)
local B = Runtime.builtins
local EvalUtils = require("Module:沙盒/a2569875/PythonScript/Evaluator/Utils")
-- ==========================================
-- 核心:支援 path-like object 與 bytes/string 的轉換
-- ==========================================
local function get_fspath(obj)
if type(obj) == "table" and obj.__type == "instance" then
local ok, fspath_m = pcall(PyClass.get_attr, obj, "__fspath__")
if ok and fspath_m and fspath_m.__type == "bound_method" then
local Evaluator = require("Module:沙盒/a2569875/PythonScript/Evaluator")
local res = Evaluator.invoke(fspath_m.__func, {}, {}, fspath_m.__self)
if type(res) == "table" and (res.__type == "bytes" or res.__type == "bytearray") then
return res._data
end
return require("Module:沙盒/a2569875/PythonScript/Runtime/Utils").to_str(res, false)
end
end
if type(obj) == "table" and (obj.__type == "bytes" or obj.__type == "bytearray") then
return obj._data
end
if type(obj) == "table" and obj.__type == "memoryview" then
if obj._released then Runtime.raise("ValueError", "operation forbidden on released memoryview object") end
return obj._data
end
if type(obj) == "string" then return obj end
Runtime.raise("TypeError", "expected string, bytes or os.PathLike object")
end
local function fn(name, func)
return { __type = "builtin_method", __name = name, __call = func }
end
-- ==========================================
-- urllib.error 模組
-- ==========================================
local URLError = PyClass.make_class("URLError", { B.OSError }, {})
URLError.__dict["__module__"] = "urllib.error"
local HTTPError = PyClass.make_class("HTTPError", { URLError }, {})
HTTPError.__dict["__module__"] = "urllib.error"
local error_mod = PyClass.make_instance(PyClass.make_class("module", {}, {}))
error_mod.__dict["__name__"] = "urllib.error"
error_mod.__dict.URLError = URLError
error_mod.__dict.HTTPError = HTTPError
-- ==========================================
-- urllib.parse 模組 (路徑與編碼處理核心)
-- ==========================================
local parse_dict = {}
parse_dict.quote = fn("quote", function(a, kw)
local s = get_fspath(a[1])
local safe = (a[2] ~= nil and a[2]) or (kw and kw.safe) or "/"
safe = get_fspath(safe)
local safe_pat = safe:gsub("([%%%^%$%(%)%.%[%]%*%+%-%?])", "%%%1")
local res = s:gsub("([^%w_~%.-" .. safe_pat .. "])", function(c)
return string.format("%%%02X", string.byte(c))
end)
return res
end)
parse_dict.unquote = fn("unquote", function(a)
local s = get_fspath(a[1])
local res = s:gsub("%%(%x%x)", function(hex)
return string.char(tonumber(hex, 16))
end)
return res
end)
parse_dict.quote_plus = fn("quote_plus", function(a, kw)
local s = get_fspath(a[1])
local safe = (a[2] ~= nil and a[2]) or (kw and kw.safe) or ""
safe = get_fspath(safe)
s = s:gsub(" ", "+")
local safe_pat = (safe .. "+"):gsub("([%%%^%$%(%)%.%[%]%*%+%-%?])", "%%%1")
local res = s:gsub("([^%w_~%.-" .. safe_pat .. "])", function(c)
return string.format("%%%02X", string.byte(c))
end)
return res
end)
parse_dict.unquote_plus = fn("unquote_plus", function(a)
local s = get_fspath(a[1])
s = s:gsub("%+", " ")
local res = s:gsub("%%(%x%x)", function(hex)
return string.char(tonumber(hex, 16))
end)
return res
end)
local function do_urlsplit(url, scheme, allow_fragments)
local fragment, query, sch, netloc, path = "", "", "", "", ""
if allow_fragments then
local h = url:find("#", 1, true)
if h then fragment = url:sub(h+1); url = url:sub(1, h-1) end
end
local q = url:find("?", 1, true)
if q then query = url:sub(q+1); url = url:sub(1, q-1) end
local c = url:find(":", 1, true)
if c then
local prefix = url:sub(1, c-1)
if prefix:match("^[a-zA-Z][a-zA-Z0-9%+%-%.]*$") then
sch = prefix:lower(); url = url:sub(c+1)
end
end
if sch == "" then sch = scheme or "" end
if url:sub(1,2) == "//" then
local s = url:find("/", 3, true)
if s then netloc = url:sub(3, s-1); path = url:sub(s)
else netloc = url:sub(3); path = "" end
else path = url end
return sch, netloc, path, query, fragment
end
local ParseResult_cls = PyClass.make_class("ParseResult", { B.tuple }, {
__repr__ = { __type = "builtin_method", __call = function(a)
local elts = a[1].__builtin_value.elements
return string.format("ParseResult(scheme='%s', netloc='%s', path='%s', params='%s', query='%s', fragment='%s')", elts[1], elts[2], elts[3], elts[4], elts[5], elts[6])
end}
})
local SplitResult_cls = PyClass.make_class("SplitResult", { B.tuple }, {
__repr__ = { __type = "builtin_method", __call = function(a)
local elts = a[1].__builtin_value.elements
return string.format("SplitResult(scheme='%s', netloc='%s', path='%s', query='%s', fragment='%s')", elts[1], elts[2], elts[3], elts[4], elts[5])
end}
})
parse_dict.urlsplit = fn("urlsplit", function(a, kw)
local url = get_fspath(a[1])
local scheme = (a[2] ~= nil and a[2]) or (kw and kw.scheme) or ""
local allow_fragments = true
if a[3] ~= nil then allow_fragments = EvalUtils.is_truthy(a[3])
elseif kw and kw.allow_fragments ~= nil then allow_fragments = EvalUtils.is_truthy(kw.allow_fragments) end
local sch, netloc, path, query, fragment = do_urlsplit(url, get_fspath(scheme), allow_fragments)
local inst = PyClass.make_instance(SplitResult_cls)
inst.__builtin_value = { __type = "tuple", elements = {sch, netloc, path, query, fragment} }
return inst
end)
parse_dict.urlparse = fn("urlparse", function(a, kw)
local url = get_fspath(a[1])
local scheme = (a[2] ~= nil and a[2]) or (kw and kw.scheme) or ""
local allow_fragments = true
if a[3] ~= nil then allow_fragments = EvalUtils.is_truthy(a[3])
elseif kw and kw.allow_fragments ~= nil then allow_fragments = EvalUtils.is_truthy(kw.allow_fragments) end
local sch, netloc, path, query, fragment = do_urlsplit(url, get_fspath(scheme), allow_fragments)
local params = ""
local semi_pos = path:find(";", 1, true)
if semi_pos then params = path:sub(semi_pos + 1); path = path:sub(1, semi_pos - 1) end
local inst = PyClass.make_instance(ParseResult_cls)
inst.__builtin_value = { __type = "tuple", elements = {sch, netloc, path, params, query, fragment} }
return inst
end)
parse_dict.urlunsplit = fn("urlunsplit", function(a)
local parts = a[1]
local elts = parts.elements or parts.__builtin_value.elements
local sch, netloc, path, query, fragment = elts[1], elts[2], elts[3], elts[4], elts[5]
local url = ""
if netloc ~= "" or (sch ~= "" and path:sub(1, 2) == "//") then
if path ~= "" and path:sub(1, 1) ~= "/" then path = "/" .. path end
url = "//" .. (netloc or "") .. path
else
url = path
end
if sch ~= "" then url = sch .. ":" .. url end
if query ~= "" then url = url .. "?" .. query end
if fragment ~= "" then url = url .. "#" .. fragment end
return url
end)
parse_dict.urlunparse = fn("urlunparse", function(a)
local parts = a[1]
local elts = parts.elements or parts.__builtin_value.elements
local sch, netloc, path, params, query, fragment = elts[1], elts[2], elts[3], elts[4], elts[5], elts[6]
if params ~= "" then path = path .. ";" .. params end
return parse_dict.urlunsplit.__call({ { __type = "tuple", elements = { sch, netloc, path, query, fragment } } })
end)
parse_dict.urljoin = fn("urljoin", function(a, kw)
local base = get_fspath(a[1])
local url = get_fspath(a[2])
local allow_fragments = true
if a[3] ~= nil then allow_fragments = EvalUtils.is_truthy(a[3])
elseif kw and kw.allow_fragments ~= nil then allow_fragments = EvalUtils.is_truthy(kw.allow_fragments) end
local b_sch, b_netloc, b_path, b_query, b_frag = do_urlsplit(base, "", allow_fragments)
local u_sch, u_netloc, u_path, u_query, u_frag = do_urlsplit(url, "", allow_fragments)
if u_sch ~= "" then return url end
if u_netloc ~= "" then return parse_dict.urlunsplit.__call({ {__type="tuple", elements={b_sch, u_netloc, u_path, u_query, u_frag}} }) end
if u_path == "" then
local res_q = (u_query == "") and b_query or u_query
return parse_dict.urlunsplit.__call({ {__type="tuple", elements={b_sch, b_netloc, b_path, res_q, u_frag}} })
end
if u_path:sub(1, 1) == "/" then
return parse_dict.urlunsplit.__call({ {__type="tuple", elements={b_sch, b_netloc, u_path, u_query, u_frag}} })
end
local p = b_path
local last_slash = p:match(".*()/")
p = (last_slash and p:sub(1, last_slash) or "") .. u_path
local segments = {}
for seg in p:gmatch("[^/]+") do
if seg == ".." then
if #segments > 0 and segments[#segments] ~= ".." then table.remove(segments)
else table.insert(segments, "..") end
elseif seg ~= "." then
table.insert(segments, seg)
end
end
local res_path = (p:sub(1, 1) == "/" and "/" or "") .. table.concat(segments, "/")
if p:sub(-1) == "/" and res_path:sub(-1) ~= "/" then res_path = res_path .. "/" end
return parse_dict.urlunsplit.__call({ {__type="tuple", elements={b_sch, b_netloc, res_path, u_query, u_frag}} })
end)
local parse_mod = PyClass.make_instance(PyClass.make_class("module", {}, {}))
parse_mod.__dict["__name__"] = "urllib.parse"
for k, v in pairs(parse_dict) do parse_mod.__dict[k] = v end
-- ==========================================
-- urllib.request 模組 (路徑轉譯與網路禁止防護)
-- ==========================================
local req_dict = {}
req_dict.pathname2url = fn("pathname2url", function(a)
local p = get_fspath(a[1])
p = p:gsub("\\", "/")
return parse_dict.quote.__call({ p })
end)
req_dict.url2pathname = fn("url2pathname", function(a)
local p = get_fspath(a[1])
return parse_dict.unquote.__call({ p })
end)
-- 封鎖所有實體網路操作,保留介面拋出例外
local function block_network(name)
return fn(name, function()
Runtime.raise("NotImplementedError", "Network operations (" .. name .. ") are disabled in this sandboxed environment.")
end)
end
req_dict.urlopen = block_network("urlopen")
req_dict.urlretrieve = block_network("urlretrieve")
req_dict.Request = PyClass.make_class("Request", {}, {
__init__ = { __type = "builtin_method", __call = function()
Runtime.raise("NotImplementedError", "Network requests are disabled in this sandboxed environment.")
end}
})
local request_mod = PyClass.make_instance(PyClass.make_class("module", {}, {}))
request_mod.__dict["__name__"] = "urllib.request"
for k, v in pairs(req_dict) do request_mod.__dict[k] = v end
-- ==========================================
-- 註冊並匯出 Package
-- ==========================================
local urllib_mod = PyClass.make_instance(PyClass.make_class("module", {}, {}))
urllib_mod.__dict["__name__"] = "urllib"
urllib_mod.__dict.parse = parse_mod
urllib_mod.__dict.request = request_mod
urllib_mod.__dict.error = error_mod
-- 寫入 sys.modules,確保 from urllib import parse 能無縫運作
local Sys = require("Module:沙盒/a2569875/PythonScript/Librarys/Argparse").Sys
Sys.modules_registry["urllib.parse"] = parse_mod
Sys.modules_registry["urllib.request"] = request_mod
Sys.modules_registry["urllib.error"] = error_mod
if type(env) == type({"table"}) and (module_name == "urllib.parse" or module_name == "urllib.error" or module_name == "urllib.error") then
env["urllib"] = env["urllib"] or urllib_mod
end
if module_name == "urllib.parse" then return parse_mod end
if module_name == "urllib.request" then return request_mod end
if module_name == "urllib.error" then return error_mod end
return urllib_mod
end
PathlibMod.Urllib = Urllib
return PathlibMod