Mercurial > repo
comparison share/lua/5.2/luarocks/search.lua @ 1132:d137f631bad5
<GreyKnight> (cd luabuild/luarocks-2.0.12; make install)
author | HackBot |
---|---|
date | Fri, 14 Dec 2012 22:24:27 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
1131:ff65dfb63263 | 1132:d137f631bad5 |
---|---|
1 | |
2 --- Module implementing the LuaRocks "search" command. | |
3 -- Queries LuaRocks servers. | |
4 module("luarocks.search", package.seeall) | |
5 | |
6 local dir = require("luarocks.dir") | |
7 local path = require("luarocks.path") | |
8 local manif = require("luarocks.manif") | |
9 local deps = require("luarocks.deps") | |
10 local cfg = require("luarocks.cfg") | |
11 local util = require("luarocks.util") | |
12 | |
13 help_summary = "Query the LuaRocks servers." | |
14 help_arguments = "[--source] [--binary] { <name> [<version>] | --all }" | |
15 help = [[ | |
16 --source Return only rockspecs and source rocks, | |
17 to be used with the "build" command. | |
18 --binary Return only pure Lua and binary rocks (rocks that can be used | |
19 with the "install" command without requiring a C toolchain). | |
20 --all List all contents of the server that are suitable to | |
21 this platform, do not filter by name. | |
22 ]] | |
23 | |
24 --- Convert the arch field of a query table to table format. | |
25 -- @param query table: A query table. | |
26 local function query_arch_as_table(query) | |
27 local format = type(query.arch) | |
28 if format == "table" then | |
29 return | |
30 elseif format == "nil" then | |
31 local accept = {} | |
32 accept["src"] = true | |
33 accept["all"] = true | |
34 accept["rockspec"] = true | |
35 accept["installed"] = true | |
36 accept[cfg.arch] = true | |
37 query.arch = accept | |
38 elseif format == "string" then | |
39 local accept = {} | |
40 for a in query.arch:gmatch("[%w_-]+") do | |
41 accept[a] = true | |
42 end | |
43 query.arch = accept | |
44 end | |
45 end | |
46 | |
47 --- Store a search result (a rock or rockspec) in the results table. | |
48 -- @param results table: The results table, where keys are package names and | |
49 -- versions are tables matching version strings to an array of servers. | |
50 -- @param name string: Package name. | |
51 -- @param version string: Package version. | |
52 -- @param arch string: Architecture of rock ("all", "src" or platform | |
53 -- identifier), "rockspec" or "installed" | |
54 -- @param repo string: Pathname of a local repository of URL of | |
55 -- rocks server. | |
56 local function store_result(results, name, version, arch, repo) | |
57 assert(type(results) == "table") | |
58 assert(type(name) == "string") | |
59 assert(type(version) == "string") | |
60 assert(type(arch) == "string") | |
61 assert(type(repo) == "string") | |
62 | |
63 if not results[name] then results[name] = {} end | |
64 if not results[name][version] then results[name][version] = {} end | |
65 table.insert(results[name][version], { | |
66 arch = arch, | |
67 repo = repo | |
68 }) | |
69 end | |
70 | |
71 --- Test the name field of a query. | |
72 -- If query has a boolean field exact_name set to false, | |
73 -- then substring match is performed; otherwise, exact string | |
74 -- comparison is done. | |
75 -- @param query table: A query in dependency table format. | |
76 -- @param name string: A package name. | |
77 -- @return boolean: True if names match, false otherwise. | |
78 local function match_name(query, name) | |
79 assert(type(query) == "table") | |
80 assert(type(name) == "string") | |
81 if query.exact_name == false then | |
82 return name:find(query.name, 0, true) and true or false | |
83 else | |
84 return name == query.name | |
85 end | |
86 end | |
87 | |
88 --- Store a match in a results table if version matches query. | |
89 -- Name, version, arch and repository path are stored in a given | |
90 -- table, optionally checking if version and arch (if given) match | |
91 -- a query. | |
92 -- @param results table: The results table, where keys are package names and | |
93 -- versions are tables matching version strings to an array of servers. | |
94 -- @param repo string: URL or pathname of the repository. | |
95 -- @param name string: The name of the package being tested. | |
96 -- @param version string: The version of the package being tested. | |
97 -- @param arch string: The arch of the package being tested. | |
98 -- @param query table: A table describing the query in dependency | |
99 -- format (for example, {name = "filesystem", exact_name = false, | |
100 -- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). | |
101 -- If the arch field is omitted, the local architecture (cfg.arch) | |
102 -- is used. The special value "any" is also recognized, returning all | |
103 -- matches regardless of architecture. | |
104 local function store_if_match(results, repo, name, version, arch, query) | |
105 if match_name(query, name) then | |
106 if query.arch[arch] or query.arch["any"] then | |
107 if deps.match_constraints(deps.parse_version(version), query.constraints) then | |
108 store_result(results, name, version, arch, repo) | |
109 end | |
110 end | |
111 end | |
112 end | |
113 | |
114 --- Perform search on a local repository. | |
115 -- @param repo string: The pathname of the local repository. | |
116 -- @param query table: A table describing the query in dependency | |
117 -- format (for example, {name = "filesystem", exact_name = false, | |
118 -- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). | |
119 -- If the arch field is omitted, the local architecture (cfg.arch) | |
120 -- is used. The special value "any" is also recognized, returning all | |
121 -- matches regardless of architecture. | |
122 -- @param results table or nil: If given, this table will store the | |
123 -- results; if not given, a new table will be created. | |
124 -- @param table: The results table, where keys are package names and | |
125 -- versions are tables matching version strings to an array of servers. | |
126 -- If a table was given in the "results" parameter, that is the result value. | |
127 function disk_search(repo, query, results) | |
128 assert(type(repo) == "string") | |
129 assert(type(query) == "table") | |
130 assert(type(results) == "table" or not results) | |
131 | |
132 local fs = require("luarocks.fs") | |
133 | |
134 if not results then | |
135 results = {} | |
136 end | |
137 query_arch_as_table(query) | |
138 | |
139 for _, name in pairs(fs.list_dir(repo)) do | |
140 local pathname = dir.path(repo, name) | |
141 local rname, rversion, rarch = path.parse_name(name) | |
142 if fs.is_dir(pathname) then | |
143 for _, version in pairs(fs.list_dir(pathname)) do | |
144 if version:match("-%d+$") then | |
145 store_if_match(results, repo, name, version, "installed", query) | |
146 end | |
147 end | |
148 elseif rname then | |
149 store_if_match(results, repo, rname, rversion, rarch, query) | |
150 end | |
151 end | |
152 return results | |
153 end | |
154 | |
155 --- Perform search on a rocks server. | |
156 -- @param results table: The results table, where keys are package names and | |
157 -- versions are tables matching version strings to an array of servers. | |
158 -- @param repo string: The URL of the rocks server. | |
159 -- @param query table: A table describing the query in dependency | |
160 -- format (for example, {name = "filesystem", exact_name = false, | |
161 -- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). | |
162 -- If the arch field is omitted, the local architecture (cfg.arch) | |
163 -- is used. The special value "any" is also recognized, returning all | |
164 -- matches regardless of architecture. | |
165 -- @return true or, in case of errors, nil and an error message. | |
166 function manifest_search(results, repo, query) | |
167 assert(type(results) == "table") | |
168 assert(type(repo) == "string") | |
169 assert(type(query) == "table") | |
170 | |
171 query_arch_as_table(query) | |
172 local manifest, err = manif.load_manifest(repo) | |
173 if not manifest then | |
174 return nil, "Failed loading manifest: "..err | |
175 end | |
176 for name, versions in pairs(manifest.repository) do | |
177 for version, items in pairs(versions) do | |
178 for _, item in ipairs(items) do | |
179 store_if_match(results, repo, name, version, item.arch, query) | |
180 end | |
181 end | |
182 end | |
183 return true | |
184 end | |
185 | |
186 --- Search on all configured rocks servers. | |
187 -- @param query table: A dependency query. | |
188 -- @return table: A table where keys are package names | |
189 -- and values are tables matching version strings to an array of | |
190 -- rocks servers; if no results are found, an empty table is returned. | |
191 function search_repos(query) | |
192 assert(type(query) == "table") | |
193 | |
194 local results = {} | |
195 for _, repo in ipairs(cfg.rocks_servers) do | |
196 if type(repo) == "string" then | |
197 repo = { repo } | |
198 end | |
199 for _, mirror in ipairs(repo) do | |
200 local protocol, pathname = dir.split_url(mirror) | |
201 if protocol == "file" then | |
202 mirror = pathname | |
203 end | |
204 local ok, err = manifest_search(results, mirror, query) | |
205 if ok then | |
206 break | |
207 else | |
208 util.warning("Failed searching manifest: "..err) | |
209 end | |
210 end | |
211 end | |
212 return results | |
213 end | |
214 | |
215 --- Prepare a query in dependency table format. | |
216 -- @param name string: The query name. | |
217 -- @param version string or nil: | |
218 -- @return table: A query in table format | |
219 function make_query(name, version) | |
220 assert(type(name) == "string") | |
221 assert(type(version) == "string" or not version) | |
222 | |
223 local query = { | |
224 name = name, | |
225 constraints = {} | |
226 } | |
227 if version then | |
228 table.insert(query.constraints, { op = "==", version = deps.parse_version(version)}) | |
229 end | |
230 return query | |
231 end | |
232 | |
233 --- Get the URL for the latest in a set of versions. | |
234 -- @param name string: The package name to be used in the URL. | |
235 -- @param versions table: An array of version informations, as stored | |
236 -- in search results tables. | |
237 -- @return string or nil: the URL for the latest version if one could | |
238 -- be picked, or nil. | |
239 local function pick_latest_version(name, versions) | |
240 assert(type(name) == "string") | |
241 assert(type(versions) == "table") | |
242 | |
243 local vtables = {} | |
244 for v, _ in pairs(versions) do | |
245 table.insert(vtables, deps.parse_version(v)) | |
246 end | |
247 table.sort(vtables) | |
248 local version = vtables[#vtables].string | |
249 local items = versions[version] | |
250 if items then | |
251 local pick = 1 | |
252 for i, item in ipairs(items) do | |
253 if (item.arch == 'src' and items[pick].arch == 'rockspec') | |
254 or (item.arch ~= 'src' and item.arch ~= 'rockspec') then | |
255 pick = i | |
256 end | |
257 end | |
258 return path.make_url(items[pick].repo, name, version, items[pick].arch) | |
259 end | |
260 return nil | |
261 end | |
262 | |
263 --- Attempt to get a single URL for a given search. | |
264 -- @param query table: A dependency query. | |
265 -- @return string or table or (nil, string): URL for matching rock if | |
266 -- a single one was found, a table of candidates if it could not narrow to | |
267 -- a single result, or nil followed by an error message. | |
268 function find_suitable_rock(query) | |
269 assert(type(query) == "table") | |
270 | |
271 local results = search_repos(query) | |
272 local first = next(results) | |
273 if not first then | |
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 | |
278 return results | |
279 end | |
280 end | |
281 | |
282 --- Print a list of rocks/rockspecs on standard output. | |
283 -- @param results table: A table where keys are package names and versions | |
284 -- are tables matching version strings to an array of rocks servers. | |
285 -- @param porcelain boolean or nil: A flag to force machine-friendly output. | |
286 function print_results(results, porcelain) | |
287 assert(type(results) == "table") | |
288 assert(type(porcelain) == "boolean" or not porcelain) | |
289 | |
290 for package, versions in util.sortedpairs(results) do | |
291 if not porcelain then | |
292 util.printout(package) | |
293 end | |
294 for version, repos in util.sortedpairs(versions, deps.compare_versions) do | |
295 for _, repo in ipairs(repos) do | |
296 if porcelain then | |
297 util.printout(package, version, repo.arch, repo.repo) | |
298 else | |
299 util.printout(" "..version.." ("..repo.arch..") - "..repo.repo) | |
300 end | |
301 end | |
302 end | |
303 if not porcelain then | |
304 util.printout() | |
305 end | |
306 end | |
307 end | |
308 | |
309 --- Splits a list of search results into two lists, one for "source" results | |
310 -- to be used with the "build" command, and one for "binary" results to be | |
311 -- used with the "install" command. | |
312 -- @param results table: A search results table. | |
313 -- @return (table, table): Two tables, one for source and one for binary | |
314 -- results. | |
315 local function split_source_and_binary_results(results) | |
316 local sources, binaries = {}, {} | |
317 for name, versions in pairs(results) do | |
318 for version, repositories in pairs(versions) do | |
319 for _, repo in ipairs(repositories) do | |
320 local where = sources | |
321 if repo.arch == "all" or repo.arch == cfg.arch then | |
322 where = binaries | |
323 end | |
324 store_result(where, name, version, repo.arch, repo.repo) | |
325 end | |
326 end | |
327 end | |
328 return sources, binaries | |
329 end | |
330 | |
331 --- Given a name and optionally a version, try to find in the rocks | |
332 -- servers a single .src.rock or .rockspec file that satisfies | |
333 -- the request, and run the given function on it; or display to the | |
334 -- user possibilities if it couldn't narrow down a single match. | |
335 -- @param action function: A function that takes a .src.rock or | |
336 -- .rockspec URL as a parameter. | |
337 -- @param name string: A rock name | |
338 -- @param version string or nil: A version number may also be given. | |
339 -- @return The result of the action function, or nil and an error message. | |
340 function act_on_src_or_rockspec(action, name, version, ...) | |
341 assert(type(action) == "function") | |
342 assert(type(name) == "string") | |
343 assert(type(version) == "string" or not version) | |
344 | |
345 local query = make_query(name, version) | |
346 query.arch = "src|rockspec" | |
347 local results, err = find_suitable_rock(query) | |
348 if type(results) == "string" then | |
349 return action(results, ...) | |
350 else | |
351 return nil, "Could not find a result named "..name..(version and " "..version or "").."." | |
352 end | |
353 end | |
354 | |
355 --- Driver function for "search" command. | |
356 -- @param name string: A substring of a rock name to search. | |
357 -- @param version string or nil: a version may also be passed. | |
358 -- @return boolean or (nil, string): True if build was successful; nil and an | |
359 -- error message otherwise. | |
360 function run(...) | |
361 local flags, name, version = util.parse_flags(...) | |
362 | |
363 if flags["all"] then | |
364 name, version = "", nil | |
365 end | |
366 | |
367 if type(name) ~= "string" and not flags["all"] then | |
368 return nil, "Enter name and version or use --all; see help." | |
369 end | |
370 | |
371 local query = make_query(name:lower(), version) | |
372 query.exact_name = false | |
373 local results, err = search_repos(query) | |
374 local porcelain = flags["porcelain"] | |
375 util.title("Search results:", porcelain, "=") | |
376 local sources, binaries = split_source_and_binary_results(results) | |
377 if next(sources) and not flags["binary"] then | |
378 util.title("Rockspecs and source rocks:", porcelain) | |
379 print_results(sources, porcelain) | |
380 end | |
381 if next(binaries) and not flags["source"] then | |
382 util.title("Binary and pure-Lua rocks:", porcelain) | |
383 print_results(binaries, porcelain) | |
384 end | |
385 return true | |
386 end |