跳转到内容

模組:沙盒/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