diff options
-rw-r--r-- | src/luarocks/build/builtin.lua | 104 | ||||
-rw-r--r-- | src/luarocks/fs/lua.lua | 100 | ||||
-rw-r--r-- | src/luarocks/search.lua | 8 |
3 files changed, 116 insertions, 96 deletions
diff --git a/src/luarocks/build/builtin.lua b/src/luarocks/build/builtin.lua index 7c24340e..7b550bff 100644 --- a/src/luarocks/build/builtin.lua +++ b/src/luarocks/build/builtin.lua | |||
@@ -19,25 +19,25 @@ end | |||
19 | --- Makes an RC file with an embedded Lua script, for building .exes on Windows | 19 | --- Makes an RC file with an embedded Lua script, for building .exes on Windows |
20 | -- @return nil if could open files, error otherwise | 20 | -- @return nil if could open files, error otherwise |
21 | local function make_rc(luafilename, rcfilename) | 21 | local function make_rc(luafilename, rcfilename) |
22 | local rcfile = io.open(rcfilename, "w") | 22 | local rcfile = io.open(rcfilename, "w") |
23 | if not rcfile then | 23 | if not rcfile then |
24 | error("Could not open "..rcfilename.." for writing.") | 24 | error("Could not open "..rcfilename.." for writing.") |
25 | end | 25 | end |
26 | rcfile:write("STRINGTABLE\r\nBEGIN\r\n") | 26 | rcfile:write("STRINGTABLE\r\nBEGIN\r\n") |
27 | 27 | ||
28 | local i = 1 | 28 | local i = 1 |
29 | for line in io.lines(luafilename) do | 29 | for line in io.lines(luafilename) do |
30 | if not line:match("^#!") then | 30 | if not line:match("^#!") then |
31 | rcfile:write(i .. " \"") | 31 | rcfile:write(i .. " \"") |
32 | line = line:gsub("\\", "\\\\"):gsub('"', '""'):gsub("[\r\n]+", "") | 32 | line = line:gsub("\\", "\\\\"):gsub('"', '""'):gsub("[\r\n]+", "") |
33 | rcfile:write(line .. "\\r\\n\"\r\n") | 33 | rcfile:write(line .. "\\r\\n\"\r\n") |
34 | i = i + 1 | 34 | i = i + 1 |
35 | end | 35 | end |
36 | end | 36 | end |
37 | 37 | ||
38 | rcfile:write("END\r\n") | 38 | rcfile:write("END\r\n") |
39 | 39 | ||
40 | rcfile:close() | 40 | rcfile:close() |
41 | end | 41 | end |
42 | 42 | ||
43 | --- Driver function for the builtin build back-end. | 43 | --- Driver function for the builtin build back-end. |
@@ -74,23 +74,23 @@ function run(rockspec) | |||
74 | local extras = { unpack(objects) } | 74 | local extras = { unpack(objects) } |
75 | add_flags(extras, "-L%s", libdirs) | 75 | add_flags(extras, "-L%s", libdirs) |
76 | add_flags(extras, "%s.lib", libraries) | 76 | add_flags(extras, "%s.lib", libraries) |
77 | extras[#extras+1] = dir.path(variables.LUA_LIBDIR, "lua5.1.lib") | 77 | extras[#extras+1] = dir.path(variables.LUA_LIBDIR, "lua5.1.lib") |
78 | extras[#extras+1] = "-l" .. (variables.MSVCRT or "msvcr80") | 78 | extras[#extras+1] = "-l" .. (variables.MSVCRT or "msvcr80") |
79 | local ok = execute(variables.LD.." "..variables.LIBFLAG, "-o", library, unpack(extras)) | 79 | local ok = execute(variables.LD.." "..variables.LIBFLAG, "-o", library, unpack(extras)) |
80 | return ok | 80 | return ok |
81 | end | 81 | end |
82 | compile_wrapper_binary = function(fullname, name) | 82 | compile_wrapper_binary = function(fullname, name) |
83 | local fullbasename = fullname:gsub("%.lua$", ""):gsub("/", "\\") | 83 | local fullbasename = fullname:gsub("%.lua$", ""):gsub("/", "\\") |
84 | local basename = name:gsub("%.lua$", ""):gsub("/", "\\") | 84 | local basename = name:gsub("%.lua$", ""):gsub("/", "\\") |
85 | local rcname = basename..".rc" | 85 | local rcname = basename..".rc" |
86 | local resname = basename..".o" | 86 | local resname = basename..".o" |
87 | local wrapname = basename..".exe" | 87 | local wrapname = basename..".exe" |
88 | make_rc(fullname, fullbasename..".rc") | 88 | make_rc(fullname, fullbasename..".rc") |
89 | local ok = execute(variables.RC, "-o", resname, rcname) | 89 | local ok = execute(variables.RC, "-o", resname, rcname) |
90 | if not ok then return ok end | 90 | if not ok then return ok end |
91 | ok = execute(variables.LD, "-o", wrapname, resname, variables.WRAPPER, | 91 | ok = execute(variables.LD, "-o", wrapname, resname, variables.WRAPPER, |
92 | dir.path(variables.LUA_LIBDIR, "lua5.1.lib"), "-l" .. (variables.MSVCRT or "msvcr80"), "-luser32") | 92 | dir.path(variables.LUA_LIBDIR, "lua5.1.lib"), "-l" .. (variables.MSVCRT or "msvcr80"), "-luser32") |
93 | return ok, wrapname | 93 | return ok, wrapname |
94 | end | 94 | end |
95 | elseif cfg.is_platform("win32") then | 95 | elseif cfg.is_platform("win32") then |
96 | compile_object = function(object, source, defines, incdirs) | 96 | compile_object = function(object, source, defines, incdirs) |
@@ -117,21 +117,21 @@ function run(rockspec) | |||
117 | return ok | 117 | return ok |
118 | end | 118 | end |
119 | compile_wrapper_binary = function(fullname, name) | 119 | compile_wrapper_binary = function(fullname, name) |
120 | local fullbasename = fullname:gsub("%.lua$", ""):gsub("/", "\\") | 120 | local fullbasename = fullname:gsub("%.lua$", ""):gsub("/", "\\") |
121 | local basename = name:gsub("%.lua$", ""):gsub("/", "\\") | 121 | local basename = name:gsub("%.lua$", ""):gsub("/", "\\") |
122 | local rcname = basename..".rc" | 122 | local rcname = basename..".rc" |
123 | local resname = basename..".res" | 123 | local resname = basename..".res" |
124 | local wrapname = basename..".exe" | 124 | local wrapname = basename..".exe" |
125 | make_rc(fullname, fullbasename..".rc") | 125 | make_rc(fullname, fullbasename..".rc") |
126 | local ok = execute(variables.RC, "-r", "-fo"..resname, rcname) | 126 | local ok = execute(variables.RC, "-r", "-fo"..resname, rcname) |
127 | if not ok then return ok end | 127 | if not ok then return ok end |
128 | ok = execute(variables.LD, "-out:"..wrapname, resname, variables.WRAPPER, | 128 | ok = execute(variables.LD, "-out:"..wrapname, resname, variables.WRAPPER, |
129 | dir.path(variables.LUA_LIBDIR, "lua5.1.lib"), "user32.lib") | 129 | dir.path(variables.LUA_LIBDIR, "lua5.1.lib"), "user32.lib") |
130 | local manifestfile = wrapname..".manifest" | 130 | local manifestfile = wrapname..".manifest" |
131 | if ok and fs.exists(manifestfile) then | 131 | if ok and fs.exists(manifestfile) then |
132 | ok = execute(variables.MT, "-manifest", manifestfile, "-outputresource:"..wrapname..";1") | 132 | ok = execute(variables.MT, "-manifest", manifestfile, "-outputresource:"..wrapname..";1") |
133 | end | 133 | end |
134 | return ok, wrapname | 134 | return ok, wrapname |
135 | end | 135 | end |
136 | else | 136 | else |
137 | compile_object = function(object, source, defines, incdirs) | 137 | compile_object = function(object, source, defines, incdirs) |
@@ -168,16 +168,16 @@ function run(rockspec) | |||
168 | local basename = name:gsub("%.lua$", "") | 168 | local basename = name:gsub("%.lua$", "") |
169 | local file | 169 | local file |
170 | if not match then | 170 | if not match then |
171 | file = io.open(fullname) | 171 | file = io.open(fullname) |
172 | end | 172 | end |
173 | if match or (file and file:read():match("#!.*lua.*")) then | 173 | if match or (file and file:read():match("#!.*lua.*")) then |
174 | ok, name = compile_wrapper_binary(fullname, name) | 174 | ok, name = compile_wrapper_binary(fullname, name) |
175 | if ok then | 175 | if ok then |
176 | build.install.bin[i] = name | 176 | build.install.bin[i] = name |
177 | else | 177 | else |
178 | if file then file:close() end | 178 | if file then file:close() end |
179 | return nil, "Build error in wrapper binaries" | 179 | return nil, "Build error in wrapper binaries" |
180 | end | 180 | end |
181 | end | 181 | end |
182 | if file then file:close() end | 182 | if file then file:close() end |
183 | end | 183 | end |
diff --git a/src/luarocks/fs/lua.lua b/src/luarocks/fs/lua.lua index 8d29f109..e055f67e 100644 --- a/src/luarocks/fs/lua.lua +++ b/src/luarocks/fs/lua.lua | |||
@@ -38,6 +38,10 @@ function Q(arg) | |||
38 | return "'" .. arg:gsub("\\", "\\\\"):gsub("'", "'\\''") .. "'" | 38 | return "'" .. arg:gsub("\\", "\\\\"):gsub("'", "'\\''") .. "'" |
39 | end | 39 | end |
40 | 40 | ||
41 | local function normalize(name) | ||
42 | return name:gsub("\\", "/"):gsub("/$", "") | ||
43 | end | ||
44 | |||
41 | --- Test is file/dir is writable. | 45 | --- Test is file/dir is writable. |
42 | -- Warning: testing if a file/dir is writable does not guarantee | 46 | -- Warning: testing if a file/dir is writable does not guarantee |
43 | -- that it will remain writable and therefore it is no replacement | 47 | -- that it will remain writable and therefore it is no replacement |
@@ -46,9 +50,10 @@ end | |||
46 | -- @return boolean: true if file exists, false otherwise. | 50 | -- @return boolean: true if file exists, false otherwise. |
47 | function is_writable(file) | 51 | function is_writable(file) |
48 | assert(file) | 52 | assert(file) |
53 | file = normalize(file) | ||
49 | local result | 54 | local result |
50 | if fs.is_dir(file) then | 55 | if fs.is_dir(file) then |
51 | local file2 = file .. '/.tmpluarockstestwritable' | 56 | local file2 = dir.path(file, '.tmpluarockstestwritable') |
52 | local fh = io.open(file2, 'wb') | 57 | local fh = io.open(file2, 'wb') |
53 | result = fh ~= nil | 58 | result = fh ~= nil |
54 | if fh then fh:close() end | 59 | if fh then fh:close() end |
@@ -67,8 +72,8 @@ end | |||
67 | -- @return string or nil: name of temporary directory or nil on failure. | 72 | -- @return string or nil: name of temporary directory or nil on failure. |
68 | function make_temp_dir(name) | 73 | function make_temp_dir(name) |
69 | assert(type(name) == "string") | 74 | assert(type(name) == "string") |
75 | name = normalize(name) | ||
70 | 76 | ||
71 | name = name:gsub("\\", "/") | ||
72 | local temp_dir = (os.getenv("TMP") or "/tmp") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-" .. tostring(math.floor(math.random() * 10000)) | 77 | local temp_dir = (os.getenv("TMP") or "/tmp") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-" .. tostring(math.floor(math.random() * 10000)) |
73 | if fs.make_dir(temp_dir) then | 78 | if fs.make_dir(temp_dir) then |
74 | return temp_dir | 79 | return temp_dir |
@@ -100,6 +105,7 @@ end | |||
100 | -- @return boolean: true if the MD5 checksum for 'file' equals 'md5sum', false if not | 105 | -- @return boolean: true if the MD5 checksum for 'file' equals 'md5sum', false if not |
101 | -- or if it could not perform the check for any reason. | 106 | -- or if it could not perform the check for any reason. |
102 | function check_md5(file, md5sum) | 107 | function check_md5(file, md5sum) |
108 | file = normalize(file) | ||
103 | local computed = fs.get_md5(file) | 109 | local computed = fs.get_md5(file) |
104 | if not computed then | 110 | if not computed then |
105 | return false | 111 | return false |
@@ -144,6 +150,7 @@ end | |||
144 | -- @param d string: The directory to switch to. | 150 | -- @param d string: The directory to switch to. |
145 | function change_dir(d) | 151 | function change_dir(d) |
146 | table.insert(dir_stack, lfs.currentdir()) | 152 | table.insert(dir_stack, lfs.currentdir()) |
153 | d = normalize(d) | ||
147 | lfs.chdir(d) | 154 | lfs.chdir(d) |
148 | end | 155 | end |
149 | 156 | ||
@@ -175,7 +182,7 @@ end | |||
175 | -- @return boolean: true on success, false on failure. | 182 | -- @return boolean: true on success, false on failure. |
176 | function make_dir(directory) | 183 | function make_dir(directory) |
177 | assert(type(directory) == "string") | 184 | assert(type(directory) == "string") |
178 | directory = directory:gsub("\\", "/") | 185 | directory = normalize(directory) |
179 | local path = nil | 186 | local path = nil |
180 | if directory:sub(2, 2) == ":" then | 187 | if directory:sub(2, 2) == ":" then |
181 | path = directory:sub(1, 2) | 188 | path = directory:sub(1, 2) |
@@ -205,6 +212,7 @@ end | |||
205 | -- @param d string: pathname of directory to remove. | 212 | -- @param d string: pathname of directory to remove. |
206 | function remove_dir_if_empty(d) | 213 | function remove_dir_if_empty(d) |
207 | assert(d) | 214 | assert(d) |
215 | d = normalize(d) | ||
208 | lfs.rmdir(d) | 216 | lfs.rmdir(d) |
209 | end | 217 | end |
210 | 218 | ||
@@ -214,6 +222,7 @@ end | |||
214 | -- @param d string: pathname of directory to remove. | 222 | -- @param d string: pathname of directory to remove. |
215 | function remove_dir_tree_if_empty(d) | 223 | function remove_dir_tree_if_empty(d) |
216 | assert(d) | 224 | assert(d) |
225 | d = normalize(d) | ||
217 | for i=1,10 do | 226 | for i=1,10 do |
218 | lfs.rmdir(d) | 227 | lfs.rmdir(d) |
219 | d = dir.dir_name(d) | 228 | d = dir.dir_name(d) |
@@ -227,6 +236,8 @@ end | |||
227 | -- plus an error message. | 236 | -- plus an error message. |
228 | function copy(src, dest) | 237 | function copy(src, dest) |
229 | assert(src and dest) | 238 | assert(src and dest) |
239 | src = normalize(src) | ||
240 | dest = normalize(dest) | ||
230 | local destmode = lfs.attributes(dest, "mode") | 241 | local destmode = lfs.attributes(dest, "mode") |
231 | if destmode == "directory" then | 242 | if destmode == "directory" then |
232 | dest = dir.path(dest, dir.base_name(src)) | 243 | dest = dir.path(dest, dir.base_name(src)) |
@@ -248,6 +259,7 @@ function copy(src, dest) | |||
248 | end | 259 | end |
249 | 260 | ||
250 | --- Implementation function for recursive copy of directory contents. | 261 | --- Implementation function for recursive copy of directory contents. |
262 | -- Assumes paths are normalized. | ||
251 | -- @param src string: Pathname of source | 263 | -- @param src string: Pathname of source |
252 | -- @param dest string: Pathname of destination | 264 | -- @param dest string: Pathname of destination |
253 | -- @return boolean or (boolean, string): true on success, false on failure | 265 | -- @return boolean or (boolean, string): true on success, false on failure |
@@ -277,6 +289,8 @@ end | |||
277 | -- plus an error message. | 289 | -- plus an error message. |
278 | function copy_contents(src, dest) | 290 | function copy_contents(src, dest) |
279 | assert(src and dest) | 291 | assert(src and dest) |
292 | src = normalize(src) | ||
293 | dest = normalize(dest) | ||
280 | assert(lfs.attributes(src, "mode") == "directory") | 294 | assert(lfs.attributes(src, "mode") == "directory") |
281 | 295 | ||
282 | for file in lfs.dir(src) do | 296 | for file in lfs.dir(src) do |
@@ -291,35 +305,34 @@ function copy_contents(src, dest) | |||
291 | end | 305 | end |
292 | 306 | ||
293 | --- Implementation function for recursive removal of directories. | 307 | --- Implementation function for recursive removal of directories. |
294 | -- @param src string: Pathname of source | 308 | -- Assumes paths are normalized. |
295 | -- @param dest string: Pathname of destination | 309 | -- @param name string: Pathname of file |
296 | -- @return boolean or (boolean, string): true on success, | 310 | -- @return boolean or (boolean, string): true on success, |
297 | -- or nil and an error message on failure. | 311 | -- or nil and an error message on failure. |
298 | local function recursive_delete(src) | 312 | local function recursive_delete(name) |
299 | local srcmode = lfs.attributes(src, "mode") | 313 | local mode = lfs.attributes(name, "mode") |
300 | 314 | ||
301 | if srcmode == "file" then | 315 | if mode == "file" then |
302 | return os.remove(src) | 316 | return os.remove(name) |
303 | elseif srcmode == "directory" then | 317 | elseif mode == "directory" then |
304 | for file in lfs.dir(src) do | 318 | for file in lfs.dir(name) do |
305 | if file ~= "." and file ~= ".." then | 319 | if file ~= "." and file ~= ".." then |
306 | local ok, err = recursive_delete(dir.path(src, file)) | 320 | local ok, err = recursive_delete(dir.path(name, file)) |
307 | if not ok then return nil, err end | 321 | if not ok then return nil, err end |
308 | end | 322 | end |
309 | end | 323 | end |
310 | local ok, err = lfs.rmdir(src) | 324 | local ok, err = lfs.rmdir(name) |
311 | if not ok then return nil, err end | 325 | if not ok then return nil, err end |
312 | end | 326 | end |
313 | return true | 327 | return true |
314 | end | 328 | end |
315 | 329 | ||
316 | --- Delete a file or a directory and all its contents. | 330 | --- Delete a file or a directory and all its contents. |
317 | -- For safety, this only accepts absolute paths. | 331 | -- @param name string: Pathname of source |
318 | -- @param arg string: Pathname of source | ||
319 | -- @return boolean: true on success, false on failure. | 332 | -- @return boolean: true on success, false on failure. |
320 | function delete(arg) | 333 | function delete(name) |
321 | assert(arg) | 334 | name = normalize(name) |
322 | return recursive_delete(arg) or false | 335 | return recursive_delete(name) or false |
323 | end | 336 | end |
324 | 337 | ||
325 | --- List the contents of a directory. | 338 | --- List the contents of a directory. |
@@ -332,6 +345,7 @@ function list_dir(at) | |||
332 | if not at then | 345 | if not at then |
333 | at = fs.current_dir() | 346 | at = fs.current_dir() |
334 | end | 347 | end |
348 | at = normalize(at) | ||
335 | if not fs.is_dir(at) then | 349 | if not fs.is_dir(at) then |
336 | return {} | 350 | return {} |
337 | end | 351 | end |
@@ -345,6 +359,7 @@ function list_dir(at) | |||
345 | end | 359 | end |
346 | 360 | ||
347 | --- Implementation function for recursive find. | 361 | --- Implementation function for recursive find. |
362 | -- Assumes paths are normalized. | ||
348 | -- @param cwd string: Current working directory in recursion. | 363 | -- @param cwd string: Current working directory in recursion. |
349 | -- @param prefix string: Auxiliary prefix string to form pathname. | 364 | -- @param prefix string: Auxiliary prefix string to form pathname. |
350 | -- @param result table: Array of strings where results are collected. | 365 | -- @param result table: Array of strings where results are collected. |
@@ -371,6 +386,7 @@ function find(at) | |||
371 | if not at then | 386 | if not at then |
372 | at = fs.current_dir() | 387 | at = fs.current_dir() |
373 | end | 388 | end |
389 | at = normalize(at) | ||
374 | if not fs.is_dir(at) then | 390 | if not fs.is_dir(at) then |
375 | return {} | 391 | return {} |
376 | end | 392 | end |
@@ -384,7 +400,8 @@ end | |||
384 | -- @return boolean: true if file exists, false otherwise. | 400 | -- @return boolean: true if file exists, false otherwise. |
385 | function exists(file) | 401 | function exists(file) |
386 | assert(file) | 402 | assert(file) |
387 | return type(lfs.attributes(file)) == "table" | 403 | file = normalize(file) |
404 | return type(file) == "table" | ||
388 | end | 405 | end |
389 | 406 | ||
390 | --- Test is pathname is a directory. | 407 | --- Test is pathname is a directory. |
@@ -392,6 +409,7 @@ end | |||
392 | -- @return boolean: true if it is a directory, false otherwise. | 409 | -- @return boolean: true if it is a directory, false otherwise. |
393 | function is_dir(file) | 410 | function is_dir(file) |
394 | assert(file) | 411 | assert(file) |
412 | file = normalize(file) | ||
395 | return lfs.attributes(file, "mode") == "directory" | 413 | return lfs.attributes(file, "mode") == "directory" |
396 | end | 414 | end |
397 | 415 | ||
@@ -400,10 +418,12 @@ end | |||
400 | -- @return boolean: true if it is a file, false otherwise. | 418 | -- @return boolean: true if it is a file, false otherwise. |
401 | function is_file(file) | 419 | function is_file(file) |
402 | assert(file) | 420 | assert(file) |
421 | file = normalize(file) | ||
403 | return lfs.attributes(file, "mode") == "file" | 422 | return lfs.attributes(file, "mode") == "file" |
404 | end | 423 | end |
405 | 424 | ||
406 | function set_time(file, time) | 425 | function set_time(file, time) |
426 | file = normalize(file) | ||
407 | return lfs.touch(file, time) | 427 | return lfs.touch(file, time) |
408 | end | 428 | end |
409 | 429 | ||
@@ -426,27 +446,27 @@ if unzip_ok then | |||
426 | -- @param zipfile string: pathname of .zip archive to be extracted. | 446 | -- @param zipfile string: pathname of .zip archive to be extracted. |
427 | -- @return boolean: true on success, false on failure. | 447 | -- @return boolean: true on success, false on failure. |
428 | function unzip(zipfile) | 448 | function unzip(zipfile) |
429 | local zipfile, err = luazip.open(zipfile) | 449 | local zipfile, err = luazip.open(zipfile) |
430 | if not zipfile then return nil, err end | 450 | if not zipfile then return nil, err end |
431 | local files = zipfile:files() | 451 | local files = zipfile:files() |
432 | local file = files() | 452 | local file = files() |
433 | repeat | 453 | repeat |
434 | if file.filename:sub(#file.filename) == "/" then | 454 | if file.filename:sub(#file.filename) == "/" then |
435 | fs.make_dir(dir.path(fs.current_dir(), file.filename)) | 455 | fs.make_dir(dir.path(fs.current_dir(), file.filename)) |
436 | else | 456 | else |
437 | local rf, err = zipfile:open(file.filename) | 457 | local rf, err = zipfile:open(file.filename) |
438 | if not rf then zipfile:close(); return nil, err end | 458 | if not rf then zipfile:close(); return nil, err end |
439 | local contents = rf:read("*a") | 459 | local contents = rf:read("*a") |
440 | rf:close() | 460 | rf:close() |
441 | local wf, err = io.open(dir.path(fs.current_dir(), file.filename), "wb") | 461 | local wf, err = io.open(dir.path(fs.current_dir(), file.filename), "wb") |
442 | if not wf then zipfile:close(); return nil, err end | 462 | if not wf then zipfile:close(); return nil, err end |
443 | wf:write(contents) | 463 | wf:write(contents) |
444 | wf:close() | 464 | wf:close() |
445 | end | 465 | end |
446 | file = files() | 466 | file = files() |
447 | until not file | 467 | until not file |
448 | zipfile:close() | 468 | zipfile:close() |
449 | return true | 469 | return true |
450 | end | 470 | end |
451 | 471 | ||
452 | end | 472 | end |
diff --git a/src/luarocks/search.lua b/src/luarocks/search.lua index 97b86a45..f8188ee5 100644 --- a/src/luarocks/search.lua +++ b/src/luarocks/search.lua | |||
@@ -269,11 +269,11 @@ function find_suitable_rock(query) | |||
269 | if not results then | 269 | if not results then |
270 | return nil, err | 270 | return nil, err |
271 | end | 271 | end |
272 | local first = results and next(results) | 272 | local first = next(results) |
273 | if first and next(results, first) == nil then | 273 | if not first then |
274 | return pick_latest_version(query.name, results[first]) | ||
275 | elseif not first then | ||
276 | return nil, "No results matching query were found." | 274 | return nil, "No results matching query were found." |
275 | elseif not next(results, first) then | ||
276 | return pick_latest_version(query.name, results[first]) | ||
277 | else | 277 | else |
278 | return results | 278 | return results |
279 | end | 279 | end |