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
|
local _ = require "mason-core.functional"
local fs = require "mason-core.fs"
---@param str string
---@param char string
local function split_once_left(str, char)
for i = 1, #str do
if str:sub(i, i) == char then
local segment = str:sub(1, i - 1)
return segment, str:sub(i + 1)
end
end
return str
end
---@param lockfile Lockfile
local function validate(lockfile)
assert(lockfile.header and lockfile.header.version, "Header and version missing.")
assert(lockfile.header.version == "1", "Unknown lockfile version.")
for pkg_name, metadata in pairs(lockfile.body) do
assert(metadata.version, pkg_name .. " is missing version field.")
assert(metadata.registry, pkg_name .. " is missing registry field.")
local registry = metadata.registry
if registry.proto == "github" then
assert(registry.integrity, pkg_name .. " is missing registry.integrity field.")
assert(registry.name, pkg_name .. " is missing registry.name field.")
assert(registry.namespace, pkg_name .. " is missing registry.namespace field.")
elseif registry.proto == "file" then
assert(registry.path, pkg_name .. " is missing registry.path field.")
elseif registry.proto == "lua" then
assert(registry.mod, pkg_name .. " is missing registry.mod field.")
else
error "Unknown registry protocol."
end
end
end
---@param contents string
local function parse(contents)
local header = nil
local body = {}
local cursor = { body }
local lines = _.split("\n", contents)
for line_no, line in ipairs(lines) do
local indentation = #line:match "^%s*"
local indent_level = indentation / 2
local current_indent_level = (#cursor - 1)
if math.fmod(indentation, 2) ~= 0 or indent_level > current_indent_level then
error(("Invalid indentation on line %s."):format(line_no))
end
if _.matches("^%s*$", line) then
-- empty line
elseif _.matches("^%s*#", line) then
-- comment
elseif _.matches("^---$", line) then
-- header
assert(header == nil, ("Duplicate headers in document on line %s."):format(line_no))
header = body
body = {}
cursor = { body }
else
if indent_level < current_indent_level then
cursor = _.take(indent_level + 1, cursor)
end
local key, val = split_once_left(line:sub(indentation + 1), " ")
if val then
cursor[#cursor][key] = val
else
cursor[#cursor][key] = {}
cursor[#cursor + 1] = cursor[#cursor][key]
end
end
end
---@type Lockfile
local lockfile = {
header = header,
body = body,
}
validate(lockfile)
return lockfile
end
---@param file string
local function deserialize_file(file)
return parse(fs.sync.read_file(file))
end
---@param contents string
local function deserialize(contents)
return parse(contents)
end
---@param lockfile Lockfile
local function to_file(lockfile)
validate(lockfile)
local output = {
"# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
}
local serialize
serialize = _.curryN(function(depth, key, val)
if type(val) == "string" then
return ("%s%s %s"):format((" "):rep(depth), key, val)
else
assert(type(val) == "table", "Unknown value type.")
return {
(" "):rep(depth) .. key,
_.compose(_.map(_.apply(serialize(depth + 1))), _.sort_by(_.head), _.to_pairs)(val),
}
end
end, 3)
-- Header
for _, key in ipairs(_.sort_by(_.identity, _.keys(lockfile.header))) do
output[#output + 1] = serialize(0, key, lockfile.header[key])
end
output[#output + 1] = { "", "---", "" }
-- Body
for _, key in ipairs(_.sort_by(_.identity, _.keys(lockfile.body))) do
output[#output + 1] = serialize(0, key, lockfile.body[key])
output[#output + 1] = { "" }
end
return _.join("\n", _.flatten(output))
end
return {
deserialize = deserialize,
deserialize_file = deserialize_file,
validate = validate,
serialize = to_file,
}
|