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
132
133
134
135
136
137
138
139
|
-- 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
-- Windows should just adapt reasonable path conventions like every other operating system.
-- But some people like to do their own thing, so we need lots of special casing here.
local windows_style_paths = os.type ~= 'unix'
-- 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 = windows_style_paths 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 = windows_style_paths and separator * separator * l.Cg(l.P(1)^0 * -1, 'unc') or l.P(false)
local drive_letter = windows_style_paths and l.Cg(l.R('az', 'AZ') * ':', 'drive') or l.P(false)
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
if windows_style_paths then
tree = tree_root[components.drive or tree[path_components].drive]
else
tree = tree_root
end
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_components, 1, depth, 1, newtable(depth + 1, 1))
components.drive, components[depth + 1] = parent_components.drive, 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 = windows_style_paths and setmetatable({}, {__index = function(t, drive)
local root_dir = build_root_dir(drive)
t[drive] = root_dir
return root_dir
end}) or build_root_dir()
local function resolve_path_to_tree(path)
local splitted = split_path(path)
if splitted[1] == '' and (not windows_style_paths or splitted.drive) 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,
}
|