diff options
Diffstat (limited to 'Master/texmf-dist/tex/luatex/luaotfload/luaotfload-realpath.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaotfload/luaotfload-realpath.lua | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/luatex/luaotfload/luaotfload-realpath.lua b/Master/texmf-dist/tex/luatex/luaotfload/luaotfload-realpath.lua new file mode 100644 index 00000000000..2738da8a1b2 --- /dev/null +++ b/Master/texmf-dist/tex/luatex/luaotfload/luaotfload-realpath.lua @@ -0,0 +1,131 @@ +-- Helper to resolve symlinks in paths + +local sub = string.sub +local symlinkattributes = lfs.symlinkattributes +local attributes = lfs.attributes +local currentdir = lfs.currentdir +local concat = table.concat +local move = table.move +local newtable = lua.newtable +local setmetatable = setmetatable + +-- Marker key for elements of result_tree to indicate the path components entry and the file mode +local path_components, file_mode = {}, {} +local tree_root + +local split_path do + local l = lpeg + local separator = os.type == 'unix' and l.S'/' or l.S'/\\' + -- We do not allow empty segments here because they get ignored. + local segment = l.C((1 - separator)^1) + -- Duplicate and trailing separators are dropped. + local unc = os.type == 'unix' and l.P(false) or separator * separator * l.Cg(l.P(1)^0 * -1, 'unc') + local drive_letter = os.type == 'unix' and l.P(false) or l.Cg(l.R('az', 'AZ') * ':', 'drive') + local path_pat = l.Ct(unc + drive_letter^-1 * (l.Cc'' * separator^1)^-1 * (segment * separator^1)^0 * segment^-1 * -1) + function split_path(path) + local splitted = path_pat:match(path) + if not splitted then + error'Invalid path rejected' + elseif splitted.unc then + error'Unsupported UNC path encountered' + elseif splitted.drive and splitted[1] ~= '' then + error'Unsupported relative path with drive letter encountered' + end + return splitted + end +end + +local function recombine_path(components) + local joined = concat(components, '/') + if components.drive then + joined = components.drive .. joined + end + return joined +end + +local function lookup_split_path_in_tree(components, tree) + if components[1] == '' then + tree = tree_root + end + for i=1, #components do + local next_tree = tree[components[i]] + if not next_tree then + return nil, string.format("Unable to find %q in %q", components[i], recombine_path(tree[path_components])) + end + tree = next_tree + end + return tree +end + +local tree_meta +tree_meta = { + __index = function(parent, component) + local parent_components = parent[path_components] + local depth = #parent_components + local components = move(parent[path_components], 1, depth, 1, newtable(depth + 1, 0)) + components[depth + 1] = component + local path = recombine_path(components) + + local mode = symlinkattributes(path, 'mode') + if not mode then + parent[component] = false + return false + end + if mode == 'link' then + local target = symlinkattributes(path, 'target') + local splitted_target = split_path(target) + local target_tree = lookup_split_path_in_tree(splitted_target, parent) or false + parent[component] = target_tree + return target_tree + end + + local child = { + [path_components] = components, + [file_mode] = mode, + } + if mode == 'directory' then + setmetatable(child, tree_meta) + child['.'] = child + child['..'] = parent + end + parent[component] = child + return child + end, +} + +-- We assume that the directory structure does not change during our run. +function build_root_dir(drive) + local root_dir = setmetatable({ + [path_components] = {'', drive = drive}, + [file_mode] = 'directory', -- "If [your root is not a directory] you are having a bad problem and you will not go to space today". + }, tree_meta) + root_dir['.'] = root_dir + root_dir['..'] = root_dir + return root_dir +end +tree_root = os.type == 'unix' and build_root_dir() or setmetatable({}, {__index = function(t, drive) + local root_dir = build_root_dir(drive) + t[drive] = root_dir + return root_dir +end}) + +local function resolve_path_to_tree(path) + local splitted = split_path(path) + if splitted[1] == '' then -- Optimization to avoid currentdir lookup. + return lookup_split_path_in_tree(splitted, tree_root) + else + local splitted_currentdir = split_path(currentdir()) + local current_tree = assert(lookup_split_path_in_tree(splitted_currentdir, tree_root)) + return lookup_split_path_in_tree(splitted, current_tree) + end +end + +local function resolve_path(path) + local tree, err = resolve_path_to_tree(path) + if not tree then return tree, err end + return recombine_path(tree[path_components]), tree[file_mode] +end + +return { + realpath = resolve_path, +} |