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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
local queries = {}
local vers = require("luarocks.core.vers")
local util = require("luarocks.util")
local cfg = require("luarocks.core.cfg")
local query_mt = {}
query_mt.__index = query_mt
function query_mt.type()
return "query"
end
-- Fallback default value for the `arch` field, if not explicitly set.
query_mt.arch = {
src = true,
all = true,
rockspec = true,
installed = true,
-- [cfg.arch] = true, -- this is set later
}
-- Fallback default value for the `substring` field, if not explicitly set.
query_mt.substring = false
--- Convert the arch field of a query table to table format.
-- @param input string, table or nil
local function arch_to_table(input)
if type(input) == "table" then
return input
elseif type(input) == "string" then
local arch = {}
for a in input:gmatch("[%w_-]+") do
arch[a] = true
end
return arch
end
end
--- Prepare a query in dependency table format.
-- @param name string: the package name.
-- @param namespace string?: the package namespace.
-- @param version string?: the package version.
-- @param substring boolean?: match substrings of the name
-- (default is false, match full name)
-- @param arch string?: a string with pipe-separated accepted arch values
-- @param operator string?: operator for version matching (default is "==")
-- @return table: A query in table format
function queries.new(name, namespace, version, substring, arch, operator)
assert(type(name) == "string")
assert(type(namespace) == "string" or not namespace)
assert(type(version) == "string" or not version)
assert(type(substring) == "boolean" or not substring)
assert(type(arch) == "string" or not arch)
assert(type(operator) == "string" or not operator)
operator = operator or "=="
local self = {
name = name,
namespace = namespace,
constraints = {},
substring = substring,
arch = arch_to_table(arch),
}
if version then
table.insert(self.constraints, { op = operator, version = vers.parse_version(version)})
end
query_mt.arch[cfg.arch] = true
return setmetatable(self, query_mt)
end
-- Query for all packages
-- @param arch string (optional)
function queries.all(arch)
assert(type(arch) == "string" or not arch)
return queries.new("", nil, nil, true, arch)
end
do
local parse_constraints
do
local parse_constraint
do
local operators = {
["=="] = "==",
["~="] = "~=",
[">"] = ">",
["<"] = "<",
[">="] = ">=",
["<="] = "<=",
["~>"] = "~>",
-- plus some convenience translations
[""] = "==",
["="] = "==",
["!="] = "~="
}
--- Consumes a constraint from a string, converting it to table format.
-- For example, a string ">= 1.0, > 2.0" is converted to a table in the
-- format {op = ">=", version={1,0}} and the rest, "> 2.0", is returned
-- back to the caller.
-- @param input string: A list of constraints in string format.
-- @return (table, string) or nil: A table representing the same
-- constraints and the string with the unused input, or nil if the
-- input string is invalid.
parse_constraint = function(input)
assert(type(input) == "string")
local no_upgrade, op, version, rest = input:match("^(@?)([<>=~!]*)%s*([%w%.%_%-]+)[%s,]*(.*)")
local _op = operators[op]
version = vers.parse_version(version)
if not _op then
return nil, "Encountered bad constraint operator: '"..tostring(op).."' in '"..input.."'"
end
if not version then
return nil, "Could not parse version from constraint: '"..input.."'"
end
return { op = _op, version = version, no_upgrade = no_upgrade=="@" and true or nil }, rest
end
end
--- Convert a list of constraints from string to table format.
-- For example, a string ">= 1.0, < 2.0" is converted to a table in the format
-- {{op = ">=", version={1,0}}, {op = "<", version={2,0}}}.
-- Version tables use a metatable allowing later comparison through
-- relational operators.
-- @param input string: A list of constraints in string format.
-- @return table or nil: A table representing the same constraints,
-- or nil if the input string is invalid.
parse_constraints = function(input)
assert(type(input) == "string")
local constraints, oinput, constraint = {}, input
while #input > 0 do
constraint, input = parse_constraint(input)
if constraint then
table.insert(constraints, constraint)
else
return nil, "Failed to parse constraint '"..tostring(oinput).."' with error: ".. input
end
end
return constraints
end
end
--- Prepare a query in dependency table format.
-- @param depstr string: A dependency in string format
-- as entered in rockspec files.
-- @return table: A query in table format, or nil and an error message in case of errors.
function queries.from_dep_string(depstr)
assert(type(depstr) == "string")
local ns_name, rest = depstr:match("^%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)")
if not ns_name then
return nil, "failed to extract dependency name from '"..depstr.."'"
end
ns_name = ns_name:lower()
local constraints, err = parse_constraints(rest)
if not constraints then
return nil, err
end
local name, namespace = util.split_namespace(ns_name)
local self = {
name = name,
namespace = namespace,
constraints = constraints,
}
query_mt.arch[cfg.arch] = true
return setmetatable(self, query_mt)
end
end
function queries.from_persisted_table(tbl)
query_mt.arch[cfg.arch] = true
return setmetatable(tbl, query_mt)
end
--- Build a string representation of a query package name.
-- Includes namespace, name and version, but not arch or constraints.
-- @param query table: a query table
-- @return string: a result such as `my_user/my_rock 1.0` or `my_rock`.
function query_mt:__tostring()
local out = {}
if self.namespace then
table.insert(out, self.namespace)
table.insert(out, "/")
end
table.insert(out, self.name)
if #self.constraints > 0 then
local pretty = {}
for _, c in ipairs(self.constraints) do
local v = c.version.string
if c.op == "==" then
table.insert(pretty, v)
else
table.insert(pretty, c.op .. " " .. v)
end
end
table.insert(out, " ")
table.insert(out, table.concat(pretty, ", "))
end
return table.concat(out)
end
return queries
|