1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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,
}
|