aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordaurnimator <quae@daurnimator.com>2019-08-24 12:15:34 +1000
committerdaurnimator <quae@daurnimator.com>2019-08-24 12:15:34 +1000
commitcffcc251be58914c49a1acbdaf5c38e42f93976c (patch)
treedffd799f90d06016e1b43cbd6f96c385e1ef0a64
parent8a957d68133a8af6650c4f8349646fa9420258f3 (diff)
parent8e602d751e79c97eb664682b5b2441495d9ad974 (diff)
downloadluarocks-cffcc251be58914c49a1acbdaf5c38e42f93976c.tar.gz
luarocks-cffcc251be58914c49a1acbdaf5c38e42f93976c.tar.bz2
luarocks-cffcc251be58914c49a1acbdaf5c38e42f93976c.zip
Merge PR #1035
-rw-r--r--.gitignore2
-rw-r--r--spec/which_spec.lua2
-rwxr-xr-xsrc/bin/luarocks1
-rwxr-xr-xsrc/bin/luarocks-admin1
-rw-r--r--src/luarocks/admin/cmd/add.lua38
-rw-r--r--src/luarocks/admin/cmd/make_manifest.lua30
-rw-r--r--src/luarocks/admin/cmd/refresh_cache.lua20
-rw-r--r--src/luarocks/admin/cmd/remove.lua31
-rw-r--r--src/luarocks/argparse.lua2078
-rw-r--r--src/luarocks/cmd.lua346
-rw-r--r--src/luarocks/cmd/build.lua98
-rw-r--r--src/luarocks/cmd/config.lua144
-rw-r--r--src/luarocks/cmd/doc.lua50
-rw-r--r--src/luarocks/cmd/download.lua44
-rw-r--r--src/luarocks/cmd/help.lua139
-rw-r--r--src/luarocks/cmd/init.lua48
-rw-r--r--src/luarocks/cmd/install.lua104
-rw-r--r--src/luarocks/cmd/lint.lua31
-rw-r--r--src/luarocks/cmd/list.lua38
-rw-r--r--src/luarocks/cmd/make.lua107
-rw-r--r--src/luarocks/cmd/new_version.lua94
-rw-r--r--src/luarocks/cmd/pack.lua41
-rw-r--r--src/luarocks/cmd/path.lua51
-rw-r--r--src/luarocks/cmd/purge.lua37
-rw-r--r--src/luarocks/cmd/remove.lua44
-rw-r--r--src/luarocks/cmd/search.lua49
-rw-r--r--src/luarocks/cmd/show.lua82
-rw-r--r--src/luarocks/cmd/test.lua44
-rw-r--r--src/luarocks/cmd/unpack.lua34
-rw-r--r--src/luarocks/cmd/upload.lua66
-rw-r--r--src/luarocks/cmd/which.lua30
-rw-r--r--src/luarocks/cmd/write_rockspec.lua139
-rw-r--r--src/luarocks/deps.lua24
-rw-r--r--src/luarocks/fs/lua.lua6
-rw-r--r--src/luarocks/manif/writer.lua4
-rw-r--r--src/luarocks/upload/api.lua10
-rw-r--r--src/luarocks/util.lua211
37 files changed, 3092 insertions, 1226 deletions
diff --git a/.gitignore b/.gitignore
index 23f76e8a..cf1c1a40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
1/config.* 1/config.*
2/src/luarocks/core/hardcoded.lua 2/src/luarocks/core/hardcoded.lua
3/testrun/* 3/testrun/*
4/lua_install
4# Stuff that pops up during development but shouldn't be in the repo (helps clean up `git status`) 5# Stuff that pops up during development but shouldn't be in the repo (helps clean up `git status`)
5/build 6/build
6/build-binary 7/build-binary
@@ -15,4 +16,5 @@
15/lua 16/lua
16/lua_modules 17/lua_modules
17/luarocks 18/luarocks
19/luarocks-admin
18/.luarocks 20/.luarocks
diff --git a/spec/which_spec.lua b/spec/which_spec.lua
index d5bcfab9..79b9ef7e 100644
--- a/spec/which_spec.lua
+++ b/spec/which_spec.lua
@@ -22,7 +22,7 @@ describe("LuaRocks which tests #integration", function()
22 22
23 it("fails on missing arguments", function() 23 it("fails on missing arguments", function()
24 local output = run.luarocks("which") 24 local output = run.luarocks("which")
25 assert.match("Missing module name", output, 1, true) 25 assert.match("missing argument 'modname'", output, 1, true)
26 end) 26 end)
27 27
28 it("finds modules found in package.path", function() 28 it("finds modules found in package.path", function()
diff --git a/src/bin/luarocks b/src/bin/luarocks
index d982530c..56caaa60 100755
--- a/src/bin/luarocks
+++ b/src/bin/luarocks
@@ -9,7 +9,6 @@ local cmd = require("luarocks.cmd")
9local description = "LuaRocks main command-line interface" 9local description = "LuaRocks main command-line interface"
10 10
11local commands = { 11local commands = {
12 help = "luarocks.cmd.help",
13 init = "luarocks.cmd.init", 12 init = "luarocks.cmd.init",
14 pack = "luarocks.cmd.pack", 13 pack = "luarocks.cmd.pack",
15 unpack = "luarocks.cmd.unpack", 14 unpack = "luarocks.cmd.unpack",
diff --git a/src/bin/luarocks-admin b/src/bin/luarocks-admin
index 71e17b55..4a85e45b 100755
--- a/src/bin/luarocks-admin
+++ b/src/bin/luarocks-admin
@@ -9,7 +9,6 @@ local cmd = require("luarocks.cmd")
9local description = "LuaRocks repository administration interface" 9local description = "LuaRocks repository administration interface"
10 10
11local commands = { 11local commands = {
12 help = "luarocks.cmd.help",
13 make_manifest = "luarocks.admin.cmd.make_manifest", 12 make_manifest = "luarocks.admin.cmd.make_manifest",
14 add = "luarocks.admin.cmd.add", 13 add = "luarocks.admin.cmd.add",
15 remove = "luarocks.admin.cmd.remove", 14 remove = "luarocks.admin.cmd.remove",
diff --git a/src/luarocks/admin/cmd/add.lua b/src/luarocks/admin/cmd/add.lua
index 19990b3c..5011c680 100644
--- a/src/luarocks/admin/cmd/add.lua
+++ b/src/luarocks/admin/cmd/add.lua
@@ -11,20 +11,20 @@ local fs = require("luarocks.fs")
11local cache = require("luarocks.admin.cache") 11local cache = require("luarocks.admin.cache")
12local index = require("luarocks.admin.index") 12local index = require("luarocks.admin.index")
13 13
14add.help_summary = "Add a rock or rockspec to a rocks server." 14function add.add_to_parser(parser)
15add.help_arguments = "[--server=<server>] [--no-refresh] {<rockspec>|<rock>...}" 15 local cmd = parser:command("add", "Add a rock or rockspec to a rocks server.", util.see_also())
16add.help = [[ 16
17Arguments are local files, which may be rockspecs or rocks. 17 cmd:argument("rock", "A local rockspec or rock file.")
18The flag --server indicates which server to use. 18 :args("+")
19If not given, the default server set in the upload_server variable 19
20from the configuration file is used instead. 20 cmd:option("--server", "The server to use. If not given, the default server "..
21 21 "set in the upload_server variable from the configuration file is used instead.")
22--no-refresh The local cache should not be refreshed 22 :target("add_server")
23 prior to generation of the updated manifest. 23 cmd:flag("--no-refresh", "Do not refresh the local cache prior to "..
24--index Produce an index.html file for the manifest. 24 "generation of the updated manifest.")
25 This flag is automatically set if an index.html 25 cmd:flag("--index", "Produce an index.html file for the manifest. This "..
26 file already exists. 26 "flag is automatically set if an index.html file already exists.")
27]] 27end
28 28
29local function zip_manifests() 29local function zip_manifests()
30 for ver in util.lua_versions() do 30 for ver in util.lua_versions() do
@@ -124,14 +124,10 @@ local function add_files_to_server(refresh, rockfiles, server, upload_server, do
124 return fs.execute(cmd) 124 return fs.execute(cmd)
125end 125end
126 126
127function add.command(flags, ...) 127function add.command(args)
128 local files = {...} 128 local server, server_table = cache.get_upload_server(args.add_server or args.server)
129 if #files < 1 then
130 return nil, "Argument missing. "..util.see_help("add", "luarocks-admin")
131 end
132 local server, server_table = cache.get_upload_server(flags["server"])
133 if not server then return nil, server_table end 129 if not server then return nil, server_table end
134 return add_files_to_server(not flags["no-refresh"], files, server, server_table, flags["index"]) 130 return add_files_to_server(not args.no_refresh, args.rock, server, server_table, args.index)
135end 131end
136 132
137 133
diff --git a/src/luarocks/admin/cmd/make_manifest.lua b/src/luarocks/admin/cmd/make_manifest.lua
index f0b64135..ab940f9e 100644
--- a/src/luarocks/admin/cmd/make_manifest.lua
+++ b/src/luarocks/admin/cmd/make_manifest.lua
@@ -11,36 +11,36 @@ local deps = require("luarocks.deps")
11local fs = require("luarocks.fs") 11local fs = require("luarocks.fs")
12local dir = require("luarocks.dir") 12local dir = require("luarocks.dir")
13 13
14make_manifest.help_summary = "Compile a manifest file for a repository." 14function make_manifest.add_to_parser(parser)
15 local cmd = parser:command("make_manifest", "Compile a manifest file for a repository.", util.see_also())
16 parser:command("make-manifest"):hidden(true):action(function(args) args.command = "make_manifest" end)
15 17
16make_manifest.help = [[ 18 cmd:argument("repository", "Local repository pathname.")
17<argument>, if given, is a local repository pathname. 19 :args("?")
18 20
19--local-tree If given, do not write versioned versions of the manifest file. 21 cmd:flag("--local-tree", "If given, do not write versioned versions of the manifest file.\n"..
20 Use this when rebuilding the manifest of a local rocks tree. 22 "Use this when rebuilding the manifest of a local rocks tree.")
21]] 23 util.deps_mode_option(cmd)
24end
22 25
23--- Driver function for "make_manifest" command. 26--- Driver function for "make_manifest" command.
24-- @param repo string or nil: Pathname of a local repository. If not given,
25-- the default local repository configured as cfg.rocks_dir is used.
26-- @return boolean or (nil, string): True if manifest was generated, 27-- @return boolean or (nil, string): True if manifest was generated,
27-- or nil and an error message. 28-- or nil and an error message.
28function make_manifest.command(flags, repo) 29function make_manifest.command(args)
29 assert(type(repo) == "string" or not repo) 30 local repo = args.repository or cfg.rocks_dir
30 repo = repo or cfg.rocks_dir
31 31
32 util.printout("Making manifest for "..repo) 32 util.printout("Making manifest for "..repo)
33 33
34 if repo:match("/lib/luarocks") and not flags["local-tree"] then 34 if repo:match("/lib/luarocks") and not args.local_tree then
35 util.warning("This looks like a local rocks tree, but you did not pass --local-tree.") 35 util.warning("This looks like a local rocks tree, but you did not pass --local-tree.")
36 end 36 end
37 37
38 local ok, err = writer.make_manifest(repo, deps.get_deps_mode(flags), not flags["local-tree"]) 38 local ok, err = writer.make_manifest(repo, deps.get_deps_mode(args), not args.local_tree)
39 if ok and not flags["local-tree"] then 39 if ok and not args.local_tree then
40 util.printout("Generating index.html for "..repo) 40 util.printout("Generating index.html for "..repo)
41 index.make_index(repo) 41 index.make_index(repo)
42 end 42 end
43 if flags["local-tree"] then 43 if args.local_tree then
44 for luaver in util.lua_versions() do 44 for luaver in util.lua_versions() do
45 fs.delete(dir.path(repo, "manifest-"..luaver)) 45 fs.delete(dir.path(repo, "manifest-"..luaver))
46 end 46 end
diff --git a/src/luarocks/admin/cmd/refresh_cache.lua b/src/luarocks/admin/cmd/refresh_cache.lua
index 3ffe56d9..40c9ff09 100644
--- a/src/luarocks/admin/cmd/refresh_cache.lua
+++ b/src/luarocks/admin/cmd/refresh_cache.lua
@@ -3,18 +3,20 @@
3local refresh_cache = {} 3local refresh_cache = {}
4 4
5local cfg = require("luarocks.core.cfg") 5local cfg = require("luarocks.core.cfg")
6local util = require("luarocks.util")
6local cache = require("luarocks.admin.cache") 7local cache = require("luarocks.admin.cache")
7 8
8refresh_cache.help_summary = "Refresh local cache of a remote rocks server." 9function refresh_cache.add_to_parser(parser)
9refresh_cache.help_arguments = "[--from=<server>]" 10 local cmd = parser:command("refresh_cache", "Refresh local cache of a remote rocks server.", util.see_also())
10refresh_cache.help = [[ 11 parser:command("refresh-cache"):hidden(true):action(function(args) args.command = "refresh_cache" end)
11The flag --from indicates which server to use.
12If not given, the default server set in the upload_server variable
13from the configuration file is used instead.
14]]
15 12
16function refresh_cache.command(flags) 13 cmd:option("--from", "The server to use. If not given, the default server "..
17 local server, upload_server = cache.get_upload_server(flags["server"]) 14 "set in the upload_server variable from the configuration file is used instead.")
15 :argname("<server>")
16end
17
18function refresh_cache.command(args)
19 local server, upload_server = cache.get_upload_server(args.server)
18 if not server then return nil, upload_server end 20 if not server then return nil, upload_server end
19 local download_url = cache.get_server_urls(server, upload_server) 21 local download_url = cache.get_server_urls(server, upload_server)
20 22
diff --git a/src/luarocks/admin/cmd/remove.lua b/src/luarocks/admin/cmd/remove.lua
index f005c83c..de58f7a3 100644
--- a/src/luarocks/admin/cmd/remove.lua
+++ b/src/luarocks/admin/cmd/remove.lua
@@ -11,16 +11,17 @@ local fs = require("luarocks.fs")
11local cache = require("luarocks.admin.cache") 11local cache = require("luarocks.admin.cache")
12local index = require("luarocks.admin.index") 12local index = require("luarocks.admin.index")
13 13
14admin_remove.help_summary = "Remove a rock or rockspec from a rocks server." 14function admin_remove.add_to_parser(parser)
15admin_remove.help_arguments = "[--server=<server>] [--no-refresh] {<rockspec>|<rock>...}" 15 local cmd = parser:command("remove", "Remove a rock or rockspec from a rocks server.", util.see_also())
16admin_remove.help = [[ 16
17Arguments are local files, which may be rockspecs or rocks. 17 cmd:argument("rock", "A local rockspec or rock file.")
18The flag --server indicates which server to use. 18 :args("+")
19If not given, the default server set in the upload_server variable 19
20from the configuration file is used instead. 20 cmd:option("--server", "The server to use. If not given, the default server "..
21The flag --no-refresh indicates the local cache should not be refreshed 21 "set in the upload_server variable from the configuration file is used instead.")
22prior to generation of the updated manifest. 22 cmd:flag("--no-refresh", "Do not refresh the local cache prior to "..
23]] 23 "generation of the updated manifest.")
24end
24 25
25local function remove_files_from_server(refresh, rockfiles, server, upload_server) 26local function remove_files_from_server(refresh, rockfiles, server, upload_server)
26 assert(type(refresh) == "boolean" or not refresh) 27 assert(type(refresh) == "boolean" or not refresh)
@@ -76,14 +77,10 @@ local function remove_files_from_server(refresh, rockfiles, server, upload_serve
76 return true 77 return true
77end 78end
78 79
79function admin_remove.command(flags, ...) 80function admin_remove.command(args)
80 local files = {...} 81 local server, server_table = cache.get_upload_server(args.server)
81 if #files < 1 then
82 return nil, "Argument missing. "..util.see_help("remove", "luarocks-admin")
83 end
84 local server, server_table = cache.get_upload_server(flags["server"])
85 if not server then return nil, server_table end 82 if not server then return nil, server_table end
86 return remove_files_from_server(not flags["no-refresh"], files, server, server_table) 83 return remove_files_from_server(not args.no_refresh, args.rock, server, server_table)
87end 84end
88 85
89 86
diff --git a/src/luarocks/argparse.lua b/src/luarocks/argparse.lua
new file mode 100644
index 00000000..dc6cdb0d
--- /dev/null
+++ b/src/luarocks/argparse.lua
@@ -0,0 +1,2078 @@
1-- The MIT License (MIT)
2
3-- Copyright (c) 2013 - 2018 Peter Melnichenko
4-- 2019 Paul Ouellette
5
6-- Permission is hereby granted, free of charge, to any person obtaining a copy of
7-- this software and associated documentation files (the "Software"), to deal in
8-- the Software without restriction, including without limitation the rights to
9-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10-- the Software, and to permit persons to whom the Software is furnished to do so,
11-- subject to the following conditions:
12
13-- The above copyright notice and this permission notice shall be included in all
14-- copies or substantial portions of the Software.
15
16-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23local function deep_update(t1, t2)
24 for k, v in pairs(t2) do
25 if type(v) == "table" then
26 v = deep_update({}, v)
27 end
28
29 t1[k] = v
30 end
31
32 return t1
33end
34
35-- A property is a tuple {name, callback}.
36-- properties.args is number of properties that can be set as arguments
37-- when calling an object.
38local function class(prototype, properties, parent)
39 -- Class is the metatable of its instances.
40 local cl = {}
41 cl.__index = cl
42
43 if parent then
44 cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype)
45 else
46 cl.__prototype = prototype
47 end
48
49 if properties then
50 local names = {}
51
52 -- Create setter methods and fill set of property names.
53 for _, property in ipairs(properties) do
54 local name, callback = property[1], property[2]
55
56 cl[name] = function(self, value)
57 if not callback(self, value) then
58 self["_" .. name] = value
59 end
60
61 return self
62 end
63
64 names[name] = true
65 end
66
67 function cl.__call(self, ...)
68 -- When calling an object, if the first argument is a table,
69 -- interpret keys as property names, else delegate arguments
70 -- to corresponding setters in order.
71 if type((...)) == "table" then
72 for name, value in pairs((...)) do
73 if names[name] then
74 self[name](self, value)
75 end
76 end
77 else
78 local nargs = select("#", ...)
79
80 for i, property in ipairs(properties) do
81 if i > nargs or i > properties.args then
82 break
83 end
84
85 local arg = select(i, ...)
86
87 if arg ~= nil then
88 self[property[1]](self, arg)
89 end
90 end
91 end
92
93 return self
94 end
95 end
96
97 -- If indexing class fails, fallback to its parent.
98 local class_metatable = {}
99 class_metatable.__index = parent
100
101 function class_metatable.__call(self, ...)
102 -- Calling a class returns its instance.
103 -- Arguments are delegated to the instance.
104 local object = deep_update({}, self.__prototype)
105 setmetatable(object, self)
106 return object(...)
107 end
108
109 return setmetatable(cl, class_metatable)
110end
111
112local function typecheck(name, types, value)
113 for _, type_ in ipairs(types) do
114 if type(value) == type_ then
115 return true
116 end
117 end
118
119 error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value)))
120end
121
122local function typechecked(name, ...)
123 local types = {...}
124 return {name, function(_, value) typecheck(name, types, value) end}
125end
126
127local multiname = {"name", function(self, value)
128 typecheck("name", {"string"}, value)
129
130 for alias in value:gmatch("%S+") do
131 self._name = self._name or alias
132 table.insert(self._aliases, alias)
133 end
134
135 -- Do not set _name as with other properties.
136 return true
137end}
138
139local function parse_boundaries(str)
140 if tonumber(str) then
141 return tonumber(str), tonumber(str)
142 end
143
144 if str == "*" then
145 return 0, math.huge
146 end
147
148 if str == "+" then
149 return 1, math.huge
150 end
151
152 if str == "?" then
153 return 0, 1
154 end
155
156 if str:match "^%d+%-%d+$" then
157 local min, max = str:match "^(%d+)%-(%d+)$"
158 return tonumber(min), tonumber(max)
159 end
160
161 if str:match "^%d+%+$" then
162 local min = str:match "^(%d+)%+$"
163 return tonumber(min), math.huge
164 end
165end
166
167local function boundaries(name)
168 return {name, function(self, value)
169 typecheck(name, {"number", "string"}, value)
170
171 local min, max = parse_boundaries(value)
172
173 if not min then
174 error(("bad property '%s'"):format(name))
175 end
176
177 self["_min" .. name], self["_max" .. name] = min, max
178 end}
179end
180
181local actions = {}
182
183local option_action = {"action", function(_, value)
184 typecheck("action", {"function", "string"}, value)
185
186 if type(value) == "string" and not actions[value] then
187 error(("unknown action '%s'"):format(value))
188 end
189end}
190
191local option_init = {"init", function(self)
192 self._has_init = true
193end}
194
195local option_default = {"default", function(self, value)
196 if type(value) ~= "string" then
197 self._init = value
198 self._has_init = true
199 return true
200 end
201end}
202
203local add_help = {"add_help", function(self, value)
204 typecheck("add_help", {"boolean", "string", "table"}, value)
205
206 if self._help_option_idx then
207 table.remove(self._options, self._help_option_idx)
208 self._help_option_idx = nil
209 end
210
211 if value then
212 local help = self:flag()
213 :description "Show this help message and exit."
214 :action(function()
215 print(self:get_help())
216 os.exit(0)
217 end)
218
219 if value ~= true then
220 help = help(value)
221 end
222
223 if not help._name then
224 help "-h" "--help"
225 end
226
227 self._help_option_idx = #self._options
228 end
229end}
230
231local Parser = class({
232 _arguments = {},
233 _options = {},
234 _commands = {},
235 _mutexes = {},
236 _groups = {},
237 _require_command = true,
238 _handle_options = true
239}, {
240 args = 3,
241 typechecked("name", "string"),
242 typechecked("description", "string"),
243 typechecked("epilog", "string"),
244 typechecked("usage", "string"),
245 typechecked("help", "string"),
246 typechecked("require_command", "boolean"),
247 typechecked("handle_options", "boolean"),
248 typechecked("action", "function"),
249 typechecked("command_target", "string"),
250 typechecked("help_vertical_space", "number"),
251 typechecked("usage_margin", "number"),
252 typechecked("usage_max_width", "number"),
253 typechecked("help_usage_margin", "number"),
254 typechecked("help_description_margin", "number"),
255 typechecked("help_max_width", "number"),
256 add_help
257})
258
259local Command = class({
260 _aliases = {}
261}, {
262 args = 3,
263 multiname,
264 typechecked("description", "string"),
265 typechecked("epilog", "string"),
266 typechecked("summary", "string"),
267 typechecked("target", "string"),
268 typechecked("usage", "string"),
269 typechecked("help", "string"),
270 typechecked("require_command", "boolean"),
271 typechecked("handle_options", "boolean"),
272 typechecked("action", "function"),
273 typechecked("command_target", "string"),
274 typechecked("help_vertical_space", "number"),
275 typechecked("usage_margin", "number"),
276 typechecked("usage_max_width", "number"),
277 typechecked("help_usage_margin", "number"),
278 typechecked("help_description_margin", "number"),
279 typechecked("help_max_width", "number"),
280 typechecked("hidden", "boolean"),
281 add_help
282}, Parser)
283
284local Argument = class({
285 _minargs = 1,
286 _maxargs = 1,
287 _mincount = 1,
288 _maxcount = 1,
289 _defmode = "unused",
290 _show_default = true
291}, {
292 args = 5,
293 typechecked("name", "string"),
294 typechecked("description", "string"),
295 option_default,
296 typechecked("convert", "function", "table"),
297 boundaries("args"),
298 typechecked("target", "string"),
299 typechecked("defmode", "string"),
300 typechecked("show_default", "boolean"),
301 typechecked("argname", "string", "table"),
302 typechecked("choices", "table"),
303 typechecked("hidden", "boolean"),
304 option_action,
305 option_init
306})
307
308local Option = class({
309 _aliases = {},
310 _mincount = 0,
311 _overwrite = true
312}, {
313 args = 6,
314 multiname,
315 typechecked("description", "string"),
316 option_default,
317 typechecked("convert", "function", "table"),
318 boundaries("args"),
319 boundaries("count"),
320 typechecked("target", "string"),
321 typechecked("defmode", "string"),
322 typechecked("show_default", "boolean"),
323 typechecked("overwrite", "boolean"),
324 typechecked("argname", "string", "table"),
325 typechecked("choices", "table"),
326 typechecked("hidden", "boolean"),
327 option_action,
328 option_init
329}, Argument)
330
331function Parser:_inherit_property(name, default)
332 local element = self
333
334 while true do
335 local value = element["_" .. name]
336
337 if value ~= nil then
338 return value
339 end
340
341 if not element._parent then
342 return default
343 end
344
345 element = element._parent
346 end
347end
348
349function Argument:_get_argument_list()
350 local buf = {}
351 local i = 1
352
353 while i <= math.min(self._minargs, 3) do
354 local argname = self:_get_argname(i)
355
356 if self._default and self._defmode:find "a" then
357 argname = "[" .. argname .. "]"
358 end
359
360 table.insert(buf, argname)
361 i = i+1
362 end
363
364 while i <= math.min(self._maxargs, 3) do
365 table.insert(buf, "[" .. self:_get_argname(i) .. "]")
366 i = i+1
367
368 if self._maxargs == math.huge then
369 break
370 end
371 end
372
373 if i < self._maxargs then
374 table.insert(buf, "...")
375 end
376
377 return buf
378end
379
380function Argument:_get_usage()
381 local usage = table.concat(self:_get_argument_list(), " ")
382
383 if self._default and self._defmode:find "u" then
384 if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then
385 usage = "[" .. usage .. "]"
386 end
387 end
388
389 return usage
390end
391
392function actions.store_true(result, target)
393 result[target] = true
394end
395
396function actions.store_false(result, target)
397 result[target] = false
398end
399
400function actions.store(result, target, argument)
401 result[target] = argument
402end
403
404function actions.count(result, target, _, overwrite)
405 if not overwrite then
406 result[target] = result[target] + 1
407 end
408end
409
410function actions.append(result, target, argument, overwrite)
411 result[target] = result[target] or {}
412 table.insert(result[target], argument)
413
414 if overwrite then
415 table.remove(result[target], 1)
416 end
417end
418
419function actions.concat(result, target, arguments, overwrite)
420 if overwrite then
421 error("'concat' action can't handle too many invocations")
422 end
423
424 result[target] = result[target] or {}
425
426 for _, argument in ipairs(arguments) do
427 table.insert(result[target], argument)
428 end
429end
430
431function Argument:_get_action()
432 local action, init
433
434 if self._maxcount == 1 then
435 if self._maxargs == 0 then
436 action, init = "store_true", nil
437 else
438 action, init = "store", nil
439 end
440 else
441 if self._maxargs == 0 then
442 action, init = "count", 0
443 else
444 action, init = "append", {}
445 end
446 end
447
448 if self._action then
449 action = self._action
450 end
451
452 if self._has_init then
453 init = self._init
454 end
455
456 if type(action) == "string" then
457 action = actions[action]
458 end
459
460 return action, init
461end
462
463-- Returns placeholder for `narg`-th argument.
464function Argument:_get_argname(narg)
465 local argname = self._argname or self:_get_default_argname()
466
467 if type(argname) == "table" then
468 return argname[narg]
469 else
470 return argname
471 end
472end
473
474function Argument:_get_choices_list()
475 return "{" .. table.concat(self._choices, ",") .. "}"
476end
477
478function Argument:_get_default_argname()
479 if self._choices then
480 return self:_get_choices_list()
481 else
482 return "<" .. self._name .. ">"
483 end
484end
485
486function Option:_get_default_argname()
487 if self._choices then
488 return self:_get_choices_list()
489 else
490 return "<" .. self:_get_default_target() .. ">"
491 end
492end
493
494-- Returns labels to be shown in the help message.
495function Argument:_get_label_lines()
496 if self._choices then
497 return {self:_get_choices_list()}
498 else
499 return {self._name}
500 end
501end
502
503function Option:_get_label_lines()
504 local argument_list = self:_get_argument_list()
505
506 if #argument_list == 0 then
507 -- Don't put aliases for simple flags like `-h` on different lines.
508 return {table.concat(self._aliases, ", ")}
509 end
510
511 local longest_alias_length = -1
512
513 for _, alias in ipairs(self._aliases) do
514 longest_alias_length = math.max(longest_alias_length, #alias)
515 end
516
517 local argument_list_repr = table.concat(argument_list, " ")
518 local lines = {}
519
520 for i, alias in ipairs(self._aliases) do
521 local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr
522
523 if i ~= #self._aliases then
524 line = line .. ","
525 end
526
527 table.insert(lines, line)
528 end
529
530 return lines
531end
532
533function Command:_get_label_lines()
534 return {table.concat(self._aliases, ", ")}
535end
536
537function Argument:_get_description()
538 if self._default and self._show_default then
539 if self._description then
540 return ("%s (default: %s)"):format(self._description, self._default)
541 else
542 return ("default: %s"):format(self._default)
543 end
544 else
545 return self._description or ""
546 end
547end
548
549function Command:_get_description()
550 return self._summary or self._description or ""
551end
552
553function Option:_get_usage()
554 local usage = self:_get_argument_list()
555 table.insert(usage, 1, self._name)
556 usage = table.concat(usage, " ")
557
558 if self._mincount == 0 or self._default then
559 usage = "[" .. usage .. "]"
560 end
561
562 return usage
563end
564
565function Argument:_get_default_target()
566 return self._name
567end
568
569function Option:_get_default_target()
570 local res
571
572 for _, alias in ipairs(self._aliases) do
573 if alias:sub(1, 1) == alias:sub(2, 2) then
574 res = alias:sub(3)
575 break
576 end
577 end
578
579 res = res or self._name:sub(2)
580 return (res:gsub("-", "_"))
581end
582
583function Option:_is_vararg()
584 return self._maxargs ~= self._minargs
585end
586
587function Parser:_get_fullname(exclude_root)
588 local parent = self._parent
589 if exclude_root and not parent then
590 return ""
591 end
592 local buf = {self._name}
593
594 while parent do
595 if not exclude_root or parent._parent then
596 table.insert(buf, 1, parent._name)
597 end
598 parent = parent._parent
599 end
600
601 return table.concat(buf, " ")
602end
603
604function Parser:_update_charset(charset)
605 charset = charset or {}
606
607 for _, command in ipairs(self._commands) do
608 command:_update_charset(charset)
609 end
610
611 for _, option in ipairs(self._options) do
612 for _, alias in ipairs(option._aliases) do
613 charset[alias:sub(1, 1)] = true
614 end
615 end
616
617 return charset
618end
619
620function Parser:argument(...)
621 local argument = Argument(...)
622 table.insert(self._arguments, argument)
623 return argument
624end
625
626function Parser:option(...)
627 local option = Option(...)
628 table.insert(self._options, option)
629 return option
630end
631
632function Parser:flag(...)
633 return self:option():args(0)(...)
634end
635
636function Parser:command(...)
637 local command = Command():add_help(true)(...)
638 command._parent = self
639 table.insert(self._commands, command)
640 return command
641end
642
643function Parser:mutex(...)
644 local elements = {...}
645
646 for i, element in ipairs(elements) do
647 local mt = getmetatable(element)
648 assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i))
649 end
650
651 table.insert(self._mutexes, elements)
652 return self
653end
654
655function Parser:group(name, ...)
656 assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name)))
657
658 local group = {name = name, ...}
659
660 for i, element in ipairs(group) do
661 local mt = getmetatable(element)
662 assert(mt == Option or mt == Argument or mt == Command,
663 ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1))
664 end
665
666 table.insert(self._groups, group)
667 return self
668end
669
670local usage_welcome = "Usage: "
671
672function Parser:get_usage()
673 if self._usage then
674 return self._usage
675 end
676
677 local usage_margin = self:_inherit_property("usage_margin", #usage_welcome)
678 local max_usage_width = self:_inherit_property("usage_max_width", 70)
679 local lines = {usage_welcome .. self:_get_fullname()}
680
681 local function add(s)
682 if #lines[#lines]+1+#s <= max_usage_width then
683 lines[#lines] = lines[#lines] .. " " .. s
684 else
685 lines[#lines+1] = (" "):rep(usage_margin) .. s
686 end
687 end
688
689 -- Normally options are before positional arguments in usage messages.
690 -- However, vararg options should be after, because they can't be reliable used
691 -- before a positional argument.
692 -- Mutexes come into play, too, and are shown as soon as possible.
693 -- Overall, output usages in the following order:
694 -- 1. Mutexes that don't have positional arguments or vararg options.
695 -- 2. Options that are not in any mutexes and are not vararg.
696 -- 3. Positional arguments - on their own or as a part of a mutex.
697 -- 4. Remaining mutexes.
698 -- 5. Remaining options.
699
700 local elements_in_mutexes = {}
701 local added_elements = {}
702 local added_mutexes = {}
703 local argument_to_mutexes = {}
704
705 local function add_mutex(mutex, main_argument)
706 if added_mutexes[mutex] then
707 return
708 end
709
710 added_mutexes[mutex] = true
711 local buf = {}
712
713 for _, element in ipairs(mutex) do
714 if not element._hidden and not added_elements[element] then
715 if getmetatable(element) == Option or element == main_argument then
716 table.insert(buf, element:_get_usage())
717 added_elements[element] = true
718 end
719 end
720 end
721
722 if #buf == 1 then
723 add(buf[1])
724 elseif #buf > 1 then
725 add("(" .. table.concat(buf, " | ") .. ")")
726 end
727 end
728
729 local function add_element(element)
730 if not element._hidden and not added_elements[element] then
731 add(element:_get_usage())
732 added_elements[element] = true
733 end
734 end
735
736 for _, mutex in ipairs(self._mutexes) do
737 local is_vararg = false
738 local has_argument = false
739
740 for _, element in ipairs(mutex) do
741 if getmetatable(element) == Option then
742 if element:_is_vararg() then
743 is_vararg = true
744 end
745 else
746 has_argument = true
747 argument_to_mutexes[element] = argument_to_mutexes[element] or {}
748 table.insert(argument_to_mutexes[element], mutex)
749 end
750
751 elements_in_mutexes[element] = true
752 end
753
754 if not is_vararg and not has_argument then
755 add_mutex(mutex)
756 end
757 end
758
759 for _, option in ipairs(self._options) do
760 if not elements_in_mutexes[option] and not option:_is_vararg() then
761 add_element(option)
762 end
763 end
764
765 -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex.
766 for _, argument in ipairs(self._arguments) do
767 -- Pick a mutex as a part of which to show this argument, take the first one that's still available.
768 local mutex
769
770 if elements_in_mutexes[argument] then
771 for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do
772 if not added_mutexes[argument_mutex] then
773 mutex = argument_mutex
774 end
775 end
776 end
777
778 if mutex then
779 add_mutex(mutex, argument)
780 else
781 add_element(argument)
782 end
783 end
784
785 for _, mutex in ipairs(self._mutexes) do
786 add_mutex(mutex)
787 end
788
789 for _, option in ipairs(self._options) do
790 add_element(option)
791 end
792
793 if #self._commands > 0 then
794 if self._require_command then
795 add("<command>")
796 else
797 add("[<command>]")
798 end
799
800 add("...")
801 end
802
803 return table.concat(lines, "\n")
804end
805
806local function split_lines(s)
807 if s == "" then
808 return {}
809 end
810
811 local lines = {}
812
813 if s:sub(-1) ~= "\n" then
814 s = s .. "\n"
815 end
816
817 for line in s:gmatch("([^\n]*)\n") do
818 table.insert(lines, line)
819 end
820
821 return lines
822end
823
824local function autowrap_line(line, max_length)
825 -- Algorithm for splitting lines is simple and greedy.
826 local result_lines = {}
827
828 -- Preserve original indentation of the line, put this at the beginning of each result line.
829 -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts
830 -- of the second and the following lines vertically align with the start of the second word.
831 local indentation = line:match("^ *")
832
833 if line:find("^ *[%*%+%-]") then
834 indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)")
835 end
836
837 -- Parts of the last line being assembled.
838 local line_parts = {}
839
840 -- Length of the current line.
841 local line_length = 0
842
843 -- Index of the next character to consider.
844 local index = 1
845
846 while true do
847 local word_start, word_finish, word = line:find("([^ ]+)", index)
848
849 if not word_start then
850 -- Ignore trailing spaces, if any.
851 break
852 end
853
854 local preceding_spaces = line:sub(index, word_start - 1)
855 index = word_finish + 1
856
857 if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then
858 -- Either this is the very first word or it fits as an addition to the current line, add it.
859 table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation.
860 table.insert(line_parts, word)
861 line_length = line_length + #preceding_spaces + #word
862 else
863 -- Does not fit, finish current line and put the word into a new one.
864 table.insert(result_lines, table.concat(line_parts))
865 line_parts = {indentation, word}
866 line_length = #indentation + #word
867 end
868 end
869
870 if #line_parts > 0 then
871 table.insert(result_lines, table.concat(line_parts))
872 end
873
874 if #result_lines == 0 then
875 -- Preserve empty lines.
876 result_lines[1] = ""
877 end
878
879 return result_lines
880end
881
882-- Automatically wraps lines within given array,
883-- attempting to limit line length to `max_length`.
884-- Existing line splits are preserved.
885local function autowrap(lines, max_length)
886 local result_lines = {}
887
888 for _, line in ipairs(lines) do
889 local autowrapped_lines = autowrap_line(line, max_length)
890
891 for _, autowrapped_line in ipairs(autowrapped_lines) do
892 table.insert(result_lines, autowrapped_line)
893 end
894 end
895
896 return result_lines
897end
898
899function Parser:_get_element_help(element)
900 local label_lines = element:_get_label_lines()
901 local description_lines = split_lines(element:_get_description())
902
903 local result_lines = {}
904
905 -- All label lines should have the same length (except the last one, it has no comma).
906 -- If too long, start description after all the label lines.
907 -- Otherwise, combine label and description lines.
908
909 local usage_margin_len = self:_inherit_property("help_usage_margin", 3)
910 local usage_margin = (" "):rep(usage_margin_len)
911 local description_margin_len = self:_inherit_property("help_description_margin", 25)
912 local description_margin = (" "):rep(description_margin_len)
913
914 local help_max_width = self:_inherit_property("help_max_width")
915
916 if help_max_width then
917 local description_max_width = math.max(help_max_width - description_margin_len, 10)
918 description_lines = autowrap(description_lines, description_max_width)
919 end
920
921 if #label_lines[1] >= (description_margin_len - usage_margin_len) then
922 for _, label_line in ipairs(label_lines) do
923 table.insert(result_lines, usage_margin .. label_line)
924 end
925
926 for _, description_line in ipairs(description_lines) do
927 table.insert(result_lines, description_margin .. description_line)
928 end
929 else
930 for i = 1, math.max(#label_lines, #description_lines) do
931 local label_line = label_lines[i]
932 local description_line = description_lines[i]
933
934 local line = ""
935
936 if label_line then
937 line = usage_margin .. label_line
938 end
939
940 if description_line and description_line ~= "" then
941 line = line .. (" "):rep(description_margin_len - #line) .. description_line
942 end
943
944 table.insert(result_lines, line)
945 end
946 end
947
948 return table.concat(result_lines, "\n")
949end
950
951local function get_group_types(group)
952 local types = {}
953
954 for _, element in ipairs(group) do
955 types[getmetatable(element)] = true
956 end
957
958 return types
959end
960
961function Parser:_add_group_help(blocks, added_elements, label, elements)
962 local buf = {label}
963
964 for _, element in ipairs(elements) do
965 if not element._hidden and not added_elements[element] then
966 added_elements[element] = true
967 table.insert(buf, self:_get_element_help(element))
968 end
969 end
970
971 if #buf > 1 then
972 table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1)))
973 end
974end
975
976function Parser:get_help()
977 if self._help then
978 return self._help
979 end
980
981 local blocks = {self:get_usage()}
982
983 local help_max_width = self:_inherit_property("help_max_width")
984
985 if self._description then
986 local description = self._description
987
988 if help_max_width then
989 description = table.concat(autowrap(split_lines(description), help_max_width), "\n")
990 end
991
992 table.insert(blocks, description)
993 end
994
995 -- 1. Put groups containing arguments first, then other arguments.
996 -- 2. Put remaining groups containing options, then other options.
997 -- 3. Put remaining groups containing commands, then other commands.
998 -- Assume that an element can't be in several groups.
999 local groups_by_type = {
1000 [Argument] = {},
1001 [Option] = {},
1002 [Command] = {}
1003 }
1004
1005 for _, group in ipairs(self._groups) do
1006 local group_types = get_group_types(group)
1007
1008 for _, mt in ipairs({Argument, Option, Command}) do
1009 if group_types[mt] then
1010 table.insert(groups_by_type[mt], group)
1011 break
1012 end
1013 end
1014 end
1015
1016 local default_groups = {
1017 {name = "Arguments", type = Argument, elements = self._arguments},
1018 {name = "Options", type = Option, elements = self._options},
1019 {name = "Commands", type = Command, elements = self._commands}
1020 }
1021
1022 local added_elements = {}
1023
1024 for _, default_group in ipairs(default_groups) do
1025 local type_groups = groups_by_type[default_group.type]
1026
1027 for _, group in ipairs(type_groups) do
1028 self:_add_group_help(blocks, added_elements, group.name .. ":", group)
1029 end
1030
1031 local default_label = default_group.name .. ":"
1032
1033 if #type_groups > 0 then
1034 default_label = "Other " .. default_label:gsub("^.", string.lower)
1035 end
1036
1037 self:_add_group_help(blocks, added_elements, default_label, default_group.elements)
1038 end
1039
1040 if self._epilog then
1041 local epilog = self._epilog
1042
1043 if help_max_width then
1044 epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n")
1045 end
1046
1047 table.insert(blocks, epilog)
1048 end
1049
1050 return table.concat(blocks, "\n\n")
1051end
1052
1053function Parser:add_help_command(value)
1054 if value then
1055 assert(type(value) == "string" or type(value) == "table",
1056 ("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(value)))
1057 end
1058
1059 local help = self:command()
1060 :description "Show help for commands."
1061 help:argument "command"
1062 :description "The command to show help for."
1063 :args "?"
1064 :action(function(_, _, cmd)
1065 if not cmd then
1066 print(self:get_help())
1067 os.exit(0)
1068 else
1069 for _, command in ipairs(self._commands) do
1070 for _, alias in ipairs(command._aliases) do
1071 if alias == cmd then
1072 print(command:get_help())
1073 os.exit(0)
1074 end
1075 end
1076 end
1077 end
1078 help:error(("unknown command '%s'"):format(cmd))
1079 end)
1080
1081 if value then
1082 help = help(value)
1083 end
1084
1085 if not help._name then
1086 help "help"
1087 end
1088
1089 help._is_help_command = true
1090 return self
1091end
1092
1093function Parser:_is_shell_safe()
1094 if self._basename then
1095 if self._basename:find("[^%w_%-%+%.]") then
1096 return false
1097 end
1098 else
1099 for _, alias in ipairs(self._aliases) do
1100 if alias:find("[^%w_%-%+%.]") then
1101 return false
1102 end
1103 end
1104 end
1105 for _, option in ipairs(self._options) do
1106 for _, alias in ipairs(option._aliases) do
1107 if alias:find("[^%w_%-%+%.]") then
1108 return false
1109 end
1110 end
1111 if option._choices then
1112 for _, choice in ipairs(option._choices) do
1113 if choice:find("[%s'\"]") then
1114 return false
1115 end
1116 end
1117 end
1118 end
1119 for _, argument in ipairs(self._arguments) do
1120 if argument._choices then
1121 for _, choice in ipairs(argument._choices) do
1122 if choice:find("[%s'\"]") then
1123 return false
1124 end
1125 end
1126 end
1127 end
1128 for _, command in ipairs(self._commands) do
1129 if not command:_is_shell_safe() then
1130 return false
1131 end
1132 end
1133 return true
1134end
1135
1136function Parser:add_complete(value)
1137 if value then
1138 assert(type(value) == "string" or type(value) == "table",
1139 ("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(value)))
1140 end
1141
1142 local complete = self:option()
1143 :description "Output a shell completion script for the specified shell."
1144 :args(1)
1145 :choices {"bash", "zsh", "fish"}
1146 :action(function(_, _, shell)
1147 io.write(self["get_" .. shell .. "_complete"](self))
1148 os.exit(0)
1149 end)
1150
1151 if value then
1152 complete = complete(value)
1153 end
1154
1155 if not complete._name then
1156 complete "--completion"
1157 end
1158
1159 return self
1160end
1161
1162function Parser:add_complete_command(value)
1163 if value then
1164 assert(type(value) == "string" or type(value) == "table",
1165 ("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(value)))
1166 end
1167
1168 local complete = self:command()
1169 :description "Output a shell completion script."
1170 complete:argument "shell"
1171 :description "The shell to output a completion script for."
1172 :choices {"bash", "zsh", "fish"}
1173 :action(function(_, _, shell)
1174 io.write(self["get_" .. shell .. "_complete"](self))
1175 os.exit(0)
1176 end)
1177
1178 if value then
1179 complete = complete(value)
1180 end
1181
1182 if not complete._name then
1183 complete "completion"
1184 end
1185
1186 return self
1187end
1188
1189local function base_name(pathname)
1190 return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname
1191end
1192
1193local function get_short_description(element)
1194 local short = element:_get_description():match("^(.-)%.%s")
1195 return short or element:_get_description():match("^(.-)%.?$")
1196end
1197
1198function Parser:_get_options()
1199 local options = {}
1200 for _, option in ipairs(self._options) do
1201 for _, alias in ipairs(option._aliases) do
1202 table.insert(options, alias)
1203 end
1204 end
1205 return table.concat(options, " ")
1206end
1207
1208function Parser:_get_commands()
1209 local commands = {}
1210 for _, command in ipairs(self._commands) do
1211 for _, alias in ipairs(command._aliases) do
1212 table.insert(commands, alias)
1213 end
1214 end
1215 return table.concat(commands, " ")
1216end
1217
1218function Parser:_bash_option_args(buf, indent)
1219 local opts = {}
1220 for _, option in ipairs(self._options) do
1221 if option._choices or option._minargs > 0 then
1222 local compreply
1223 if option._choices then
1224 compreply = 'COMPREPLY=($(compgen -W "' .. table.concat(option._choices, " ") .. '" -- "$cur"))'
1225 else
1226 compreply = 'COMPREPLY=($(compgen -f -- "$cur"))'
1227 end
1228 table.insert(opts, (" "):rep(indent + 4) .. table.concat(option._aliases, "|") .. ")")
1229 table.insert(opts, (" "):rep(indent + 8) .. compreply)
1230 table.insert(opts, (" "):rep(indent + 8) .. "return 0")
1231 table.insert(opts, (" "):rep(indent + 8) .. ";;")
1232 end
1233 end
1234
1235 if #opts > 0 then
1236 table.insert(buf, (" "):rep(indent) .. 'case "$prev" in')
1237 table.insert(buf, table.concat(opts, "\n"))
1238 table.insert(buf, (" "):rep(indent) .. "esac\n")
1239 end
1240end
1241
1242function Parser:_bash_get_cmd(buf, indent)
1243 if #self._commands == 0 then
1244 return
1245 end
1246
1247 table.insert(buf, (" "):rep(indent) .. 'args=("${args[@]:1}")')
1248 table.insert(buf, (" "):rep(indent) .. 'for arg in "${args[@]}"; do')
1249 table.insert(buf, (" "):rep(indent + 4) .. 'case "$arg" in')
1250
1251 for _, command in ipairs(self._commands) do
1252 table.insert(buf, (" "):rep(indent + 8) .. table.concat(command._aliases, "|") .. ")")
1253 if self._parent then
1254 table.insert(buf, (" "):rep(indent + 12) .. 'cmd="$cmd ' .. command._name .. '"')
1255 else
1256 table.insert(buf, (" "):rep(indent + 12) .. 'cmd="' .. command._name .. '"')
1257 end
1258 table.insert(buf, (" "):rep(indent + 12) .. 'opts="$opts ' .. command:_get_options() .. '"')
1259 command:_bash_get_cmd(buf, indent + 12)
1260 table.insert(buf, (" "):rep(indent + 12) .. "break")
1261 table.insert(buf, (" "):rep(indent + 12) .. ";;")
1262 end
1263
1264 table.insert(buf, (" "):rep(indent + 4) .. "esac")
1265 table.insert(buf, (" "):rep(indent) .. "done")
1266end
1267
1268function Parser:_bash_cmd_completions(buf)
1269 local cmd_buf = {}
1270 if self._parent then
1271 self:_bash_option_args(cmd_buf, 12)
1272 end
1273 if #self._commands > 0 then
1274 table.insert(cmd_buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self:_get_commands() .. '" -- "$cur"))')
1275 elseif self._is_help_command then
1276 table.insert(cmd_buf, (" "):rep(12)
1277 .. 'COMPREPLY=($(compgen -W "'
1278 .. self._parent:_get_commands()
1279 .. '" -- "$cur"))')
1280 end
1281 if #cmd_buf > 0 then
1282 table.insert(buf, (" "):rep(8) .. "'" .. self:_get_fullname(true) .. "')")
1283 table.insert(buf, table.concat(cmd_buf, "\n"))
1284 table.insert(buf, (" "):rep(12) .. ";;")
1285 end
1286
1287 for _, command in ipairs(self._commands) do
1288 command:_bash_cmd_completions(buf)
1289 end
1290end
1291
1292function Parser:get_bash_complete()
1293 self._basename = base_name(self._name)
1294 assert(self:_is_shell_safe())
1295 local buf = {([[
1296_%s() {
1297 local IFS=$' \t\n'
1298 local args cur prev cmd opts arg
1299 args=("${COMP_WORDS[@]}")
1300 cur="${COMP_WORDS[COMP_CWORD]}"
1301 prev="${COMP_WORDS[COMP_CWORD-1]}"
1302 opts="%s"
1303]]):format(self._basename, self:_get_options())}
1304
1305 self:_bash_option_args(buf, 4)
1306 self:_bash_get_cmd(buf, 4)
1307 if #self._commands > 0 then
1308 table.insert(buf, "")
1309 table.insert(buf, (" "):rep(4) .. 'case "$cmd" in')
1310 self:_bash_cmd_completions(buf)
1311 table.insert(buf, (" "):rep(4) .. "esac\n")
1312 end
1313
1314 table.insert(buf, ([=[
1315 if [[ "$cur" = -* ]]; then
1316 COMPREPLY=($(compgen -W "$opts" -- "$cur"))
1317 fi
1318}
1319
1320complete -F _%s -o bashdefault -o default %s
1321]=]):format(self._basename, self._basename))
1322
1323 return table.concat(buf, "\n")
1324end
1325
1326function Parser:_zsh_arguments(buf, cmd_name, indent)
1327 if self._parent then
1328 table.insert(buf, (" "):rep(indent) .. "options=(")
1329 table.insert(buf, (" "):rep(indent + 2) .. "$options")
1330 else
1331 table.insert(buf, (" "):rep(indent) .. "local -a options=(")
1332 end
1333
1334 for _, option in ipairs(self._options) do
1335 local line = {}
1336 if #option._aliases > 1 then
1337 if option._maxcount > 1 then
1338 table.insert(line, '"*"')
1339 end
1340 table.insert(line, "{" .. table.concat(option._aliases, ",") .. '}"')
1341 else
1342 table.insert(line, '"')
1343 if option._maxcount > 1 then
1344 table.insert(line, "*")
1345 end
1346 table.insert(line, option._name)
1347 end
1348 if option._description then
1349 local description = get_short_description(option):gsub('["%]:`$]', "\\%0")
1350 table.insert(line, "[" .. description .. "]")
1351 end
1352 if option._maxargs == math.huge then
1353 table.insert(line, ":*")
1354 end
1355 if option._choices then
1356 table.insert(line, ": :(" .. table.concat(option._choices, " ") .. ")")
1357 elseif option._maxargs > 0 then
1358 table.insert(line, ": :_files")
1359 end
1360 table.insert(line, '"')
1361 table.insert(buf, (" "):rep(indent + 2) .. table.concat(line))
1362 end
1363
1364 table.insert(buf, (" "):rep(indent) .. ")")
1365 table.insert(buf, (" "):rep(indent) .. "_arguments -s -S \\")
1366 table.insert(buf, (" "):rep(indent + 2) .. "$options \\")
1367
1368 if self._is_help_command then
1369 table.insert(buf, (" "):rep(indent + 2) .. '": :(' .. self._parent:_get_commands() .. ')" \\')
1370 else
1371 for _, argument in ipairs(self._arguments) do
1372 local spec
1373 if argument._choices then
1374 spec = ": :(" .. table.concat(argument._choices, " ") .. ")"
1375 else
1376 spec = ": :_files"
1377 end
1378 if argument._maxargs == math.huge then
1379 table.insert(buf, (" "):rep(indent + 2) .. '"*' .. spec .. '" \\')
1380 break
1381 end
1382 for _ = 1, argument._maxargs do
1383 table.insert(buf, (" "):rep(indent + 2) .. '"' .. spec .. '" \\')
1384 end
1385 end
1386
1387 if #self._commands > 0 then
1388 table.insert(buf, (" "):rep(indent + 2) .. '": :_' .. cmd_name .. '_cmds" \\')
1389 table.insert(buf, (" "):rep(indent + 2) .. '"*:: :->args" \\')
1390 end
1391 end
1392
1393 table.insert(buf, (" "):rep(indent + 2) .. "&& return 0")
1394end
1395
1396function Parser:_zsh_cmds(buf, cmd_name)
1397 table.insert(buf, "\n_" .. cmd_name .. "_cmds() {")
1398 table.insert(buf, " local -a commands=(")
1399
1400 for _, command in ipairs(self._commands) do
1401 local line = {}
1402 if #command._aliases > 1 then
1403 table.insert(line, "{" .. table.concat(command._aliases, ",") .. '}"')
1404 else
1405 table.insert(line, '"' .. command._name)
1406 end
1407 if command._description then
1408 table.insert(line, ":" .. get_short_description(command):gsub('["`$]', "\\%0"))
1409 end
1410 table.insert(buf, " " .. table.concat(line) .. '"')
1411 end
1412
1413 table.insert(buf, ' )\n _describe "command" commands\n}')
1414end
1415
1416function Parser:_zsh_complete_help(buf, cmds_buf, cmd_name, indent)
1417 if #self._commands == 0 then
1418 return
1419 end
1420
1421 self:_zsh_cmds(cmds_buf, cmd_name)
1422 table.insert(buf, "\n" .. (" "):rep(indent) .. "case $words[1] in")
1423
1424 for _, command in ipairs(self._commands) do
1425 local name = cmd_name .. "_" .. command._name
1426 table.insert(buf, (" "):rep(indent + 2) .. table.concat(command._aliases, "|") .. ")")
1427 command:_zsh_arguments(buf, name, indent + 4)
1428 command:_zsh_complete_help(buf, cmds_buf, name, indent + 4)
1429 table.insert(buf, (" "):rep(indent + 4) .. ";;\n")
1430 end
1431
1432 table.insert(buf, (" "):rep(indent) .. "esac")
1433end
1434
1435function Parser:get_zsh_complete()
1436 self._basename = base_name(self._name)
1437 assert(self:_is_shell_safe())
1438 local buf = {("#compdef %s\n"):format(self._basename)}
1439 local cmds_buf = {}
1440 table.insert(buf, "_" .. self._basename .. "() {")
1441 if #self._commands > 0 then
1442 table.insert(buf, " local context state state_descr line")
1443 table.insert(buf, " typeset -A opt_args\n")
1444 end
1445 self:_zsh_arguments(buf, self._basename, 2)
1446 self:_zsh_complete_help(buf, cmds_buf, self._basename, 2)
1447 table.insert(buf, "\n return 1")
1448 table.insert(buf, "}")
1449
1450 local result = table.concat(buf, "\n")
1451 if #cmds_buf > 0 then
1452 result = result .. "\n" .. table.concat(cmds_buf, "\n")
1453 end
1454 return result .. "\n\n_" .. self._basename .. "\n"
1455end
1456
1457local function fish_escape(string)
1458 return string:gsub("[\\']", "\\%0")
1459end
1460
1461function Parser:_fish_get_cmd(buf, indent)
1462 if #self._commands == 0 then
1463 return
1464 end
1465
1466 table.insert(buf, (" "):rep(indent) .. "set -e cmdline[1]")
1467 table.insert(buf, (" "):rep(indent) .. "for arg in $cmdline")
1468 table.insert(buf, (" "):rep(indent + 4) .. "switch $arg")
1469
1470 for _, command in ipairs(self._commands) do
1471 table.insert(buf, (" "):rep(indent + 8) .. "case " .. table.concat(command._aliases, " "))
1472 table.insert(buf, (" "):rep(indent + 12) .. "set cmd $cmd " .. command._name)
1473 command:_fish_get_cmd(buf, indent + 12)
1474 table.insert(buf, (" "):rep(indent + 12) .. "break")
1475 end
1476
1477 table.insert(buf, (" "):rep(indent + 4) .. "end")
1478 table.insert(buf, (" "):rep(indent) .. "end")
1479end
1480
1481function Parser:_fish_complete_help(buf, basename)
1482 local prefix = "complete -c " .. basename
1483 table.insert(buf, "")
1484
1485 for _, command in ipairs(self._commands) do
1486 local aliases = table.concat(command._aliases, " ")
1487 local line
1488 if self._parent then
1489 line = ("%s -n '__fish_%s_using_command %s' -xa '%s'")
1490 :format(prefix, basename, self:_get_fullname(true), aliases)
1491 else
1492 line = ("%s -n '__fish_%s_using_command' -xa '%s'"):format(prefix, basename, aliases)
1493 end
1494 if command._description then
1495 line = ("%s -d '%s'"):format(line, fish_escape(get_short_description(command)))
1496 end
1497 table.insert(buf, line)
1498 end
1499
1500 if self._is_help_command then
1501 local line = ("%s -n '__fish_%s_using_command %s' -xa '%s'")
1502 :format(prefix, basename, self:_get_fullname(true), self._parent:_get_commands())
1503 table.insert(buf, line)
1504 end
1505
1506 for _, option in ipairs(self._options) do
1507 local parts = {prefix}
1508
1509 if self._parent then
1510 table.insert(parts, "-n '__fish_" .. basename .. "_seen_command " .. self:_get_fullname(true) .. "'")
1511 end
1512
1513 for _, alias in ipairs(option._aliases) do
1514 if alias:match("^%-.$") then
1515 table.insert(parts, "-s " .. alias:sub(2))
1516 elseif alias:match("^%-%-.+") then
1517 table.insert(parts, "-l " .. alias:sub(3))
1518 end
1519 end
1520
1521 if option._choices then
1522 table.insert(parts, "-xa '" .. table.concat(option._choices, " ") .. "'")
1523 elseif option._minargs > 0 then
1524 table.insert(parts, "-r")
1525 end
1526
1527 if option._description then
1528 table.insert(parts, "-d '" .. fish_escape(get_short_description(option)) .. "'")
1529 end
1530
1531 table.insert(buf, table.concat(parts, " "))
1532 end
1533
1534 for _, command in ipairs(self._commands) do
1535 command:_fish_complete_help(buf, basename)
1536 end
1537end
1538
1539function Parser:get_fish_complete()
1540 self._basename = base_name(self._name)
1541 assert(self:_is_shell_safe())
1542 local buf = {}
1543
1544 if #self._commands > 0 then
1545 table.insert(buf, ([[
1546function __fish_%s_print_command
1547 set -l cmdline (commandline -poc)
1548 set -l cmd]]):format(self._basename))
1549 self:_fish_get_cmd(buf, 4)
1550 table.insert(buf, ([[
1551 echo "$cmd"
1552end
1553
1554function __fish_%s_using_command
1555 test (__fish_%s_print_command) = "$argv"
1556 and return 0
1557 or return 1
1558end
1559
1560function __fish_%s_seen_command
1561 string match -q "$argv*" (__fish_%s_print_command)
1562 and return 0
1563 or return 1
1564end]]):format(self._basename, self._basename, self._basename, self._basename))
1565 end
1566
1567 self:_fish_complete_help(buf, self._basename)
1568 return table.concat(buf, "\n") .. "\n"
1569end
1570
1571local function get_tip(context, wrong_name)
1572 local context_pool = {}
1573 local possible_name
1574 local possible_names = {}
1575
1576 for name in pairs(context) do
1577 if type(name) == "string" then
1578 for i = 1, #name do
1579 possible_name = name:sub(1, i - 1) .. name:sub(i + 1)
1580
1581 if not context_pool[possible_name] then
1582 context_pool[possible_name] = {}
1583 end
1584
1585 table.insert(context_pool[possible_name], name)
1586 end
1587 end
1588 end
1589
1590 for i = 1, #wrong_name + 1 do
1591 possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1)
1592
1593 if context[possible_name] then
1594 possible_names[possible_name] = true
1595 elseif context_pool[possible_name] then
1596 for _, name in ipairs(context_pool[possible_name]) do
1597 possible_names[name] = true
1598 end
1599 end
1600 end
1601
1602 local first = next(possible_names)
1603
1604 if first then
1605 if next(possible_names, first) then
1606 local possible_names_arr = {}
1607
1608 for name in pairs(possible_names) do
1609 table.insert(possible_names_arr, "'" .. name .. "'")
1610 end
1611
1612 table.sort(possible_names_arr)
1613 return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?"
1614 else
1615 return "\nDid you mean '" .. first .. "'?"
1616 end
1617 else
1618 return ""
1619 end
1620end
1621
1622local ElementState = class({
1623 invocations = 0
1624})
1625
1626function ElementState:__call(state, element)
1627 self.state = state
1628 self.result = state.result
1629 self.element = element
1630 self.target = element._target or element:_get_default_target()
1631 self.action, self.result[self.target] = element:_get_action()
1632 return self
1633end
1634
1635function ElementState:error(fmt, ...)
1636 self.state:error(fmt, ...)
1637end
1638
1639function ElementState:convert(argument, index)
1640 local converter = self.element._convert
1641
1642 if converter then
1643 local ok, err
1644
1645 if type(converter) == "function" then
1646 ok, err = converter(argument)
1647 elseif type(converter[index]) == "function" then
1648 ok, err = converter[index](argument)
1649 else
1650 ok = converter[argument]
1651 end
1652
1653 if ok == nil then
1654 self:error(err and "%s" or "malformed argument '%s'", err or argument)
1655 end
1656
1657 argument = ok
1658 end
1659
1660 return argument
1661end
1662
1663function ElementState:default(mode)
1664 return self.element._defmode:find(mode) and self.element._default
1665end
1666
1667local function bound(noun, min, max, is_max)
1668 local res = ""
1669
1670 if min ~= max then
1671 res = "at " .. (is_max and "most" or "least") .. " "
1672 end
1673
1674 local number = is_max and max or min
1675 return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s")
1676end
1677
1678function ElementState:set_name(alias)
1679 self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name)
1680end
1681
1682function ElementState:invoke()
1683 self.open = true
1684 self.overwrite = false
1685
1686 if self.invocations >= self.element._maxcount then
1687 if self.element._overwrite then
1688 self.overwrite = true
1689 else
1690 local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true)
1691 self:error("%s must be used %s", self.name, num_times_repr)
1692 end
1693 else
1694 self.invocations = self.invocations + 1
1695 end
1696
1697 self.args = {}
1698
1699 if self.element._maxargs <= 0 then
1700 self:close()
1701 end
1702
1703 return self.open
1704end
1705
1706function ElementState:check_choices(argument)
1707 if self.element._choices then
1708 for _, choice in ipairs(self.element._choices) do
1709 if argument == choice then
1710 return
1711 end
1712 end
1713 local choices_list = "'" .. table.concat(self.element._choices, "', '") .. "'"
1714 local is_option = getmetatable(self.element) == Option
1715 self:error("%s%s must be one of %s", is_option and "argument for " or "", self.name, choices_list)
1716 end
1717end
1718
1719function ElementState:pass(argument)
1720 self:check_choices(argument)
1721 argument = self:convert(argument, #self.args + 1)
1722 table.insert(self.args, argument)
1723
1724 if #self.args >= self.element._maxargs then
1725 self:close()
1726 end
1727
1728 return self.open
1729end
1730
1731function ElementState:complete_invocation()
1732 while #self.args < self.element._minargs do
1733 self:pass(self.element._default)
1734 end
1735end
1736
1737function ElementState:close()
1738 if self.open then
1739 self.open = false
1740
1741 if #self.args < self.element._minargs then
1742 if self:default("a") then
1743 self:complete_invocation()
1744 else
1745 if #self.args == 0 then
1746 if getmetatable(self.element) == Argument then
1747 self:error("missing %s", self.name)
1748 elseif self.element._maxargs == 1 then
1749 self:error("%s requires an argument", self.name)
1750 end
1751 end
1752
1753 self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs))
1754 end
1755 end
1756
1757 local args
1758
1759 if self.element._maxargs == 0 then
1760 args = self.args[1]
1761 elseif self.element._maxargs == 1 then
1762 if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then
1763 args = self.args
1764 else
1765 args = self.args[1]
1766 end
1767 else
1768 args = self.args
1769 end
1770
1771 self.action(self.result, self.target, args, self.overwrite)
1772 end
1773end
1774
1775local ParseState = class({
1776 result = {},
1777 options = {},
1778 arguments = {},
1779 argument_i = 1,
1780 element_to_mutexes = {},
1781 mutex_to_element_state = {},
1782 command_actions = {}
1783})
1784
1785function ParseState:__call(parser, error_handler)
1786 self.parser = parser
1787 self.error_handler = error_handler
1788 self.charset = parser:_update_charset()
1789 self:switch(parser)
1790 return self
1791end
1792
1793function ParseState:error(fmt, ...)
1794 self.error_handler(self.parser, fmt:format(...))
1795end
1796
1797function ParseState:switch(parser)
1798 self.parser = parser
1799
1800 if parser._action then
1801 table.insert(self.command_actions, {action = parser._action, name = parser._name})
1802 end
1803
1804 for _, option in ipairs(parser._options) do
1805 option = ElementState(self, option)
1806 table.insert(self.options, option)
1807
1808 for _, alias in ipairs(option.element._aliases) do
1809 self.options[alias] = option
1810 end
1811 end
1812
1813 for _, mutex in ipairs(parser._mutexes) do
1814 for _, element in ipairs(mutex) do
1815 if not self.element_to_mutexes[element] then
1816 self.element_to_mutexes[element] = {}
1817 end
1818
1819 table.insert(self.element_to_mutexes[element], mutex)
1820 end
1821 end
1822
1823 for _, argument in ipairs(parser._arguments) do
1824 argument = ElementState(self, argument)
1825 table.insert(self.arguments, argument)
1826 argument:set_name()
1827 argument:invoke()
1828 end
1829
1830 self.handle_options = parser._handle_options
1831 self.argument = self.arguments[self.argument_i]
1832 self.commands = parser._commands
1833
1834 for _, command in ipairs(self.commands) do
1835 for _, alias in ipairs(command._aliases) do
1836 self.commands[alias] = command
1837 end
1838 end
1839end
1840
1841function ParseState:get_option(name)
1842 local option = self.options[name]
1843
1844 if not option then
1845 self:error("unknown option '%s'%s", name, get_tip(self.options, name))
1846 else
1847 return option
1848 end
1849end
1850
1851function ParseState:get_command(name)
1852 local command = self.commands[name]
1853
1854 if not command then
1855 if #self.commands > 0 then
1856 self:error("unknown command '%s'%s", name, get_tip(self.commands, name))
1857 else
1858 self:error("too many arguments")
1859 end
1860 else
1861 return command
1862 end
1863end
1864
1865function ParseState:check_mutexes(element_state)
1866 if self.element_to_mutexes[element_state.element] then
1867 for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do
1868 local used_element_state = self.mutex_to_element_state[mutex]
1869
1870 if used_element_state and used_element_state ~= element_state then
1871 self:error("%s can not be used together with %s", element_state.name, used_element_state.name)
1872 else
1873 self.mutex_to_element_state[mutex] = element_state
1874 end
1875 end
1876 end
1877end
1878
1879function ParseState:invoke(option, name)
1880 self:close()
1881 option:set_name(name)
1882 self:check_mutexes(option, name)
1883
1884 if option:invoke() then
1885 self.option = option
1886 end
1887end
1888
1889function ParseState:pass(arg)
1890 if self.option then
1891 if not self.option:pass(arg) then
1892 self.option = nil
1893 end
1894 elseif self.argument then
1895 self:check_mutexes(self.argument)
1896
1897 if not self.argument:pass(arg) then
1898 self.argument_i = self.argument_i + 1
1899 self.argument = self.arguments[self.argument_i]
1900 end
1901 else
1902 local command = self:get_command(arg)
1903 self.result[command._target or command._name] = true
1904
1905 if self.parser._command_target then
1906 self.result[self.parser._command_target] = command._name
1907 end
1908
1909 self:switch(command)
1910 end
1911end
1912
1913function ParseState:close()
1914 if self.option then
1915 self.option:close()
1916 self.option = nil
1917 end
1918end
1919
1920function ParseState:finalize()
1921 self:close()
1922
1923 for i = self.argument_i, #self.arguments do
1924 local argument = self.arguments[i]
1925 if #argument.args == 0 and argument:default("u") then
1926 argument:complete_invocation()
1927 else
1928 argument:close()
1929 end
1930 end
1931
1932 if self.parser._require_command and #self.commands > 0 then
1933 self:error("a command is required")
1934 end
1935
1936 for _, option in ipairs(self.options) do
1937 option.name = option.name or ("option '%s'"):format(option.element._name)
1938
1939 if option.invocations == 0 then
1940 if option:default("u") then
1941 option:invoke()
1942 option:complete_invocation()
1943 option:close()
1944 end
1945 end
1946
1947 local mincount = option.element._mincount
1948
1949 if option.invocations < mincount then
1950 if option:default("a") then
1951 while option.invocations < mincount do
1952 option:invoke()
1953 option:close()
1954 end
1955 elseif option.invocations == 0 then
1956 self:error("missing %s", option.name)
1957 else
1958 self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount))
1959 end
1960 end
1961 end
1962
1963 for i = #self.command_actions, 1, -1 do
1964 self.command_actions[i].action(self.result, self.command_actions[i].name)
1965 end
1966end
1967
1968function ParseState:parse(args)
1969 for _, arg in ipairs(args) do
1970 local plain = true
1971
1972 if self.handle_options then
1973 local first = arg:sub(1, 1)
1974
1975 if self.charset[first] then
1976 if #arg > 1 then
1977 plain = false
1978
1979 if arg:sub(2, 2) == first then
1980 if #arg == 2 then
1981 if self.options[arg] then
1982 local option = self:get_option(arg)
1983 self:invoke(option, arg)
1984 else
1985 self:close()
1986 end
1987
1988 self.handle_options = false
1989 else
1990 local equals = arg:find "="
1991 if equals then
1992 local name = arg:sub(1, equals - 1)
1993 local option = self:get_option(name)
1994
1995 if option.element._maxargs <= 0 then
1996 self:error("option '%s' does not take arguments", name)
1997 end
1998
1999 self:invoke(option, name)
2000 self:pass(arg:sub(equals + 1))
2001 else
2002 local option = self:get_option(arg)
2003 self:invoke(option, arg)
2004 end
2005 end
2006 else
2007 for i = 2, #arg do
2008 local name = first .. arg:sub(i, i)
2009 local option = self:get_option(name)
2010 self:invoke(option, name)
2011
2012 if i ~= #arg and option.element._maxargs > 0 then
2013 self:pass(arg:sub(i + 1))
2014 break
2015 end
2016 end
2017 end
2018 end
2019 end
2020 end
2021
2022 if plain then
2023 self:pass(arg)
2024 end
2025 end
2026
2027 self:finalize()
2028 return self.result
2029end
2030
2031function Parser:error(msg)
2032 io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg))
2033 os.exit(1)
2034end
2035
2036-- Compatibility with strict.lua and other checkers:
2037local default_cmdline = rawget(_G, "arg") or {}
2038
2039function Parser:_parse(args, error_handler)
2040 return ParseState(self, error_handler):parse(args or default_cmdline)
2041end
2042
2043function Parser:parse(args)
2044 return self:_parse(args, self.error)
2045end
2046
2047local function xpcall_error_handler(err)
2048 return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2)
2049end
2050
2051function Parser:pparse(args)
2052 local parse_error
2053
2054 local ok, result = xpcall(function()
2055 return self:_parse(args, function(_, err)
2056 parse_error = err
2057 error(err, 0)
2058 end)
2059 end, xpcall_error_handler)
2060
2061 if ok then
2062 return true, result
2063 elseif not parse_error then
2064 error(result, 0)
2065 else
2066 return false, parse_error
2067 end
2068end
2069
2070local argparse = {}
2071
2072argparse.version = "0.7.0"
2073
2074setmetatable(argparse, {__call = function(_, ...)
2075 return Parser(default_cmdline[0]):add_help(true)(...)
2076end})
2077
2078return argparse
diff --git a/src/luarocks/cmd.lua b/src/luarocks/cmd.lua
index 1ead6c4b..f424f488 100644
--- a/src/luarocks/cmd.lua
+++ b/src/luarocks/cmd.lua
@@ -2,16 +2,14 @@
2--- Functions for command-line scripts. 2--- Functions for command-line scripts.
3local cmd = {} 3local cmd = {}
4 4
5local unpack = unpack or table.unpack
6
7local loader = require("luarocks.loader") 5local loader = require("luarocks.loader")
8local util = require("luarocks.util") 6local util = require("luarocks.util")
9local path = require("luarocks.path") 7local path = require("luarocks.path")
10local deps = require("luarocks.deps")
11local cfg = require("luarocks.core.cfg") 8local cfg = require("luarocks.core.cfg")
12local dir = require("luarocks.dir") 9local dir = require("luarocks.dir")
13local fun = require("luarocks.fun") 10local fun = require("luarocks.fun")
14local fs = require("luarocks.fs") 11local fs = require("luarocks.fs")
12local argparse = require("luarocks.argparse")
15 13
16local hc_ok, hardcoded = pcall(require, "luarocks.core.hardcoded") 14local hc_ok, hardcoded = pcall(require, "luarocks.core.hardcoded")
17if not hc_ok then 15if not hc_ok then
@@ -41,11 +39,11 @@ local function check_popen()
41 end 39 end
42end 40end
43 41
44local process_tree_flags 42local process_tree_args
45do 43do
46 local function replace_tree(flags, root, tree) 44 local function replace_tree(args, root, tree)
47 root = dir.normalize(root) 45 root = dir.normalize(root)
48 flags["tree"] = root 46 args.tree = root
49 path.use_tree(tree or root) 47 path.use_tree(tree or root)
50 end 48 end
51 49
@@ -61,44 +59,44 @@ do
61 cfg.deploy_lib_dir = cfg.deploy_lib_dir:gsub("/+$", "") 59 cfg.deploy_lib_dir = cfg.deploy_lib_dir:gsub("/+$", "")
62 end 60 end
63 61
64 process_tree_flags = function(flags, project_dir) 62 process_tree_args = function(args, project_dir)
65 63
66 if flags["global"] then 64 if args.global then
67 cfg.local_by_default = false 65 cfg.local_by_default = false
68 end 66 end
69 67
70 if flags["tree"] then 68 if args.tree then
71 local named = false 69 local named = false
72 for _, tree in ipairs(cfg.rocks_trees) do 70 for _, tree in ipairs(cfg.rocks_trees) do
73 if type(tree) == "table" and flags["tree"] == tree.name then 71 if type(tree) == "table" and args.tree == tree.name then
74 if not tree.root then 72 if not tree.root then
75 return nil, "Configuration error: tree '"..tree.name.."' has no 'root' field." 73 return nil, "Configuration error: tree '"..tree.name.."' has no 'root' field."
76 end 74 end
77 replace_tree(flags, tree.root, tree) 75 replace_tree(args, tree.root, tree)
78 named = true 76 named = true
79 break 77 break
80 end 78 end
81 end 79 end
82 if not named then 80 if not named then
83 local root_dir = fs.absolute_name(flags["tree"]) 81 local root_dir = fs.absolute_name(args.tree)
84 replace_tree(flags, root_dir) 82 replace_tree(args, root_dir)
85 end 83 end
86 elseif flags["local"] then 84 elseif args["local"] then
87 if not cfg.home_tree then 85 if not cfg.home_tree then
88 return nil, "The --local flag is meant for operating in a user's home directory.\n".. 86 return nil, "The --local flag is meant for operating in a user's home directory.\n"..
89 "You are running as a superuser, which is intended for system-wide operation.\n".. 87 "You are running as a superuser, which is intended for system-wide operation.\n"..
90 "To force using the superuser's home, use --tree explicitly." 88 "To force using the superuser's home, use --tree explicitly."
91 else 89 else
92 replace_tree(flags, cfg.home_tree) 90 replace_tree(args, cfg.home_tree)
93 end 91 end
94 elseif flags["project-tree"] then 92 elseif args.project_tree then
95 local tree = flags["project-tree"] 93 local tree = args.project_tree
96 table.insert(cfg.rocks_trees, 1, { name = "project", root = tree } ) 94 table.insert(cfg.rocks_trees, 1, { name = "project", root = tree } )
97 loader.load_rocks_trees() 95 loader.load_rocks_trees()
98 path.use_tree(tree) 96 path.use_tree(tree)
99 elseif cfg.local_by_default then 97 elseif cfg.local_by_default then
100 if cfg.home_tree then 98 if cfg.home_tree then
101 replace_tree(flags, cfg.home_tree) 99 replace_tree(args, cfg.home_tree)
102 end 100 end
103 elseif project_dir then 101 elseif project_dir then
104 local project_tree = project_dir .. "/lua_modules" 102 local project_tree = project_dir .. "/lua_modules"
@@ -119,26 +117,26 @@ do
119 end 117 end
120end 118end
121 119
122local function process_server_flags(flags) 120local function process_server_args(args)
123 if flags["server"] then 121 if args.server then
124 local protocol, pathname = dir.split_url(flags["server"]) 122 local protocol, pathname = dir.split_url(args.server)
125 table.insert(cfg.rocks_servers, 1, protocol.."://"..pathname) 123 table.insert(cfg.rocks_servers, 1, protocol.."://"..pathname)
126 end 124 end
127 125
128 if flags["dev"] then 126 if args.dev then
129 local append_dev = function(s) return dir.path(s, "dev") end 127 local append_dev = function(s) return dir.path(s, "dev") end
130 local dev_servers = fun.traverse(cfg.rocks_servers, append_dev) 128 local dev_servers = fun.traverse(cfg.rocks_servers, append_dev)
131 cfg.rocks_servers = fun.concat(dev_servers, cfg.rocks_servers) 129 cfg.rocks_servers = fun.concat(dev_servers, cfg.rocks_servers)
132 end 130 end
133 131
134 if flags["only-server"] then 132 if args.only_server then
135 if flags["dev"] then 133 if args.dev then
136 return nil, "--only-server cannot be used with --dev" 134 return nil, "--only-server cannot be used with --dev"
137 end 135 end
138 if flags["server"] then 136 if args.server then
139 return nil, "--only-server cannot be used with --server" 137 return nil, "--only-server cannot be used with --server"
140 end 138 end
141 cfg.rocks_servers = { flags["only-server"] } 139 cfg.rocks_servers = { args.only_server }
142 end 140 end
143 141
144 return true 142 return true
@@ -174,7 +172,7 @@ end
174 172
175local init_config 173local init_config
176do 174do
177 local detect_config_via_flags 175 local detect_config_via_args
178 do 176 do
179 local function find_project_dir(project_tree) 177 local function find_project_dir(project_tree)
180 if project_tree then 178 if project_tree then
@@ -193,7 +191,7 @@ do
193 return nil 191 return nil
194 end 192 end
195 193
196 local function find_default_lua_version(flags, project_dir) 194 local function find_default_lua_version(args, project_dir)
197 if hardcoded.FORCE_CONFIG then 195 if hardcoded.FORCE_CONFIG then
198 return nil 196 return nil
199 end 197 end
@@ -212,7 +210,7 @@ do
212 if mod then 210 if mod then
213 local pok, ver = pcall(mod) 211 local pok, ver = pcall(mod)
214 if pok and type(ver) == "string" and ver:match("%d+.%d+") then 212 if pok and type(ver) == "string" and ver:match("%d+.%d+") then
215 if flags["verbose"] then 213 if args.verbose then
216 util.printout("Defaulting to Lua " .. ver .. " based on " .. f .. " ...") 214 util.printout("Defaulting to Lua " .. ver .. " based on " .. f .. " ...")
217 end 215 end
218 return ver 216 return ver
@@ -230,13 +228,13 @@ do
230 end) 228 end)
231 end 229 end
232 230
233 local function detect_lua_via_flags(flags, project_dir) 231 local function detect_lua_via_args(args, project_dir)
234 local lua_version = flags["lua-version"] 232 local lua_version = args.lua_version
235 or find_default_lua_version(flags, project_dir) 233 or find_default_lua_version(args, project_dir)
236 or (project_dir and find_version_from_config(project_dir)) 234 or (project_dir and find_version_from_config(project_dir))
237 235
238 if flags["lua-dir"] then 236 if args.lua_dir then
239 local detected, err = util.find_lua(flags["lua-dir"], lua_version) 237 local detected, err = util.find_lua(args.lua_dir, lua_version)
240 if not detected then 238 if not detected then
241 die(err) 239 die(err)
242 end 240 end
@@ -264,14 +262,14 @@ do
264 return {} 262 return {}
265 end 263 end
266 264
267 detect_config_via_flags = function(flags) 265 detect_config_via_args = function(args)
268 local project_dir, given = find_project_dir(flags["project-tree"]) 266 local project_dir, given = find_project_dir(args.project_tree)
269 local detected = detect_lua_via_flags(flags, project_dir) 267 local detected = detect_lua_via_args(args, project_dir)
270 if flags["lua-version"] then 268 if args.lua_version then
271 detected.given_lua_version = flags["lua-version"] 269 detected.given_lua_version = args.lua_version
272 end 270 end
273 if flags["lua-dir"] then 271 if args.lua_dir then
274 detected.given_lua_dir = flags["lua-dir"] 272 detected.given_lua_dir = args.lua_dir
275 end 273 end
276 if given then 274 if given then
277 detected.given_project_dir = project_dir 275 detected.given_project_dir = project_dir
@@ -281,8 +279,8 @@ do
281 end 279 end
282 end 280 end
283 281
284 init_config = function(flags) 282 init_config = function(args)
285 local detected = detect_config_via_flags(flags) 283 local detected = detect_config_via_args(args)
286 284
287 -- FIXME A quick hack for the experimental Windows build 285 -- FIXME A quick hack for the experimental Windows build
288 if os.getenv("LUAROCKS_CROSS_COMPILING") then 286 if os.getenv("LUAROCKS_CROSS_COMPILING") then
@@ -306,6 +304,135 @@ do
306 end 304 end
307end 305end
308 306
307local variables_help = [[
308Variables:
309 Variables from the "variables" table of the configuration file can be
310 overridden with VAR=VALUE assignments.
311
312]]
313
314local function get_status(status)
315 return status and "ok" or "not found"
316end
317
318local function get_config_text(cfg)
319 local buf = "Configuration:\n Lua version: "..cfg.lua_version.."\n"
320 if cfg.luajit_version then
321 buf = buf.." LuaJIT version: "..cfg.luajit_version.."\n"
322 end
323 buf = buf.."\n Configuration files:\n"
324 local conf = cfg.config_files
325 buf = buf.." System : "..fs.absolute_name(conf.system.file).." ("..get_status(conf.system.found)..")\n"
326 if conf.user.file then
327 buf = buf.." User : "..fs.absolute_name(conf.user.file).." ("..get_status(conf.user.found)..")\n"
328 else
329 buf = buf.." User : disabled in this LuaRocks installation.\n"
330 end
331 if conf.project then
332 buf = buf.." Project : "..fs.absolute_name(conf.project.file).." ("..get_status(conf.project.found)..")\n"
333 end
334 buf = buf.."\n Rocks trees in use: \n"
335 for _, tree in ipairs(cfg.rocks_trees) do
336 if type(tree) == "string" then
337 buf = buf.." "..fs.absolute_name(tree)
338 else
339 local name = tree.name and " (\""..tree.name.."\")" or ""
340 buf = buf.." "..fs.absolute_name(tree.root)..name
341 end
342 end
343
344 return buf.."\n"
345end
346
347local function get_parser(description, cmd_modules)
348 local basename = dir.base_name(program)
349 local parser = argparse(
350 basename, "LuaRocks "..cfg.program_version..", the Lua package manager\n\n"..
351 program.." - "..description, variables_help.."Run '"..basename..
352 "' without any arguments to see the configuration.")
353 :help_max_width(80)
354 :add_help_command()
355 :add_complete_command({
356 help_max_width = 100,
357 summary = "Output a shell completion script.",
358 description = [[
359Output a shell completion script.
360
361Enabling completions for Bash:
362
363 Add the following line to your ~/.bashrc:
364 source <(]]..basename..[[ completion bash)
365 or save the completion script to the local completion directory:
366 ]]..basename..[[ completion bash > ~/.local/share/bash-completion/completions/]]..basename..[[
367
368
369Enabling completions for Zsh:
370
371 Save the completion script to a file in your $fpath.
372 You can add a new directory to your $fpath by adding e.g.
373 fpath=(~/.zfunc $fpath)
374 to your ~/.zshrc.
375 Then run:
376 ]]..basename..[[ completion zsh > ~/.zfunc/_]]..basename..[[
377
378
379Enabling completion for Fish:
380
381 Add the following line to your ~/.config/fish/config.fish:
382 ]]..basename..[[ completion fish | source
383 or save the completion script to the local completion directory:
384 ]]..basename..[[ completion fish > ~/.config/fish/completions/]]..basename..[[.fish
385]]})
386 :command_target("command")
387 :require_command(false)
388
389 parser:flag("--version", "Show version info and exit.")
390 :action(function()
391 util.printout(program.." "..cfg.program_version)
392 util.printout(description)
393 util.printout()
394 os.exit(cmd.errorcodes.OK)
395 end)
396 parser:flag("--dev", "Enable the sub-repositories in rocks servers for "..
397 "rockspecs of in-development versions.")
398 parser:option("--server", "Fetch rocks/rockspecs from this server "..
399 "(takes priority over config file).")
400 parser:option("--only-server", "Fetch rocks/rockspecs from this server only "..
401 "(overrides any entries in the config file).")
402 :argname("<server>")
403 parser:option("--only-sources", "Restrict downloads to paths matching the given URL.")
404 :argname("<url>")
405 parser:option("--namespace", "Specify the rocks server namespace to use.")
406 parser:option("--lua-dir", "Which Lua installation to use.")
407 :argname("<prefix>")
408 parser:option("--lua-version", "Which Lua version to use.")
409 :argname("<ver>")
410 parser:option("--tree", "Which tree to operate on.")
411 parser:flag("--local", "Use the tree in the user's home directory.\n"..
412 "To enable it, see '"..program.." help path'.")
413 parser:flag("--global", "Use the system tree when `local_by_default` is `true`.")
414 parser:flag("--verbose", "Display verbose output of commands executed.")
415 parser:option("--timeout", "Timeout on network operations, in seconds.\n"..
416 "0 means no timeout (wait forever). Default is "..
417 tostring(cfg.connection_timeout)..".")
418 :argname("<seconds>")
419 :convert(tonumber)
420
421 -- Used internally to force the use of a particular project tree
422 parser:option("--project-tree"):hidden(true)
423 -- Compatibility for old names of some options
424 parser:option("--to"):target("tree"):hidden(true)
425 parser:option("--from"):target("server"):hidden(true)
426 parser:option("--only-from"):target("only_server"):hidden(true)
427 parser:option("--only-sources-from"):target("only_sources"):hidden(true)
428
429 for _, module in util.sortedpairs(cmd_modules) do
430 module.add_to_parser(parser)
431 end
432
433 return parser
434end
435
309--- Main command-line processor. 436--- Main command-line processor.
310-- Parses input arguments and calls the appropriate driver function 437-- Parses input arguments and calls the appropriate driver function
311-- to execute the action requested on the command-line, forwarding 438-- to execute the action requested on the command-line, forwarding
@@ -318,7 +445,20 @@ function cmd.run_command(description, commands, external_namespace, ...)
318 445
319 check_popen() 446 check_popen()
320 447
321 local function process_arguments(...) 448 fs.init()
449
450 for _, module_name in ipairs(fs.modules(external_namespace)) do
451 if not commands[module_name] then
452 commands[module_name] = external_namespace.."."..module_name
453 end
454 end
455
456 local cmd_modules = {}
457 for name, module in pairs(commands) do
458 cmd_modules[name] = require(module)
459 end
460
461 local function process_cmdline_vars(...)
322 local args = {...} 462 local args = {...}
323 local cmdline_vars = {} 463 local cmdline_vars = {}
324 local last = #args 464 local last = #args
@@ -340,69 +480,38 @@ function cmd.run_command(description, commands, external_namespace, ...)
340 end 480 end
341 end 481 end
342 end 482 end
343 local nonflags = { util.parse_flags(unpack(args)) }
344 local flags = table.remove(nonflags, 1)
345 if flags.ERROR then
346 die(flags.ERROR.." See --help.")
347 end
348 483
349 -- Compatibility for old names of some flags 484 return args, cmdline_vars
350 if flags["to"] then flags["tree"] = flags["to"] end
351 if flags["from"] then flags["server"] = flags["from"] end
352 if flags["nodeps"] then flags["deps-mode"] = "none" end
353 if flags["only-from"] then flags["only-server"] = flags["only-from"] end
354 if flags["only-sources-from"] then flags["only-sources"] = flags["only-sources-from"] end
355
356 return flags, nonflags, cmdline_vars
357 end 485 end
358 486
359 local flags, nonflags, cmdline_vars = process_arguments(...) 487 local args, cmdline_vars = process_cmdline_vars(...)
488 local parser = get_parser(description, cmd_modules)
489 args = parser:parse(args)
360 490
361 if flags["timeout"] then -- setting it in the config file will kick-in earlier in the process 491 -- Compatibility for old flag
362 local timeout = tonumber(flags["timeout"]) 492 if args.nodeps then
363 if timeout then 493 args.deps_mode = "none"
364 cfg.connection_timeout = timeout
365 else
366 die "Argument error: --timeout expects a numeric argument."
367 end
368 end 494 end
369 495
370 local command 496 if args.timeout then -- setting it in the config file will kick-in earlier in the process
371 if flags["help"] or #nonflags == 0 then 497 cfg.connection_timeout = args.timeout
372 command = "help"
373 else
374 command = table.remove(nonflags, 1)
375 end 498 end
376 command = command:gsub("-", "_")
377 499
378 if command == "config" then 500 if args.command == "config" then
379 if nonflags[1] == "lua_version" and nonflags[2] then 501 if args.key == "lua_version" and args.value then
380 flags["lua-version"] = nonflags[2] 502 args.lua_version = args.value
381 elseif nonflags[1] == "lua_dir" and nonflags[2] then 503 elseif args.key == "lua_dir" and args.value then
382 flags["lua-dir"] = nonflags[2] 504 args.lua_dir = args.value
383 end 505 end
384 end 506 end
385 507
386 if flags["deps-mode"] and not deps.check_deps_mode_flag(flags["deps-mode"]) then
387 die("Invalid entry for --deps-mode.")
388 end
389
390 ----------------------------------------------------------------------------- 508 -----------------------------------------------------------------------------
391 local lua_found, err = init_config(flags) 509 local lua_found, err = init_config(args)
392 if err then 510 if err then
393 die(err) 511 die(err)
394 end 512 end
395 ----------------------------------------------------------------------------- 513 -----------------------------------------------------------------------------
396 514
397 if flags["version"] then
398 util.printout(program.." "..cfg.program_version)
399 util.printout(description)
400 util.printout()
401 os.exit(cmd.errorcodes.OK)
402 end
403
404 fs.init()
405
406 -- if the Lua interpreter wasn't explicitly found before cfg.init, 515 -- if the Lua interpreter wasn't explicitly found before cfg.init,
407 -- try again now. 516 -- try again now.
408 if not lua_found then 517 if not lua_found then
@@ -414,7 +523,7 @@ function cmd.run_command(description, commands, external_namespace, ...)
414 if not lua_found then 523 if not lua_found then
415 util.warning("Could not find a Lua " .. cfg.lua_version .. " interpreter in your PATH. " .. 524 util.warning("Could not find a Lua " .. cfg.lua_version .. " interpreter in your PATH. " ..
416 "Modules may not install with the correct configurations. " .. 525 "Modules may not install with the correct configurations. " ..
417 "You may want to specify to the path prefix to your build " .. 526 "You may want to specify the path prefix to your build " ..
418 "of Lua " .. cfg.lua_version .. " using --lua-dir") 527 "of Lua " .. cfg.lua_version .. " using --lua-dir")
419 end 528 end
420 cfg.lua_found = lua_found 529 cfg.lua_found = lua_found
@@ -423,13 +532,7 @@ function cmd.run_command(description, commands, external_namespace, ...)
423 cfg.project_dir = fs.absolute_name(cfg.project_dir) 532 cfg.project_dir = fs.absolute_name(cfg.project_dir)
424 end 533 end
425 534
426 for _, module_name in ipairs(fs.modules(external_namespace)) do 535 if args.verbose then
427 if not commands[module_name] then
428 commands[module_name] = external_namespace.."."..module_name
429 end
430 end
431
432 if flags["verbose"] then
433 cfg.verbose = true 536 cfg.verbose = true
434 fs.verbose() 537 fs.verbose()
435 end 538 end
@@ -438,24 +541,22 @@ function cmd.run_command(description, commands, external_namespace, ...)
438 die("Current directory does not exist. Please run LuaRocks from an existing directory.") 541 die("Current directory does not exist. Please run LuaRocks from an existing directory.")
439 end 542 end
440 543
441 ok, err = process_tree_flags(flags, cfg.project_dir) 544 local ok, err = process_tree_args(args, cfg.project_dir)
442 if not ok then 545 if not ok then
443 die(err) 546 die(err)
444 end 547 end
445 548
446 ok, err = process_server_flags(flags) 549 ok, err = process_server_args(args)
447 if not ok then 550 if not ok then
448 die(err) 551 die(err)
449 end 552 end
450 553
451 if flags["only-sources"] then 554 if args.only_sources then
452 cfg.only_sources_from = flags["only-sources"] 555 cfg.only_sources_from = args.only_sources
453 end 556 end
454 557
455 if command ~= "help" then 558 for k, v in pairs(cmdline_vars) do
456 for k, v in pairs(cmdline_vars) do 559 cfg.variables[k] = v
457 cfg.variables[k] = v
458 end
459 end 560 end
460 561
461 -- if running as superuser, use system cache dir 562 -- if running as superuser, use system cache dir
@@ -463,22 +564,23 @@ function cmd.run_command(description, commands, external_namespace, ...)
463 cfg.local_cache = dir.path(fs.system_cache_dir(), "luarocks") 564 cfg.local_cache = dir.path(fs.system_cache_dir(), "luarocks")
464 end 565 end
465 566
466 if commands[command] then 567 if not args.command then
467 local cmd_mod = require(commands[command]) 568 parser:epilog(variables_help..get_config_text(cfg))
468 local call_ok, ok, err, exitcode = xpcall(function() 569 util.printout()
469 if command == "help" then 570 util.printout(parser:get_help())
470 return cmd_mod.command(description, commands, unpack(nonflags)) 571 util.printout()
471 else 572 os.exit(cmd.errorcodes.OK)
472 return cmd_mod.command(flags, unpack(nonflags)) 573 end
473 end 574
474 end, error_handler) 575 local cmd_mod = cmd_modules[args.command]
475 if not call_ok then 576 local call_ok, ok, err, exitcode = xpcall(function()
476 die(ok, cmd.errorcodes.CRASH) 577 return cmd_mod.command(args)
477 elseif not ok then 578 end, error_handler)
478 die(err, exitcode) 579
479 end 580 if not call_ok then
480 else 581 die(ok, cmd.errorcodes.CRASH)
481 die("Unknown command: "..command) 582 elseif not ok then
583 die(err, exitcode)
482 end 584 end
483 util.run_scheduled_functions() 585 util.run_scheduled_functions()
484end 586end
diff --git a/src/luarocks/cmd/build.lua b/src/luarocks/cmd/build.lua
index 23a94fa7..0654b4eb 100644
--- a/src/luarocks/cmd/build.lua
+++ b/src/luarocks/cmd/build.lua
@@ -18,40 +18,20 @@ local search = require("luarocks.search")
18local make = require("luarocks.cmd.make") 18local make = require("luarocks.cmd.make")
19local cmd = require("luarocks.cmd") 19local cmd = require("luarocks.cmd")
20 20
21cmd_build.help_summary = "build/compile a rock." 21function cmd_build.add_to_parser(parser)
22cmd_build.help_arguments = "[<flags...>] {<rockspec>|<rock>|<name> [<version>]}" 22 local cmd = parser:command("build", "Build and install a rock, compiling its C parts if any.\n"..
23cmd_build.help = [[ 23 "If no arguments are given, behaves as luarocks make.", util.see_also())
24Build and install a rock, compiling its C parts if any. 24 :summary("Build/compile a rock.")
25Argument may be a rockspec file, a source rock file 25
26or the name of a rock to be fetched from a repository. 26 cmd:argument("rock", "A rockspec file, a source rock file, or the name of "..
27 27 "a rock to be fetched from a repository.")
28--pack-binary-rock Do not install rock. Instead, produce a .rock file 28 :args("?")
29 with the contents of compilation in the current 29 cmd:argument("version", "Rock version.")
30 directory. 30 :args("?")
31 31
32--keep Do not remove previously installed versions of the 32 cmd:flag("--only-deps", "Installs only the dependencies of the rock.")
33 rock after building a new one. This behavior can 33 make.cmd_options(cmd)
34 be made permanent by setting keep_other_versions=true 34end
35 in the configuration file.
36
37--branch=<name> Override the `source.branch` field in the loaded
38 rockspec. Allows to specify a different branch to
39 fetch. Particularly for "dev" rocks.
40
41--only-deps Installs only the dependencies of the rock.
42
43--verify Verify signature of the rockspec or src.rock being
44 built. If the rockspec or src.rock is being downloaded,
45 LuaRocks will attempt to download the signature as well.
46 Otherwise, the signature file should be already
47 available locally in the same directory.
48 You need the signer’s public key in your local
49 keyring for this option to work properly.
50
51--sign To be used with --pack-binary-rock. Also produce
52 a signature file for the generated .rock file.
53
54]]..util.deps_mode_help()
55 35
56--- Build and install a rock. 36--- Build and install a rock.
57-- @param rock_filename string: local or remote filename of a rock. 37-- @param rock_filename string: local or remote filename of a rock.
@@ -132,58 +112,54 @@ local function remove_doc_dir(name, version)
132end 112end
133 113
134--- Driver function for "build" command. 114--- Driver function for "build" command.
135-- @param name string: A local or remote rockspec or rock file.
136-- If a package name is given, forwards the request to "search" and, 115-- If a package name is given, forwards the request to "search" and,
137-- if returned a result, installs the matching rock. 116-- if returned a result, installs the matching rock.
138-- @param version string: When passing a package name, a version number may 117-- When passing a package name, a version number may also be given.
139-- also be given.
140-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an 118-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an
141-- error message otherwise. exitcode is optionally returned. 119-- error message otherwise. exitcode is optionally returned.
142function cmd_build.command(flags, name, version) 120function cmd_build.command(args)
143 assert(type(name) == "string" or not name) 121 if not args.rock then
144 assert(type(version) == "string" or not version) 122 return make.command(args)
145
146 if not name then
147 return make.command(flags)
148 end 123 end
149 124
150 name = util.adjust_name_and_namespace(name, flags) 125 local name = util.adjust_name_and_namespace(args.rock, args)
151 126
152 local opts = build.opts({ 127 local opts = build.opts({
153 need_to_fetch = true, 128 need_to_fetch = true,
154 minimal_mode = false, 129 minimal_mode = false,
155 deps_mode = deps.get_deps_mode(flags), 130 deps_mode = deps.get_deps_mode(args),
156 build_only_deps = not not flags["only-deps"], 131 build_only_deps = not not args.only_deps,
157 namespace = flags["namespace"], 132 namespace = args.namespace,
158 branch = not not flags["branch"], 133 branch = not not args.branch,
159 verify = not not flags["verify"], 134 verify = not not args.verify,
160 }) 135 })
161 136
162 if flags["sign"] and not flags["pack-binary-rock"] then 137 if args.sign and not args.pack_binary_rock then
163 return nil, "In the build command, --sign is meant to be used only with --pack-binary-rock" 138 return nil, "In the build command, --sign is meant to be used only with --pack-binary-rock"
164 end 139 end
165 140
166 if flags["pack-binary-rock"] then 141 if args.pack_binary_rock then
167 return pack.pack_binary_rock(name, version, flags["sign"], function() 142 return pack.pack_binary_rock(name, args.version, args.sign, function()
168 opts.build_only_deps = false 143 opts.build_only_deps = false
169 local status, err, errcode = do_build(name, version, opts) 144 local status, err, errcode = do_build(name, args.version, opts)
170 if status and flags["no-doc"] then 145 if status and args.no_doc then
171 remove_doc_dir(name, version) 146 remove_doc_dir(name, args.version)
172 end 147 end
173 return status, err, errcode 148 return status, err, errcode
174 end) 149 end)
175 end 150 end
176 151
177 local ok, err = fs.check_command_permissions(flags) 152 local ok, err = fs.check_command_permissions(args)
178 if not ok then 153 if not ok then
179 return nil, err, cmd.errorcodes.PERMISSIONDENIED 154 return nil, err, cmd.errorcodes.PERMISSIONDENIED
180 end 155 end
181 156
182 ok, err = do_build(name, version, opts) 157 ok, err = do_build(name, args.version, opts)
183 if not ok then return nil, err end 158 if not ok then return nil, err end
159 local version
184 name, version = ok, err 160 name, version = ok, err
185 161
186 if flags["no-doc"] then 162 if args.no_doc then
187 remove_doc_dir(name, version) 163 remove_doc_dir(name, version)
188 end 164 end
189 165
@@ -191,15 +167,15 @@ function cmd_build.command(flags, name, version)
191 util.printout("Stopping after installing dependencies for " ..name.." "..version) 167 util.printout("Stopping after installing dependencies for " ..name.." "..version)
192 util.printout() 168 util.printout()
193 else 169 else
194 if (not flags["keep"]) and not cfg.keep_other_versions then 170 if (not args.keep) and not cfg.keep_other_versions then
195 local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) 171 local ok, err = remove.remove_other_versions(name, version, args.force, args.force_fast)
196 if not ok then 172 if not ok then
197 util.printerr(err) 173 util.printerr(err)
198 end 174 end
199 end 175 end
200 end 176 end
201 177
202 writer.check_dependencies(nil, deps.get_deps_mode(flags)) 178 writer.check_dependencies(nil, deps.get_deps_mode(args))
203 return name, version 179 return name, version
204end 180end
205 181
diff --git a/src/luarocks/cmd/config.lua b/src/luarocks/cmd/config.lua
index b3cb5e07..59dafdb5 100644
--- a/src/luarocks/cmd/config.lua
+++ b/src/luarocks/cmd/config.lua
@@ -9,21 +9,22 @@ local deps = require("luarocks.deps")
9local dir = require("luarocks.dir") 9local dir = require("luarocks.dir")
10local fs = require("luarocks.fs") 10local fs = require("luarocks.fs")
11 11
12config_cmd.help_summary = "Query information about the LuaRocks configuration." 12function config_cmd.add_to_parser(parser)
13config_cmd.help_arguments = "(<key> | <key> <value> --scope=<scope> | <key> --unset --scope=<scope> | )" 13 local cmd = parser:command("config", [[
14config_cmd.help = [[ 14Query information about the LuaRocks configuration.
15* When given a configuration key, it prints the value of that key 15
16 according to the currently active configuration (taking into account 16* When given a configuration key, it prints the value of that key according to
17 all config files and any command-line flags passed) 17 the currently active configuration (taking into account all config files and
18 any command-line flags passed)
18 19
19 Examples: 20 Examples:
20 luarocks config lua_interpreter 21 luarocks config lua_interpreter
21 luarocks config variables.LUA_INCDIR 22 luarocks config variables.LUA_INCDIR
22 luarocks config lua_version 23 luarocks config lua_version
23 24
24* When given a configuration key and a value, 25* When given a configuration key and a value, it overwrites the config file (see
25 it overwrites the config file (see the --scope option below to determine which) 26 the --scope option below to determine which) and replaces the value of the
26 and replaces the value of the given key with the given value. 27 given key with the given value.
27 28
28 * `lua_dir` is a special key as it checks for a valid Lua installation 29 * `lua_dir` is a special key as it checks for a valid Lua installation
29 (equivalent to --lua-dir) and sets several keys at once. 30 (equivalent to --lua-dir) and sets several keys at once.
@@ -35,33 +36,42 @@ config_cmd.help = [[
35 luarocks config lua_dir /usr/local 36 luarocks config lua_dir /usr/local
36 luarocks config lua_version 5.3 37 luarocks config lua_version 5.3
37 38
38* When given a configuration key and --unset, 39* When given a configuration key and --unset, it overwrites the config file (see
39 it overwrites the config file (see the --scope option below to determine which) 40 the --scope option below to determine which) and deletes that key from the
40 and deletes that key from the file. 41 file.
41 42
42 Example: luarocks config variables.OPENSSL_DIR --unset 43 Example: luarocks config variables.OPENSSL_DIR --unset
43 44
44* When given no arguments, it prints the entire currently active 45* When given no arguments, it prints the entire currently active configuration,
45 configuration, resulting from reading the config files from 46 resulting from reading the config files from all scopes.
46 all scopes. 47
47 48 Example: luarocks config]], util.see_also([[
48 Example: luarocks config 49 https://github.com/luarocks/luarocks/wiki/Config-file-format
49 50 for detailed information on the LuaRocks config file format.
50OPTIONS 51]]))
51--scope=<scope> The scope indicates which config file should be rewritten. 52 :summary("Query information about the LuaRocks configuration.")
52 Accepted values are "system", "user" or "project". 53
53 * Using a wrapper created with `luarocks init`, 54 cmd:argument("key", "The configuration key.")
54 the default is "project". 55 :args("?")
55 * Using --local (or when `local_by_default` is `true`), 56 cmd:argument("value", "The configuration value.")
56 the default is "user". 57 :args("?")
57 * Otherwise, the default is "system". 58
58 59 cmd:option("--scope", "The scope indicates which config file should be rewritten.\n"..
59--json Output as JSON 60 '* Using a wrapper created with `luarocks init`, the default is "project".\n'..
60]] 61 '* Using --local (or when `local_by_default` is `true`), the default is "user".\n'..
61config_cmd.help_see_also = [[ 62 '* Otherwise, the default is "system".')
62 https://github.com/luarocks/luarocks/wiki/Config-file-format 63 :choices({"system", "user", "project"})
63 for detailed information on the LuaRocks config file format. 64 cmd:flag("--unset", "Delete the key from the configuration file.")
64]] 65 cmd:flag("--json", "Output as JSON.")
66
67 -- Deprecated flags
68 cmd:flag("--lua-incdir"):hidden(true)
69 cmd:flag("--lua-libdir"):hidden(true)
70 cmd:flag("--lua-ver"):hidden(true)
71 cmd:flag("--system-config"):hidden(true)
72 cmd:flag("--user-config"):hidden(true)
73 cmd:flag("--rock-trees"):hidden(true)
74end
65 75
66local function config_file(conf) 76local function config_file(conf)
67 print(dir.normalize(conf.file)) 77 print(dir.normalize(conf.file))
@@ -218,45 +228,40 @@ local function write_entries(keys, scope, do_unset)
218 end 228 end
219end 229end
220 230
221local function check_scope(flags) 231local function get_scope(args)
222 local scope = flags["scope"] 232 return args.scope
223 or (flags["local"] and "user") 233 or (args["local"] and "user")
224 or (flags["project-tree"] and "project") 234 or (args.project_tree and "project")
225 or (cfg.local_by_default and "user") 235 or (cfg.local_by_default and "user")
226 or "system" 236 or "system"
227 if scope ~= "system" and scope ~= "user" and scope ~= "project" then
228 return nil, "Valid values for scope are: system, user, project"
229 end
230
231 return scope
232end 237end
233 238
234--- Driver function for "config" command. 239--- Driver function for "config" command.
235-- @return boolean: True if succeeded, nil on errors. 240-- @return boolean: True if succeeded, nil on errors.
236function config_cmd.command(flags, var, val) 241function config_cmd.command(args)
237 deps.check_lua_incdir(cfg.variables) 242 deps.check_lua_incdir(cfg.variables)
238 deps.check_lua_libdir(cfg.variables) 243 deps.check_lua_libdir(cfg.variables)
239 244
240 -- deprecated flags 245 -- deprecated flags
241 if flags["lua-incdir"] then 246 if args.lua_incdir then
242 print(cfg.variables.LUA_INCDIR) 247 print(cfg.variables.LUA_INCDIR)
243 return true 248 return true
244 end 249 end
245 if flags["lua-libdir"] then 250 if args.lua_libdir then
246 print(cfg.variables.LUA_LIBDIR) 251 print(cfg.variables.LUA_LIBDIR)
247 return true 252 return true
248 end 253 end
249 if flags["lua-ver"] then 254 if args.lua_ver then
250 print(cfg.lua_version) 255 print(cfg.lua_version)
251 return true 256 return true
252 end 257 end
253 if flags["system-config"] then 258 if args.system_config then
254 return config_file(cfg.config_files.system) 259 return config_file(cfg.config_files.system)
255 end 260 end
256 if flags["user-config"] then 261 if args.user_config then
257 return config_file(cfg.config_files.user) 262 return config_file(cfg.config_files.user)
258 end 263 end
259 if flags["rock-trees"] then 264 if args.rock_trees then
260 for _, tree in ipairs(cfg.rocks_trees) do 265 for _, tree in ipairs(cfg.rocks_trees) do
261 if type(tree) == "string" then 266 if type(tree) == "string" then
262 util.printout(dir.normalize(tree)) 267 util.printout(dir.normalize(tree))
@@ -268,29 +273,22 @@ function config_cmd.command(flags, var, val)
268 return true 273 return true
269 end 274 end
270 275
271 if var == "lua_version" and val then 276 if args.key == "lua_version" and args.value then
272 local scope, err = check_scope(flags) 277 local scope = get_scope(args)
273 if not scope then
274 return nil, err
275 end
276
277 if scope == "project" and not cfg.config_files.project then 278 if scope == "project" and not cfg.config_files.project then
278 return nil, "Current directory is not part of a project. You may want to run `luarocks init`." 279 return nil, "Current directory is not part of a project. You may want to run `luarocks init`."
279 end 280 end
280 281
281 local prefix = dir.dir_name(cfg.config_files[scope].file) 282 local prefix = dir.dir_name(cfg.config_files[scope].file)
282 local ok, err = persist.save_default_lua_version(prefix, val) 283 local ok, err = persist.save_default_lua_version(prefix, args.value)
283 if not ok then 284 if not ok then
284 return nil, "could not set default Lua version: " .. err 285 return nil, "could not set default Lua version: " .. err
285 end 286 end
286 print("Lua version will default to " .. val .. " in " .. prefix) 287 print("Lua version will default to " .. args.value .. " in " .. prefix)
287 end 288 end
288 289
289 if var == "lua_dir" and val then 290 if args.key == "lua_dir" and args.value then
290 local scope, err = check_scope(flags) 291 local scope = get_scope(args)
291 if not scope then
292 return nil, err
293 end
294 local keys = { 292 local keys = {
295 ["variables.LUA_DIR"] = cfg.variables.LUA_DIR, 293 ["variables.LUA_DIR"] = cfg.variables.LUA_DIR,
296 ["variables.LUA_BINDIR"] = cfg.variables.LUA_BINDIR, 294 ["variables.LUA_BINDIR"] = cfg.variables.LUA_BINDIR,
@@ -298,25 +296,21 @@ function config_cmd.command(flags, var, val)
298 ["variables.LUA_LIBDIR"] = cfg.variables.LUA_LIBDIR, 296 ["variables.LUA_LIBDIR"] = cfg.variables.LUA_LIBDIR,
299 ["lua_interpreter"] = cfg.lua_interpreter, 297 ["lua_interpreter"] = cfg.lua_interpreter,
300 } 298 }
301 return write_entries(keys, scope, flags["unset"]) 299 return write_entries(keys, scope, args.unset)
302 end 300 end
303 301
304 if var then 302 if args.key then
305 if val or flags["unset"] then 303 if args.value or args.unset then
306 local scope, err = check_scope(flags) 304 local scope = get_scope(args)
307 if not scope then 305 return write_entries({ [args.key] = args.value }, scope, args.unset)
308 return nil, err
309 end
310
311 return write_entries({ [var] = val }, scope, flags["unset"])
312 else 306 else
313 return print_entry(var, cfg, flags["json"]) 307 return print_entry(args.key, cfg, args.json)
314 end 308 end
315 end 309 end
316 310
317 local cleancfg = cleanup(cfg) 311 local cleancfg = cleanup(cfg)
318 312
319 if flags["json"] then 313 if args.json then
320 return print_json(cleancfg) 314 return print_json(cleancfg)
321 else 315 else
322 print(persist.save_from_table_to_string(cleancfg)) 316 print(persist.save_from_table_to_string(cleancfg))
diff --git a/src/luarocks/cmd/doc.lua b/src/luarocks/cmd/doc.lua
index a2472be4..4b3335d8 100644
--- a/src/luarocks/cmd/doc.lua
+++ b/src/luarocks/cmd/doc.lua
@@ -12,19 +12,22 @@ local fetch = require("luarocks.fetch")
12local fs = require("luarocks.fs") 12local fs = require("luarocks.fs")
13local download = require("luarocks.download") 13local download = require("luarocks.download")
14 14
15doc.help_summary = "Show documentation for an installed rock." 15function doc.add_to_parser(parser)
16 16 local cmd = parser:command("doc", "Show documentation for an installed rock.\n\n"..
17doc.help = [[ 17 "Without any flags, tries to load the documentation using a series of heuristics.\n"..
18<argument> is an existing package name. 18 "With flags, return only the desired information.", util.see_also([[
19Without any flags, tries to load the documentation 19 For more information about a rock, see the 'show' command.
20using a series of heuristics. 20]]))
21With these flags, return only the desired information: 21 :summary("Show documentation for an installed rock.")
22 22
23--home Open the home page of project. 23 cmd:argument("rock", "Name of the rock.")
24--list List documentation files only. 24 cmd:argument("version", "Version of the rock.")
25 25 :args("?")
26For more information about a rock, see the 'show' command. 26
27]] 27 cmd:flag("--home", "Open the home page of project.")
28 cmd:flag("--list", "List documentation files only.")
29 cmd:flag("--porcelain", "Produce machine-friendly output.")
30end
28 31
29local function show_homepage(homepage, name, version) 32local function show_homepage(homepage, name, version)
30 if not homepage then 33 if not homepage then
@@ -54,17 +57,12 @@ local function try_to_open_homepage(name, version)
54end 57end
55 58
56--- Driver function for "doc" command. 59--- Driver function for "doc" command.
57-- @param name or nil: an existing package name.
58-- @param version string or nil: a version may also be passed.
59-- @return boolean: True if succeeded, nil on errors. 60-- @return boolean: True if succeeded, nil on errors.
60function doc.command(flags, name, version) 61function doc.command(args)
61 if not name then 62 local name = util.adjust_name_and_namespace(args.rock, args)
62 return nil, "Argument missing. "..util.see_help("doc") 63 local version = args.version
63 end
64
65 name = util.adjust_name_and_namespace(name, flags)
66 local query = queries.new(name, version) 64 local query = queries.new(name, version)
67 local iname, iversion, repo = search.pick_installed_rock(query, flags["tree"]) 65 local iname, iversion, repo = search.pick_installed_rock(query, args.tree)
68 if not iname then 66 if not iname then
69 util.printout(name..(version and " "..version or "").." is not installed. Looking for it in the rocks servers...") 67 util.printout(name..(version and " "..version or "").." is not installed. Looking for it in the rocks servers...")
70 return try_to_open_homepage(name, version) 68 return try_to_open_homepage(name, version)
@@ -75,7 +73,7 @@ function doc.command(flags, name, version)
75 if not rockspec then return nil,err end 73 if not rockspec then return nil,err end
76 local descript = rockspec.description or {} 74 local descript = rockspec.description or {}
77 75
78 if flags["home"] then 76 if args.home then
79 return show_homepage(descript.homepage, name, version) 77 return show_homepage(descript.homepage, name, version)
80 end 78 end
81 79
@@ -91,7 +89,7 @@ function doc.command(flags, name, version)
91 end 89 end
92 end 90 end
93 if not docdir then 91 if not docdir then
94 if descript.homepage and not flags["list"] then 92 if descript.homepage and not args.list then
95 util.printout("Local documentation directory not found -- opening "..descript.homepage.." ...") 93 util.printout("Local documentation directory not found -- opening "..descript.homepage.." ...")
96 fs.browser(descript.homepage) 94 fs.browser(descript.homepage)
97 return true 95 return true
@@ -105,7 +103,7 @@ function doc.command(flags, name, version)
105 local extensions = { htmlpatt, "%.md$", "%.txt$", "%.textile$", "" } 103 local extensions = { htmlpatt, "%.md$", "%.txt$", "%.textile$", "" }
106 local basenames = { "index", "readme", "manual" } 104 local basenames = { "index", "readme", "manual" }
107 105
108 local porcelain = flags["porcelain"] 106 local porcelain = args.porcelain
109 if #files > 0 then 107 if #files > 0 then
110 util.title("Documentation files for "..name.." "..version, porcelain) 108 util.title("Documentation files for "..name.." "..version, porcelain)
111 if porcelain then 109 if porcelain then
@@ -120,7 +118,7 @@ function doc.command(flags, name, version)
120 end 118 end
121 end 119 end
122 120
123 if flags["list"] then 121 if args.list then
124 return true 122 return true
125 end 123 end
126 124
diff --git a/src/luarocks/cmd/download.lua b/src/luarocks/cmd/download.lua
index 50c10c0c..5032d580 100644
--- a/src/luarocks/cmd/download.lua
+++ b/src/luarocks/cmd/download.lua
@@ -6,42 +6,44 @@ local cmd_download = {}
6local util = require("luarocks.util") 6local util = require("luarocks.util")
7local download = require("luarocks.download") 7local download = require("luarocks.download")
8 8
9cmd_download.help_summary = "Download a specific rock file from a rocks server." 9function cmd_download.add_to_parser(parser)
10cmd_download.help_arguments = "[--all] [--arch=<arch> | --source | --rockspec] [<name> [<version>]]" 10 local cmd = parser:command("download", "Download a specific rock file from a rocks server.", util.see_also())
11cmd_download.help = [[ 11
12--all Download all files if there are multiple matches. 12 cmd:argument("name", "Name of the rock.")
13--source Download .src.rock if available. 13 :args("?")
14--rockspec Download .rockspec if available. 14 cmd:argument("version", "Version of the rock.")
15--arch=<arch> Download rock for a specific architecture. 15 :args("?")
16]] 16
17 cmd:flag("--all", "Download all files if there are multiple matches.")
18 cmd:mutex(
19 cmd:flag("--source", "Download .src.rock if available."),
20 cmd:flag("--rockspec", "Download .rockspec if available."),
21 cmd:option("--arch", "Download rock for a specific architecture."))
22end
17 23
18--- Driver function for the "download" command. 24--- Driver function for the "download" command.
19-- @param name string: a rock name.
20-- @param version string or nil: if the name of a package is given, a
21-- version may also be passed.
22-- @return boolean or (nil, string): true if successful or nil followed 25-- @return boolean or (nil, string): true if successful or nil followed
23-- by an error message. 26-- by an error message.
24function cmd_download.command(flags, name, version) 27function cmd_download.command(args)
25 assert(type(version) == "string" or not version) 28 if not args.name and not args.all then
26 if type(name) ~= "string" and not flags["all"] then
27 return nil, "Argument missing. "..util.see_help("download") 29 return nil, "Argument missing. "..util.see_help("download")
28 end 30 end
29 31
30 name = util.adjust_name_and_namespace(name, flags) 32 local name = util.adjust_name_and_namespace(args.name, args)
31 33
32 if not name then name, version = "", "" end 34 if not name then name, args.version = "", "" end
33 35
34 local arch 36 local arch
35 37
36 if flags["source"] then 38 if args.source then
37 arch = "src" 39 arch = "src"
38 elseif flags["rockspec"] then 40 elseif args.rockspec then
39 arch = "rockspec" 41 arch = "rockspec"
40 elseif flags["arch"] then 42 elseif args.arch then
41 arch = flags["arch"] 43 arch = args.arch
42 end 44 end
43 45
44 local dl, err = download.download(arch, name:lower(), version, flags["all"]) 46 local dl, err = download.download(arch, name:lower(), args.version, args.all)
45 return dl and true, err 47 return dl and true, err
46end 48end
47 49
diff --git a/src/luarocks/cmd/help.lua b/src/luarocks/cmd/help.lua
deleted file mode 100644
index b3d937e1..00000000
--- a/src/luarocks/cmd/help.lua
+++ /dev/null
@@ -1,139 +0,0 @@
1
2--- Module implementing the LuaRocks "help" command.
3-- This is a generic help display module, which
4-- uses a global table called "commands" to find commands
5-- to show help for; each command should be represented by a
6-- table containing "help" and "help_summary" fields.
7local help = {}
8
9local util = require("luarocks.util")
10local cfg = require("luarocks.core.cfg")
11local dir = require("luarocks.dir")
12local fs = require("luarocks.fs")
13
14local program = util.this_program("luarocks")
15
16help.help_summary = "Help on commands. Type '"..program.." help <command>' for more."
17
18help.help_arguments = "[<command>]"
19help.help = [[
20<command> is the command to show help for.
21]]
22
23local function print_banner()
24 util.printout("\nLuaRocks "..cfg.program_version..", the Lua package manager")
25end
26
27local function print_section(section)
28 util.printout("\n"..section)
29end
30
31local function get_status(status)
32 if status then
33 return "ok"
34 else
35 return "not found"
36 end
37end
38
39--- Driver function for the "help" command.
40-- @param command string or nil: command to show help for; if not
41-- given, help summaries for all commands are shown.
42-- @return boolean or (nil, string): true if there were no errors
43-- or nil and an error message if an invalid command was requested.
44function help.command(description, commands, command)
45 assert(type(description) == "string")
46 assert(type(commands) == "table")
47
48 if not command then
49 print_banner()
50 print_section("NAME")
51 util.printout("\t"..program..[[ - ]]..description)
52 print_section("SYNOPSIS")
53 util.printout("\t"..program..[[ [<flags...>] [VAR=VALUE]... <command> [<argument>] ]])
54 print_section("GENERAL OPTIONS")
55 util.printout([[
56 These apply to all commands, as appropriate:
57
58 --dev Enable the sub-repositories in rocks servers
59 for rockspecs of in-development versions
60 --server=<server> Fetch rocks/rockspecs from this server
61 (takes priority over config file)
62 --only-server=<server> Fetch rocks/rockspecs from this server only
63 (overrides any entries in the config file)
64 --only-sources=<url> Restrict downloads to paths matching the
65 given URL.
66 --lua-dir=<prefix> Which Lua installation to use.
67 --lua-version=<ver> Which Lua version to use.
68 --tree=<tree> Which tree to operate on.
69 --local Use the tree in the user's home directory.
70 To enable it, see ']]..program..[[ help path'.
71 --global Use the system tree when `local_by_default` is `true`.
72 --verbose Display verbose output of commands executed.
73 --timeout=<seconds> Timeout on network operations, in seconds.
74 0 means no timeout (wait forever).
75 Default is ]]..tostring(cfg.connection_timeout)..[[.]])
76 print_section("VARIABLES")
77 util.printout([[
78 Variables from the "variables" table of the configuration file
79 can be overridden with VAR=VALUE assignments.]])
80 print_section("COMMANDS")
81 for name, modname in util.sortedpairs(commands) do
82 local cmd = require(modname)
83 util.printout("", name)
84 util.printout("\t", cmd.help_summary)
85 end
86 print_section("CONFIGURATION")
87 util.printout("\tLua version: " .. cfg.lua_version)
88 local ljv = util.get_luajit_version()
89 if ljv then
90 util.printout("\tLuaJIT version: " .. ljv)
91 end
92 util.printout()
93 util.printout("\tConfiguration files:")
94 local conf = cfg.config_files
95 util.printout("\t\tSystem : ".. fs.absolute_name(conf.system.file) .. " (" .. get_status(conf.system.found) ..")")
96 if conf.user.file then
97 util.printout("\t\tUser : ".. fs.absolute_name(conf.user.file) .. " (" .. get_status(conf.user.found) ..")")
98 else
99 util.printout("\t\tUser : disabled in this LuaRocks installation.")
100 end
101 if conf.project then
102 util.printout("\t\tProject : ".. fs.absolute_name(conf.project.file) .. " (" .. get_status(conf.project.found) ..")")
103 end
104 util.printout()
105 util.printout("\tRocks trees in use: ")
106 for _, tree in ipairs(cfg.rocks_trees) do
107 if type(tree) == "string" then
108 util.printout("\t\t"..fs.absolute_name(tree))
109 else
110 local name = tree.name and " (\""..tree.name.."\")" or ""
111 util.printout("\t\t"..fs.absolute_name(tree.root)..name)
112 end
113 end
114 util.printout()
115 else
116 command = command:gsub("-", "_")
117 local cmd = commands[command] and require(commands[command])
118 if cmd then
119 local arguments = cmd.help_arguments or "<argument>"
120 print_banner()
121 print_section("NAME")
122 util.printout("\t"..program.." "..command.." - "..cmd.help_summary)
123 print_section("SYNOPSIS")
124 util.printout("\t"..program.." "..command.." "..arguments)
125 print_section("DESCRIPTION")
126 util.printout("",(cmd.help:gsub("\n","\n\t"):gsub("\n\t$","")))
127 print_section("SEE ALSO")
128 if cmd.help_see_also then
129 util.printout(cmd.help_see_also)
130 end
131 util.printout("","'"..program.." help' for general options and configuration.\n")
132 else
133 return nil, "Unknown command: "..command
134 end
135 end
136 return true
137end
138
139return help
diff --git a/src/luarocks/cmd/init.lua b/src/luarocks/cmd/init.lua
index 60aa4f82..5f269e22 100644
--- a/src/luarocks/cmd/init.lua
+++ b/src/luarocks/cmd/init.lua
@@ -10,27 +10,17 @@ local util = require("luarocks.util")
10local persist = require("luarocks.persist") 10local persist = require("luarocks.persist")
11local write_rockspec = require("luarocks.cmd.write_rockspec") 11local write_rockspec = require("luarocks.cmd.write_rockspec")
12 12
13init.help_summary = "Initialize a directory for a Lua project using LuaRocks." 13function init.add_to_parser(parser)
14init.help_arguments = "[<name> [<version>]]" 14 local cmd = parser:command("init", "Initialize a directory for a Lua project using LuaRocks.", util.see_also())
15init.help = [[ 15
16<name> is the project name. 16 cmd:argument("name", "The project name.")
17<version> is an optional project version. 17 :args("?")
18 18 cmd:argument("version", "An optional project version.")
19--reset Delete .luarocks/config-5.x.lua and ./lua 19 :args("?")
20 and generate new ones. 20 cmd:flag("--reset", "Delete .luarocks/config-5.x.lua and ./lua and generate new ones.")
21 21
22Options for specifying rockspec data: 22 cmd:group("Options for specifying rockspec data", write_rockspec.cmd_options(cmd))
23 23end
24--license="<string>" A license string, such as "MIT/X11" or "GNU GPL v3".
25--summary="<txt>" A short one-line description summary.
26--detailed="<txt>" A longer description string.
27--homepage=<url> Project homepage.
28--lua-versions=<ver> Supported Lua versions. Accepted values are "5.1", "5.2",
29 "5.3", "5.1,5.2", "5.2,5.3", or "5.1,5.2,5.3".
30--rockspec-format=<ver> Rockspec format version, such as "1.0" or "1.1".
31--lib=<lib>[,<lib>] A comma-separated list of libraries that C files need to
32 link to.
33]]
34 24
35local function write_gitignore(entries) 25local function write_gitignore(entries)
36 local gitignore = "" 26 local gitignore = ""
@@ -53,18 +43,18 @@ end
53 43
54--- Driver function for "init" command. 44--- Driver function for "init" command.
55-- @return boolean: True if succeeded, nil on errors. 45-- @return boolean: True if succeeded, nil on errors.
56function init.command(flags, name, version) 46function init.command(args)
57 47
58 local pwd = fs.current_dir() 48 local pwd = fs.current_dir()
59 49
60 if not name then 50 if not args.name then
61 name = dir.base_name(pwd) 51 args.name = dir.base_name(pwd)
62 if name == "/" then 52 if args.name == "/" then
63 return nil, "When running from the root directory, please specify the <name> argument" 53 return nil, "When running from the root directory, please specify the <name> argument"
64 end 54 end
65 end 55 end
66 56
67 util.title("Initializing project '" .. name .. "' for Lua " .. cfg.lua_version .. " ...") 57 util.title("Initializing project '" .. args.name .. "' for Lua " .. cfg.lua_version .. " ...")
68 58
69 util.printout("Checking your Lua installation ...") 59 util.printout("Checking your Lua installation ...")
70 if not cfg.lua_found then 60 if not cfg.lua_found then
@@ -84,7 +74,9 @@ function init.command(flags, name, version)
84 end 74 end
85 75
86 if not has_rockspec then 76 if not has_rockspec then
87 local ok, err = write_rockspec.command(flags, name, version or "dev", pwd) 77 args.version = args.version or "dev"
78 args.location = pwd
79 local ok, err = write_rockspec.command(args)
88 if not ok then 80 if not ok then
89 util.printerr(err) 81 util.printerr(err)
90 end 82 end
@@ -101,7 +93,7 @@ function init.command(flags, name, version)
101 fs.make_dir(".luarocks") 93 fs.make_dir(".luarocks")
102 local config_file = ".luarocks/config-" .. cfg.lua_version .. ".lua" 94 local config_file = ".luarocks/config-" .. cfg.lua_version .. ".lua"
103 95
104 if flags["reset"] then 96 if args.reset then
105 fs.delete(lua_wrapper) 97 fs.delete(lua_wrapper)
106 fs.delete(config_file) 98 fs.delete(config_file)
107 end 99 end
diff --git a/src/luarocks/cmd/install.lua b/src/luarocks/cmd/install.lua
index d1d7bf6c..4020918e 100644
--- a/src/luarocks/cmd/install.lua
+++ b/src/luarocks/cmd/install.lua
@@ -16,32 +16,35 @@ local cfg = require("luarocks.core.cfg")
16local cmd = require("luarocks.cmd") 16local cmd = require("luarocks.cmd")
17local dir = require("luarocks.dir") 17local dir = require("luarocks.dir")
18 18
19install.help_summary = "Install a rock." 19function install.add_to_parser(parser)
20 20 local cmd = parser:command("install", "Install a rock.", util.see_also())
21install.help_arguments = "{<rock>|<name> [<version>]}" 21
22 22 cmd:argument("rock", "The name of a rock to be fetched from a repository "..
23install.help = [[ 23 "or a filename of a locally available rock.")
24Argument may be the name of a rock to be fetched from a repository 24 cmd:argument("version", "Version of the rock.")
25or a filename of a locally available rock. 25 :args("?")
26 26
27--keep Do not remove previously installed versions of the 27 cmd:flag("--keep", "Do not remove previously installed versions of the "..
28 rock after installing a new one. This behavior can 28 "rock after building a new one. This behavior can be made permanent by "..
29 be made permanent by setting keep_other_versions=true 29 "setting keep_other_versions=true in the configuration file.")
30 in the configuration file. 30 cmd:flag("--force", "If --keep is not specified, force removal of "..
31 31 "previously installed versions if it would break dependencies.")
32--only-deps Installs only the dependencies of the rock. 32 cmd:flag("--force-fast", "Like --force, but performs a forced removal "..
33 33 "without reporting dependency issues.")
34--no-doc Installs the rock without its documentation. 34 cmd:flag("--only-deps", "Installs only the dependencies of the rock.")
35 35 cmd:flag("--no-doc", "Installs the rock without its documentation.")
36--verify Verify signature of the rock being installed. 36 cmd:flag("--verify", "Verify signature of the rockspec or src.rock being "..
37 If rock is being downloaded, LuaRocks will attempt 37 "built. If the rockspec or src.rock is being downloaded, LuaRocks will "..
38 to download the signature as well. If the rock is 38 "attempt to download the signature as well. Otherwise, the signature "..
39 local, the signature file should be in the same 39 "file should be already available locally in the same directory.\n"..
40 directory. 40 "You need the signer’s public key in your local keyring for this "..
41 You need the signer’s public key in your local 41 "option to work properly.")
42 keyring for this option to work properly. 42 util.deps_mode_option(cmd)
43 43 -- luarocks build options
44]]..util.deps_mode_help() 44 parser:flag("--pack-binary-rock"):hidden(true)
45 parser:flag("--branch"):hidden(true)
46 parser:flag("--sign"):hidden(true)
47end
45 48
46install.opts = util.opts_table("install.opts", { 49install.opts = util.opts_table("install.opts", {
47 namespace = "string?", 50 namespace = "string?",
@@ -208,51 +211,46 @@ local function install_rock_file(filename, opts)
208end 211end
209 212
210--- Driver function for the "install" command. 213--- Driver function for the "install" command.
211-- @param name string: name of a binary rock. If an URL or pathname 214-- If an URL or pathname to a binary rock is given, fetches and installs it.
212-- to a binary rock is given, fetches and installs it. If a rockspec or a 215-- If a rockspec or a source rock is given, forwards the request to the "build"
213-- source rock is given, forwards the request to the "build" command. 216-- command.
214-- If a package name is given, forwards the request to "search" and, 217-- If a package name is given, forwards the request to "search" and,
215-- if returned a result, installs the matching rock. 218-- if returned a result, installs the matching rock.
216-- @param version string: When passing a package name, a version number
217-- may also be given.
218-- @return boolean or (nil, string, exitcode): True if installation was 219-- @return boolean or (nil, string, exitcode): True if installation was
219-- successful, nil and an error message otherwise. exitcode is optionally returned. 220-- successful, nil and an error message otherwise. exitcode is optionally returned.
220function install.command(flags, name, version) 221function install.command(args)
221 if type(name) ~= "string" then 222 args.rock = util.adjust_name_and_namespace(args.rock, args)
222 return nil, "Argument missing. "..util.see_help("install")
223 end
224
225 name = util.adjust_name_and_namespace(name, flags)
226 223
227 local ok, err = fs.check_command_permissions(flags) 224 local ok, err = fs.check_command_permissions(args)
228 if not ok then return nil, err, cmd.errorcodes.PERMISSIONDENIED end 225 if not ok then return nil, err, cmd.errorcodes.PERMISSIONDENIED end
229 226
230 if name:match("%.rockspec$") or name:match("%.src%.rock$") then 227 if args.rock:match("%.rockspec$") or args.rock:match("%.src%.rock$") then
231 local build = require("luarocks.cmd.build") 228 local build = require("luarocks.cmd.build")
232 return build.command(flags, name) 229 return build.command(args)
233 elseif name:match("%.rock$") then 230 elseif args.rock:match("%.rock$") then
234 local deps_mode = deps.get_deps_mode(flags) 231 local deps_mode = deps.get_deps_mode(args)
235 local opts = install.opts({ 232 local opts = install.opts({
236 namespace = flags["namespace"], 233 namespace = args.namespace,
237 keep = not not flags["keep"], 234 keep = not not args.keep,
238 force = not not flags["force"], 235 force = not not args.force,
239 force_fast = not not flags["force-fast"], 236 force_fast = not not args.force_fast,
240 no_doc = not not flags["no-doc"], 237 no_doc = not not args.no_doc,
241 deps_mode = deps_mode, 238 deps_mode = deps_mode,
242 verify = not not flags["verify"], 239 verify = not not args.verify,
243 }) 240 })
244 if flags["only-deps"] then 241 if args.only_deps then
245 return install_rock_file_deps(name, opts) 242 return install_rock_file_deps(args.rock, opts)
246 else 243 else
247 return install_rock_file(name, opts) 244 return install_rock_file(args.rock, opts)
248 end 245 end
249 else 246 else
250 local url, err = search.find_suitable_rock(queries.new(name:lower(), version), true) 247 local url, err = search.find_suitable_rock(queries.new(args.rock:lower(), args.version), true)
251 if not url then 248 if not url then
252 return nil, err 249 return nil, err
253 end 250 end
254 util.printout("Installing "..url) 251 util.printout("Installing "..url)
255 return install.command(flags, url) 252 args.rock = url
253 return install.command(args)
256 end 254 end
257end 255end
258 256
diff --git a/src/luarocks/cmd/lint.lua b/src/luarocks/cmd/lint.lua
index c9ea45ea..20c842ff 100644
--- a/src/luarocks/cmd/lint.lua
+++ b/src/luarocks/cmd/lint.lua
@@ -7,24 +7,21 @@ local util = require("luarocks.util")
7local download = require("luarocks.download") 7local download = require("luarocks.download")
8local fetch = require("luarocks.fetch") 8local fetch = require("luarocks.fetch")
9 9
10lint.help_summary = "Check syntax of a rockspec." 10function lint.add_to_parser(parser)
11lint.help_arguments = "<rockspec>" 11 local cmd = parser:command("lint", "Check syntax of a rockspec.\n\n"..
12lint.help = [[ 12 "Returns success if the text of the rockspec is syntactically correct, else failure.",
13This is a utility function that checks the syntax of a rockspec. 13 util.see_also())
14 14 :summary("Check syntax of a rockspec.")
15It returns success or failure if the text of a rockspec is 15
16syntactically correct. 16 cmd:argument("rockspec", "The rockspec to check.")
17]] 17end
18 18
19function lint.command(flags, input) 19function lint.command(args)
20 if not input then 20
21 return nil, "Argument missing. "..util.see_help("lint") 21 local filename = args.rockspec
22 end 22 if not filename:match(".rockspec$") then
23
24 local filename = input
25 if not input:match(".rockspec$") then
26 local err 23 local err
27 filename, err = download.download("rockspec", input:lower()) 24 filename, err = download.download("rockspec", filename:lower())
28 if not filename then 25 if not filename then
29 return nil, err 26 return nil, err
30 end 27 end
diff --git a/src/luarocks/cmd/list.lua b/src/luarocks/cmd/list.lua
index 5e5cfac8..cac5cd8a 100644
--- a/src/luarocks/cmd/list.lua
+++ b/src/luarocks/cmd/list.lua
@@ -10,16 +10,18 @@ local cfg = require("luarocks.core.cfg")
10local util = require("luarocks.util") 10local util = require("luarocks.util")
11local path = require("luarocks.path") 11local path = require("luarocks.path")
12 12
13list.help_summary = "List currently installed rocks." 13function list.add_to_parser(parser)
14list.help_arguments = "[--porcelain] <filter>" 14 local cmd = parser:command("list", "List currently installed rocks.", util.see_also())
15list.help = [[
16<filter> is a substring of a rock name to filter by.
17 15
18--outdated List only rocks for which there is a 16 cmd:argument("filter", "A substring of a rock name to filter by.")
19 higher version available in the rocks server. 17 :args("?")
18 cmd:argument("version", "Rock version to filter by.")
19 :args("?")
20 20
21--porcelain Produce machine-friendly output. 21 cmd:flag("--outdated", "List only rocks for which there is a higher "..
22]] 22 "version available in the rocks server.")
23 cmd:flag("--porcelain", "Produce machine-friendly output.")
24end
23 25
24local function check_outdated(trees, query) 26local function check_outdated(trees, query)
25 local results_installed = {} 27 local results_installed = {}
@@ -65,20 +67,18 @@ local function list_outdated(trees, query, porcelain)
65end 67end
66 68
67--- Driver function for "list" command. 69--- Driver function for "list" command.
68-- @param filter string or nil: A substring of a rock name to filter by.
69-- @param version string or nil: a version may also be passed.
70-- @return boolean: True if succeeded, nil on errors. 70-- @return boolean: True if succeeded, nil on errors.
71function list.command(flags, filter, version) 71function list.command(args)
72 local query = queries.new(filter and filter:lower() or "", version, true) 72 local query = queries.new(args.filter and args.filter:lower() or "", args.version, true)
73 local trees = cfg.rocks_trees 73 local trees = cfg.rocks_trees
74 local title = "Rocks installed for Lua "..cfg.lua_version 74 local title = "Rocks installed for Lua "..cfg.lua_version
75 if flags["tree"] then 75 if args.tree then
76 trees = { flags["tree"] } 76 trees = { args.tree }
77 title = title .. " in " .. flags["tree"] 77 title = title .. " in " .. args.tree
78 end 78 end
79 79
80 if flags["outdated"] then 80 if args.outdated then
81 return list_outdated(trees, query, flags["porcelain"]) 81 return list_outdated(trees, query, args.porcelain)
82 end 82 end
83 83
84 local results = {} 84 local results = {}
@@ -88,8 +88,8 @@ function list.command(flags, filter, version)
88 util.warning(err) 88 util.warning(err)
89 end 89 end
90 end 90 end
91 util.title(title, flags["porcelain"]) 91 util.title(title, args.porcelain)
92 search.print_result_tree(results, flags["porcelain"]) 92 search.print_result_tree(results, args.porcelain)
93 return true 93 return true
94end 94end
95 95
diff --git a/src/luarocks/cmd/make.lua b/src/luarocks/cmd/make.lua
index 4d813864..480ec48d 100644
--- a/src/luarocks/cmd/make.lua
+++ b/src/luarocks/cmd/make.lua
@@ -16,58 +16,61 @@ local deps = require("luarocks.deps")
16local writer = require("luarocks.manif.writer") 16local writer = require("luarocks.manif.writer")
17local cmd = require("luarocks.cmd") 17local cmd = require("luarocks.cmd")
18 18
19make.help_summary = "Compile package in current directory using a rockspec." 19function make.cmd_options(parser)
20make.help_arguments = "[--pack-binary-rock] [<rockspec>]" 20 parser:flag("--pack-binary-rock", "Do not install rock. Instead, produce a "..
21make.help = [[ 21 ".rock file with the contents of compilation in the current directory.")
22Builds sources in the current directory, but unlike "build", 22 parser:flag("--keep", "Do not remove previously installed versions of the "..
23it does not fetch sources, etc., assuming everything is 23 "rock after building a new one. This behavior can be made permanent by "..
24available in the current directory. If no argument is given, 24 "setting keep_other_versions=true in the configuration file.")
25it looks for a rockspec in the current directory and in "rockspec/" 25 parser:flag("--force", "If --keep is not specified, force removal of "..
26and "rockspecs/" subdirectories, picking the rockspec with newest version 26 "previously installed versions if it would break dependencies.")
27or without version name. If rockspecs for different rocks are found 27 parser:flag("--force-fast", "Like --force, but performs a forced removal "..
28or there are several rockspecs without version, you must specify which to use, 28 "without reporting dependency issues.")
29 parser:option("--branch", "Override the `source.branch` field in the loaded "..
30 "rockspec. Allows to specify a different branch to fetch. Particularly "..
31 'for "dev" rocks.')
32 :argname("<name>")
33 parser:flag("--verify", "Verify signature of the rockspec or src.rock being "..
34 "built. If the rockspec or src.rock is being downloaded, LuaRocks will "..
35 "attempt to download the signature as well. Otherwise, the signature "..
36 "file should be already available locally in the same directory.\n"..
37 "You need the signer’s public key in your local keyring for this "..
38 "option to work properly.")
39 parser:flag("--sign", "To be used with --pack-binary-rock. Also produce a "..
40 "signature file for the generated .rock file.")
41 util.deps_mode_option(parser)
42end
43
44function make.add_to_parser(parser)
45 local cmd = parser:command("make", [[
46Builds sources in the current directory, but unlike "build", it does not fetch
47sources, etc., assuming everything is available in the current directory. If no
48argument is given, it looks for a rockspec in the current directory and in
49"rockspec/" and "rockspecs/" subdirectories, picking the rockspec with newest
50version or without version name. If rockspecs for different rocks are found or
51there are several rockspecs without version, you must specify which to use,
29through the command-line. 52through the command-line.
30 53
31This command is useful as a tool for debugging rockspecs. 54This command is useful as a tool for debugging rockspecs.
32To install rocks, you'll normally want to use the "install" and 55To install rocks, you'll normally want to use the "install" and "build"
33"build" commands. See the help on those for details. 56commands. See the help on those for details.
34 57
35NB: Use `luarocks install` with the `--only-deps` flag if you want to install 58NB: Use `luarocks install` with the `--only-deps` flag if you want to install
36only dependencies of the rockspec (see `luarocks help install`). 59only dependencies of the rockspec (see `luarocks help install`).
60]], util.see_also())
61 :summary("Compile package in current directory using a rockspec.")
37 62
38--pack-binary-rock Do not install rock. Instead, produce a .rock file 63 cmd:argument("rockspec", "Rockspec for the rock to build.")
39 with the contents of compilation in the current 64 :args("?")
40 directory.
41
42--keep Do not remove previously installed versions of the
43 rock after installing a new one. This behavior can
44 be made permanent by setting keep_other_versions=true
45 in the configuration file.
46
47--branch=<name> Override the `source.branch` field in the loaded
48 rockspec. Allows to specify a different branch to
49 fetch. Particularly for "dev" rocks.
50 65
51--verify Verify signature of the rockspec or src.rock being 66 make.cmd_options(cmd)
52 built. If the rockspec or src.rock is being downloaded, 67end
53 LuaRocks will attempt to download the signature as well.
54 Otherwise, the signature file should be already
55 available locally in the same directory.
56 You need the signer’s public key in your local
57 keyring for this option to work properly.
58
59--sign To be used with --pack-binary-rock. Also produce
60 a signature file for the generated .rock file.
61
62]]
63 68
64--- Driver function for "make" command. 69--- Driver function for "make" command.
65-- @param name string: A local rockspec.
66-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an 70-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an
67-- error message otherwise. exitcode is optionally returned. 71-- error message otherwise. exitcode is optionally returned.
68function make.command(flags, rockspec_filename) 72function make.command(args)
69 assert(type(rockspec_filename) == "string" or not rockspec_filename) 73 local rockspec_filename = args.rockspec
70
71 if not rockspec_filename then 74 if not rockspec_filename then
72 local err 75 local err
73 rockspec_filename, err = util.get_default_rockspec() 76 rockspec_filename, err = util.get_default_rockspec()
@@ -84,39 +87,39 @@ function make.command(flags, rockspec_filename)
84 return nil, err 87 return nil, err
85 end 88 end
86 89
87 local name = util.adjust_name_and_namespace(rockspec.name, flags) 90 local name = util.adjust_name_and_namespace(rockspec.name, args)
88 91
89 local opts = build.opts({ 92 local opts = build.opts({
90 need_to_fetch = false, 93 need_to_fetch = false,
91 minimal_mode = true, 94 minimal_mode = true,
92 deps_mode = deps.get_deps_mode(flags), 95 deps_mode = deps.get_deps_mode(args),
93 build_only_deps = false, 96 build_only_deps = false,
94 namespace = flags["namespace"], 97 namespace = args.namespace,
95 branch = not not flags["branch"], 98 branch = not not args.branch,
96 verify = not not flags["verify"], 99 verify = not not args.verify,
97 }) 100 })
98 101
99 if flags["sign"] and not flags["pack-binary-rock"] then 102 if args.sign and not args.pack_binary_rock then
100 return nil, "In the make command, --sign is meant to be used only with --pack-binary-rock" 103 return nil, "In the make command, --sign is meant to be used only with --pack-binary-rock"
101 end 104 end
102 105
103 if flags["pack-binary-rock"] then 106 if args.pack_binary_rock then
104 return pack.pack_binary_rock(name, rockspec.version, flags["sign"], function() 107 return pack.pack_binary_rock(name, rockspec.version, args.sign, function()
105 return build.build_rockspec(rockspec, opts) 108 return build.build_rockspec(rockspec, opts)
106 end) 109 end)
107 else 110 else
108 local ok, err = fs.check_command_permissions(flags) 111 local ok, err = fs.check_command_permissions(args)
109 if not ok then return nil, err, cmd.errorcodes.PERMISSIONDENIED end 112 if not ok then return nil, err, cmd.errorcodes.PERMISSIONDENIED end
110 ok, err = build.build_rockspec(rockspec, opts) 113 ok, err = build.build_rockspec(rockspec, opts)
111 if not ok then return nil, err end 114 if not ok then return nil, err end
112 local name, version = ok, err 115 local name, version = ok, err
113 116
114 if (not flags["keep"]) and not cfg.keep_other_versions then 117 if (not args.keep) and not cfg.keep_other_versions then
115 local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) 118 local ok, err = remove.remove_other_versions(name, version, args.force, args.force_fast)
116 if not ok then util.printerr(err) end 119 if not ok then util.printerr(err) end
117 end 120 end
118 121
119 writer.check_dependencies(nil, deps.get_deps_mode(flags)) 122 writer.check_dependencies(nil, deps.get_deps_mode(args))
120 return name, version 123 return name, version
121 end 124 end
122end 125end
diff --git a/src/luarocks/cmd/new_version.lua b/src/luarocks/cmd/new_version.lua
index 19b5fa1e..62fb08f6 100644
--- a/src/luarocks/cmd/new_version.lua
+++ b/src/luarocks/cmd/new_version.lua
@@ -11,35 +11,46 @@ local fs = require("luarocks.fs")
11local dir = require("luarocks.dir") 11local dir = require("luarocks.dir")
12local type_rockspec = require("luarocks.type.rockspec") 12local type_rockspec = require("luarocks.type.rockspec")
13 13
14new_version.help_summary = "Auto-write a rockspec for a new version of a rock." 14function new_version.add_to_parser(parser)
15new_version.help_arguments = "[--tag=<tag>] [--dir=<path>] [<package>|<rockspec>] [<new_version>] [<new_url>]" 15 local cmd = parser:command("new_version", [[
16new_version.help = [[ 16This is a utility function that writes a new rockspec, updating data from a
17This is a utility function that writes a new rockspec, updating data 17previous one.
18from a previous one. 18
19 19If a package name is given, it downloads the latest rockspec from the default
20If a package name is given, it downloads the latest rockspec from the 20server. If a rockspec is given, it uses it instead. If no argument is given, it
21default server. If a rockspec is given, it uses it instead. If no argument 21looks for a rockspec same way 'luarocks make' does.
22is given, it looks for a rockspec same way 'luarocks make' does. 22
23 23If the version number is not given and tag is passed using --tag, it is used as
24If the version number is not given and tag is passed using --tag, 24the version, with 'v' removed from beginning. Otherwise, it only increments the
25it is used as the version, with 'v' removed from beginning. 25revision number of the given (or downloaded) rockspec.
26Otherwise, it only increments the revision number of the given 26
27(or downloaded) rockspec. 27If a URL is given, it replaces the one from the old rockspec with the given URL.
28 28If a URL is not given and a new version is given, it tries to guess the new URL
29If a URL is given, it replaces the one from the old rockspec with the 29by replacing occurrences of the version number in the URL or tag. It also tries
30given URL. If a URL is not given and a new version is given, it tries 30to download the new URL to determine the new MD5 checksum.
31to guess the new URL by replacing occurrences of the version number 31
32in the URL or tag. It also tries to download the new URL to determine 32If a tag is given, it replaces the one from the old rockspec. If there is an old
33the new MD5 checksum. 33tag but no new one passed, it is guessed in the same way URL is.
34
35If a tag is given, it replaces the one from the old rockspec. If there is
36an old tag but no new one passed, it is guessed in the same way URL is.
37 34
38If a directory is not given, it defaults to the current directory. 35If a directory is not given, it defaults to the current directory.
39 36
40WARNING: it writes the new rockspec to the given directory, 37WARNING: it writes the new rockspec to the given directory, overwriting the file
41overwriting the file if it already exists. 38if it already exists.]], util.see_also())
42]] 39 :summary("Auto-write a rockspec for a new version of a rock.")
40
41 parser:command("new-version"):hidden(true):action(function(args) args.command = "new_version" end)
42
43 cmd:argument("rock", "Package name or rockspec.")
44 :args("?")
45 cmd:argument("new_version", "New version of the rock.")
46 :args("?")
47 cmd:argument("new_url", "New URL of the rock.")
48 :args("?")
49
50 cmd:option("--dir", "Output directory for the new rockspec.")
51 cmd:option("--tag", "New SCM tag.")
52end
53
43 54
44local function try_replace(tbl, field, old, new) 55local function try_replace(tbl, field, old, new)
45 if not tbl[field] then 56 if not tbl[field] then
@@ -126,24 +137,23 @@ local function update_source_section(out_rs, url, tag, old_ver, new_ver)
126 return true 137 return true
127end 138end
128 139
129function new_version.command(flags, input, version, url) 140function new_version.command(args)
130 if not input then 141 if not args.rock then
131 local err 142 local err
132 input, err = util.get_default_rockspec() 143 args.rock, err = util.get_default_rockspec()
133 if not input then 144 if not args.rock then
134 return nil, err 145 return nil, err
135 end 146 end
136 end 147 end
137 assert(type(input) == "string")
138 148
139 local filename, err 149 local filename, err
140 if input:match("rockspec$") then 150 if args.rock:match("rockspec$") then
141 filename, err = fetch.fetch_url(input) 151 filename, err = fetch.fetch_url(args.rock)
142 if not filename then 152 if not filename then
143 return nil, err 153 return nil, err
144 end 154 end
145 else 155 else
146 filename, err = download.download("rockspec", input:lower()) 156 filename, err = download.download("rockspec", args.rock:lower())
147 if not filename then 157 if not filename then
148 return nil, err 158 return nil, err
149 end 159 end
@@ -157,20 +167,20 @@ function new_version.command(flags, input, version, url)
157 local old_ver, old_rev = valid_rs.version:match("(.*)%-(%d+)$") 167 local old_ver, old_rev = valid_rs.version:match("(.*)%-(%d+)$")
158 local new_ver, new_rev 168 local new_ver, new_rev
159 169
160 if flags.tag and not version then 170 if args.tag and not args.new_version then
161 version = flags.tag:gsub("^v", "") 171 args.new_version = args.tag:gsub("^v", "")
162 end 172 end
163 173
164 local out_dir 174 local out_dir
165 if flags.dir then 175 if args.dir then
166 out_dir = dir.normalize(flags.dir) 176 out_dir = dir.normalize(args.dir)
167 end 177 end
168 178
169 if version then 179 if args.new_version then
170 new_ver, new_rev = version:match("(.*)%-(%d+)$") 180 new_ver, new_rev = args.new_version:match("(.*)%-(%d+)$")
171 new_rev = tonumber(new_rev) 181 new_rev = tonumber(new_rev)
172 if not new_rev then 182 if not new_rev then
173 new_ver = version 183 new_ver = args.new_version
174 new_rev = 1 184 new_rev = 1
175 end 185 end
176 else 186 else
@@ -183,7 +193,7 @@ function new_version.command(flags, input, version, url)
183 local out_name = out_rs.package:lower() 193 local out_name = out_rs.package:lower()
184 out_rs.version = new_rockver.."-"..new_rev 194 out_rs.version = new_rockver.."-"..new_rev
185 195
186 local ok, err = update_source_section(out_rs, url, flags.tag, old_ver, new_ver) 196 local ok, err = update_source_section(out_rs, args.new_url, args.tag, old_ver, new_ver)
187 if not ok then return nil, err end 197 if not ok then return nil, err end
188 198
189 if out_rs.build and out_rs.build.type == "module" then 199 if out_rs.build and out_rs.build.type == "module" then
diff --git a/src/luarocks/cmd/pack.lua b/src/luarocks/cmd/pack.lua
index 7781a3bd..fde8f875 100644
--- a/src/luarocks/cmd/pack.lua
+++ b/src/luarocks/cmd/pack.lua
@@ -8,39 +8,30 @@ local pack = require("luarocks.pack")
8local signing = require("luarocks.signing") 8local signing = require("luarocks.signing")
9local queries = require("luarocks.queries") 9local queries = require("luarocks.queries")
10 10
11cmd_pack.help_summary = "Create a rock, packing sources or binaries." 11function cmd_pack.add_to_parser(parser)
12cmd_pack.help_arguments = "{<rockspec>|<name> [<version>]}" 12 local cmd = parser:command("pack", "Create a rock, packing sources or binaries.", util.see_also())
13cmd_pack.help = [[
14--sign Produce a signature file as well.
15 13
16Argument may be a rockspec file, for creating a source rock, 14 cmd:argument("rock", "A rockspec file, for creating a source rock, or the "..
17or the name of an installed package, for creating a binary rock. 15 "name of an installed package, for creating a binary rock.")
18In the latter case, the app version may be given as a second 16 cmd:argument("version", "A version may be given if the first argument is a rock name.")
19argument. 17 :args("?")
20]] 18
19 cmd:flag("--sign", "Produce a signature file as well.")
20end
21 21
22--- Driver function for the "pack" command. 22--- Driver function for the "pack" command.
23-- @param arg string: may be a rockspec file, for creating a source rock,
24-- or the name of an installed package, for creating a binary rock.
25-- @param version string or nil: if the name of a package is given, a
26-- version may also be passed.
27-- @return boolean or (nil, string): true if successful or nil followed 23-- @return boolean or (nil, string): true if successful or nil followed
28-- by an error message. 24-- by an error message.
29function cmd_pack.command(flags, arg, version) 25function cmd_pack.command(args)
30 assert(type(version) == "string" or not version)
31 if type(arg) ~= "string" then
32 return nil, "Argument missing. "..util.see_help("pack")
33 end
34
35 local file, err 26 local file, err
36 if arg:match(".*%.rockspec") then 27 if args.rock:match(".*%.rockspec") then
37 file, err = pack.pack_source_rock(arg) 28 file, err = pack.pack_source_rock(args.rock)
38 else 29 else
39 local name = util.adjust_name_and_namespace(arg, flags) 30 local name = util.adjust_name_and_namespace(args.rock, args)
40 local query = queries.new(name, version) 31 local query = queries.new(name, args.version)
41 file, err = pack.pack_installed_rock(query, flags["tree"]) 32 file, err = pack.pack_installed_rock(query, args.tree)
42 end 33 end
43 return pack.report_and_sign_local_file(file, err, flags["sign"]) 34 return pack.report_and_sign_local_file(file, err, args.sign)
44end 35end
45 36
46return cmd_pack 37return cmd_pack
diff --git a/src/luarocks/cmd/path.lua b/src/luarocks/cmd/path.lua
index bb383ad9..b1da4c0b 100644
--- a/src/luarocks/cmd/path.lua
+++ b/src/luarocks/cmd/path.lua
@@ -7,48 +7,45 @@ local util = require("luarocks.util")
7local cfg = require("luarocks.core.cfg") 7local cfg = require("luarocks.core.cfg")
8local fs = require("luarocks.fs") 8local fs = require("luarocks.fs")
9 9
10path_cmd.help_summary = "Return the currently configured package path." 10function path_cmd.add_to_parser(parser)
11path_cmd.help_arguments = "" 11 local cmd = parser:command("path", [[
12path_cmd.help = [[
13Returns the package path currently configured for this installation 12Returns the package path currently configured for this installation
14of LuaRocks, formatted as shell commands to update LUA_PATH and LUA_CPATH. 13of LuaRocks, formatted as shell commands to update LUA_PATH and LUA_CPATH.
15 14
16--no-bin Do not export the PATH variable
17
18--append Appends the paths to the existing paths. Default is to prefix
19 the LR paths to the existing paths.
20
21--lr-path Exports the Lua path (not formatted as shell command)
22
23--lr-cpath Exports the Lua cpath (not formatted as shell command)
24
25--lr-bin Exports the system path (not formatted as shell command)
26
27
28On Unix systems, you may run: 15On Unix systems, you may run:
29 eval `luarocks path` 16 eval `luarocks path`
30And on Windows: 17And on Windows:
31 luarocks path > "%temp%\_lrp.bat" && call "%temp%\_lrp.bat" && del "%temp%\_lrp.bat" 18 luarocks path > "%temp%\_lrp.bat" && call "%temp%\_lrp.bat" && del "%temp%\_lrp.bat"]],
32]] 19 util.see_also())
20 :summary("Return the currently configured package path.")
21
22 cmd:flag("--no-bin", "Do not export the PATH variable.")
23 cmd:flag("--append", "Appends the paths to the existing paths. Default is "..
24 "to prefix the LR paths to the existing paths.")
25 cmd:flag("--lr-path", "Exports the Lua path (not formatted as shell command).")
26 cmd:flag("--lr-cpath", "Exports the Lua cpath (not formatted as shell command).")
27 cmd:flag("--lr-bin", "Exports the system path (not formatted as shell command).")
28 cmd:flag("--bin"):hidden(true)
29end
33 30
34--- Driver function for "path" command. 31--- Driver function for "path" command.
35-- @return boolean This function always succeeds. 32-- @return boolean This function always succeeds.
36function path_cmd.command(flags) 33function path_cmd.command(args)
37 local lr_path, lr_cpath, lr_bin = cfg.package_paths(flags["tree"]) 34 local lr_path, lr_cpath, lr_bin = cfg.package_paths(args.tree)
38 local path_sep = cfg.export_path_separator 35 local path_sep = cfg.export_path_separator
39 36
40 if flags["lr-path"] then 37 if args.lr_path then
41 util.printout(util.cleanup_path(lr_path, ';', cfg.lua_version)) 38 util.printout(util.cleanup_path(lr_path, ';', cfg.lua_version))
42 return true 39 return true
43 elseif flags["lr-cpath"] then 40 elseif args.lr_cpath then
44 util.printout(util.cleanup_path(lr_cpath, ';', cfg.lua_version)) 41 util.printout(util.cleanup_path(lr_cpath, ';', cfg.lua_version))
45 return true 42 return true
46 elseif flags["lr-bin"] then 43 elseif args.lr_bin then
47 util.printout(util.cleanup_path(lr_bin, path_sep)) 44 util.printout(util.cleanup_path(lr_bin, path_sep))
48 return true 45 return true
49 end 46 end
50 47
51 if flags["append"] then 48 if args.append then
52 lr_path = package.path .. ";" .. lr_path 49 lr_path = package.path .. ";" .. lr_path
53 lr_cpath = package.cpath .. ";" .. lr_cpath 50 lr_cpath = package.cpath .. ";" .. lr_cpath
54 lr_bin = os.getenv("PATH") .. path_sep .. lr_bin 51 lr_bin = os.getenv("PATH") .. path_sep .. lr_bin
@@ -60,10 +57,10 @@ function path_cmd.command(flags)
60 57
61 local lpath_var, lcpath_var = util.lua_path_variables() 58 local lpath_var, lcpath_var = util.lua_path_variables()
62 59
63 util.printout(fs.export_cmd(lpath_var, util.cleanup_path(lr_path, ';', cfg.lua_version, flags["append"]))) 60 util.printout(fs.export_cmd(lpath_var, util.cleanup_path(lr_path, ';', cfg.lua_version)))
64 util.printout(fs.export_cmd(lcpath_var, util.cleanup_path(lr_cpath, ';', cfg.lua_version, flags["append"]))) 61 util.printout(fs.export_cmd(lcpath_var, util.cleanup_path(lr_cpath, ';', cfg.lua_version)))
65 if not flags["no-bin"] then 62 if not args.no_bin then
66 util.printout(fs.export_cmd("PATH", util.cleanup_path(lr_bin, path_sep, nil, flags["append"]))) 63 util.printout(fs.export_cmd("PATH", util.cleanup_path(lr_bin, path_sep)))
67 end 64 end
68 return true 65 return true
69end 66end
diff --git a/src/luarocks/cmd/purge.lua b/src/luarocks/cmd/purge.lua
index 98b76a0f..b71baa7c 100644
--- a/src/luarocks/cmd/purge.lua
+++ b/src/luarocks/cmd/purge.lua
@@ -15,24 +15,27 @@ local remove = require("luarocks.remove")
15local queries = require("luarocks.queries") 15local queries = require("luarocks.queries")
16local cmd = require("luarocks.cmd") 16local cmd = require("luarocks.cmd")
17 17
18purge.help_summary = "Remove all installed rocks from a tree." 18function purge.add_to_parser(parser)
19purge.help_arguments = "--tree=<tree> [--old-versions]" 19 local cmd = parser:command("purge", [[
20purge.help = [[
21This command removes rocks en masse from a given tree. 20This command removes rocks en masse from a given tree.
22By default, it removes all rocks from a tree. 21By default, it removes all rocks from a tree.
23 22
24The --tree argument is mandatory: luarocks purge does not 23The --tree option is mandatory: luarocks purge does not assume a default tree.]],
25assume a default tree. 24 util.see_also())
25 :summary("Remove all installed rocks from a tree.")
26 26
27--old-versions Keep the highest-numbered version of each 27 cmd:flag("--old-versions", "Keep the highest-numbered version of each "..
28 rock and remove the other ones. By default 28 "rock and remove the other ones. By default it only removes old "..
29 it only removes old versions if they are 29 "versions if they are not needed as dependencies. This can be "..
30 not needed as dependencies. This can be 30 "overridden with the flag --force.")
31 overridden with the flag --force. 31 cmd:flag("--force", "If --old-versions is specified, force removal of "..
32]] 32 "previously installed versions if it would break dependencies.")
33 cmd:flag("--force-fast", "Like --force, but performs a forced removal "..
34 "without reporting dependency issues.")
35end
33 36
34function purge.command(flags) 37function purge.command(args)
35 local tree = flags["tree"] 38 local tree = args.tree
36 39
37 if type(tree) ~= "string" then 40 if type(tree) ~= "string" then
38 return nil, "The --tree argument is mandatory. "..util.see_help("purge") 41 return nil, "The --tree argument is mandatory. "..util.see_help("purge")
@@ -43,21 +46,21 @@ function purge.command(flags)
43 return nil, "Directory not found: "..tree 46 return nil, "Directory not found: "..tree
44 end 47 end
45 48
46 local ok, err = fs.check_command_permissions(flags) 49 local ok, err = fs.check_command_permissions(args)
47 if not ok then return nil, err, cmd.errorcodes.PERMISSIONDENIED end 50 if not ok then return nil, err, cmd.errorcodes.PERMISSIONDENIED end
48 51
49 search.local_manifest_search(results, path.rocks_dir(tree), queries.all()) 52 search.local_manifest_search(results, path.rocks_dir(tree), queries.all())
50 53
51 local sort = function(a,b) return vers.compare_versions(b,a) end 54 local sort = function(a,b) return vers.compare_versions(b,a) end
52 if flags["old-versions"] then 55 if args.old_versions then
53 sort = vers.compare_versions 56 sort = vers.compare_versions
54 end 57 end
55 58
56 for package, versions in util.sortedpairs(results) do 59 for package, versions in util.sortedpairs(results) do
57 for version, _ in util.sortedpairs(versions, sort) do 60 for version, _ in util.sortedpairs(versions, sort) do
58 if flags["old-versions"] then 61 if args.old_versions then
59 util.printout("Keeping "..package.." "..version.."...") 62 util.printout("Keeping "..package.." "..version.."...")
60 local ok, err = remove.remove_other_versions(package, version, flags["force"], flags["force-fast"]) 63 local ok, err = remove.remove_other_versions(package, version, args.force, args.force_fast)
61 if not ok then 64 if not ok then
62 util.printerr(err) 65 util.printerr(err)
63 end 66 end
diff --git a/src/luarocks/cmd/remove.lua b/src/luarocks/cmd/remove.lua
index 5ddf7477..f29b0b7d 100644
--- a/src/luarocks/cmd/remove.lua
+++ b/src/luarocks/cmd/remove.lua
@@ -14,38 +14,38 @@ local writer = require("luarocks.manif.writer")
14local queries = require("luarocks.queries") 14local queries = require("luarocks.queries")
15local cmd = require("luarocks.cmd") 15local cmd = require("luarocks.cmd")
16 16
17cmd_remove.help_summary = "Uninstall a rock." 17function cmd_remove.add_to_parser(parser)
18cmd_remove.help_arguments = "[--force|--force-fast] <name> [<version>]" 18 local cmd = parser:command("remove", [[
19cmd_remove.help = [[ 19Uninstall a rock.
20Argument is the name of a rock to be uninstalled. 20
21If a version is not given, try to remove all versions at once. 21If a version is not given, try to remove all versions at once.
22Will only perform the removal if it does not break dependencies. 22Will only perform the removal if it does not break dependencies.
23To override this check and force the removal, use --force. 23To override this check and force the removal, use --force or --force-fast.]],
24To perform a forced removal without reporting dependency issues, 24 util.see_also())
25use --force-fast. 25 :summary("Uninstall a rock.")
26
27 cmd:argument("rock", "Name of the rock to be uninstalled.")
28 cmd:argument("version", "Version of the rock to uninstall.")
29 :args("?")
26 30
27]]..util.deps_mode_help() 31 cmd:flag("--force", "Force removal if it would break dependencies.")
32 cmd:flag("--force-fast", "Perform a forced removal without reporting dependency issues.")
33 util.deps_mode_option(cmd)
34end
28 35
29--- Driver function for the "remove" command. 36--- Driver function for the "remove" command.
30-- @param name string: name of a rock. If a version is given, refer to
31-- a specific version; otherwise, try to remove all versions.
32-- @param version string: When passing a package name, a version number
33-- may also be given.
34-- @return boolean or (nil, string, exitcode): True if removal was 37-- @return boolean or (nil, string, exitcode): True if removal was
35-- successful, nil and an error message otherwise. exitcode is optionally returned. 38-- successful, nil and an error message otherwise. exitcode is optionally returned.
36function cmd_remove.command(flags, name, version) 39function cmd_remove.command(args)
37 if type(name) ~= "string" then 40 local name = util.adjust_name_and_namespace(args.rock, args)
38 return nil, "Argument missing. "..util.see_help("remove")
39 end
40
41 name = util.adjust_name_and_namespace(name, flags)
42 41
43 local deps_mode = flags["deps-mode"] or cfg.deps_mode 42 local deps_mode = args.deps_mode or cfg.deps_mode
44 43
45 local ok, err = fs.check_command_permissions(flags) 44 local ok, err = fs.check_command_permissions(args)
46 if not ok then return nil, err, cmd.errorcodes.PERMISSIONDENIED end 45 if not ok then return nil, err, cmd.errorcodes.PERMISSIONDENIED end
47 46
48 local rock_type = name:match("%.(rock)$") or name:match("%.(rockspec)$") 47 local rock_type = name:match("%.(rock)$") or name:match("%.(rockspec)$")
48 local version = args.version
49 local filename = name 49 local filename = name
50 if rock_type then 50 if rock_type then
51 name, version = path.parse_name(filename) 51 name, version = path.parse_name(filename)
@@ -59,12 +59,12 @@ function cmd_remove.command(flags, name, version)
59 return nil, "Could not find rock '"..name..(version and " "..version or "").."' in "..path.rocks_tree_to_string(cfg.root_dir) 59 return nil, "Could not find rock '"..name..(version and " "..version or "").."' in "..path.rocks_tree_to_string(cfg.root_dir)
60 end 60 end
61 61
62 local ok, err = remove.remove_search_results(results, name, deps_mode, flags["force"], flags["force-fast"]) 62 local ok, err = remove.remove_search_results(results, name, deps_mode, args.force, args.force_fast)
63 if not ok then 63 if not ok then
64 return nil, err 64 return nil, err
65 end 65 end
66 66
67 writer.check_dependencies(nil, deps.get_deps_mode(flags)) 67 writer.check_dependencies(nil, deps.get_deps_mode(args))
68 return true 68 return true
69end 69end
70 70
diff --git a/src/luarocks/cmd/search.lua b/src/luarocks/cmd/search.lua
index 8f4d014e..f34cf7b9 100644
--- a/src/luarocks/cmd/search.lua
+++ b/src/luarocks/cmd/search.lua
@@ -9,17 +9,22 @@ local search = require("luarocks.search")
9local queries = require("luarocks.queries") 9local queries = require("luarocks.queries")
10local results = require("luarocks.results") 10local results = require("luarocks.results")
11 11
12cmd_search.help_summary = "Query the LuaRocks servers." 12function cmd_search.add_to_parser(parser)
13cmd_search.help_arguments = "[--source] [--binary] { <name> [<version>] | --all }" 13 local cmd = parser:command("search", "Query the LuaRocks servers.", util.see_also())
14cmd_search.help = [[ 14
15--source Return only rockspecs and source rocks, 15 cmd:argument("name", "Name of the rock to search for.")
16 to be used with the "build" command. 16 :args("?")
17--binary Return only pure Lua and binary rocks (rocks that can be used 17 cmd:argument("version", "Rock version to search for.")
18 with the "install" command without requiring a C toolchain). 18 :args("?")
19--all List all contents of the server that are suitable to 19
20 this platform, do not filter by name. 20 cmd:flag("--source", "Return only rockspecs and source rocks, to be used "..
21--porcelain Return a machine readable format. 21 'with the "build" command.')
22]] 22 cmd:flag("--binary", "Return only pure Lua and binary rocks (rocks that "..
23 'can be used with the "install" command without requiring a C toolchain).')
24 cmd:flag("--all", "List all contents of the server that are suitable to "..
25 "this platform, do not filter by name.")
26 cmd:flag("--porcelain", "Return a machine readable format.")
27end
23 28
24--- Splits a list of search results into two lists, one for "source" results 29--- Splits a list of search results into two lists, one for "source" results
25-- to be used with the "build" command, and one for "binary" results to be 30-- to be used with the "build" command, and one for "binary" results to be
@@ -45,33 +50,31 @@ local function split_source_and_binary_results(result_tree)
45end 50end
46 51
47--- Driver function for "search" command. 52--- Driver function for "search" command.
48-- @param name string: A substring of a rock name to search.
49-- @param version string or nil: a version may also be passed.
50-- @return boolean or (nil, string): True if build was successful; nil and an 53-- @return boolean or (nil, string): True if build was successful; nil and an
51-- error message otherwise. 54-- error message otherwise.
52function cmd_search.command(flags, name, version) 55function cmd_search.command(args)
53 56
54 name = util.adjust_name_and_namespace(name, flags) 57 local name = util.adjust_name_and_namespace(args.name, args)
55 58
56 if flags["all"] then 59 if args.all then
57 name, version = "", nil 60 name, args.version = "", nil
58 end 61 end
59 62
60 if type(name) ~= "string" and not flags["all"] then 63 if not args.name and not args.all then
61 return nil, "Enter name and version or use --all. "..util.see_help("search") 64 return nil, "Enter name and version or use --all. "..util.see_help("search")
62 end 65 end
63 66
64 local query = queries.new(name:lower(), version, true) 67 local query = queries.new(name:lower(), args.version, true)
65 local result_tree, err = search.search_repos(query) 68 local result_tree, err = search.search_repos(query)
66 local porcelain = flags["porcelain"] 69 local porcelain = args.porcelain
67 local full_name = name .. (version and " " .. version or "") 70 local full_name = name .. (args.version and " " .. args.version or "")
68 util.title(full_name .. " - Search results for Lua "..cfg.lua_version..":", porcelain, "=") 71 util.title(full_name .. " - Search results for Lua "..cfg.lua_version..":", porcelain, "=")
69 local sources, binaries = split_source_and_binary_results(result_tree) 72 local sources, binaries = split_source_and_binary_results(result_tree)
70 if next(sources) and not flags["binary"] then 73 if next(sources) and not args.binary then
71 util.title("Rockspecs and source rocks:", porcelain) 74 util.title("Rockspecs and source rocks:", porcelain)
72 search.print_result_tree(sources, porcelain) 75 search.print_result_tree(sources, porcelain)
73 end 76 end
74 if next(binaries) and not flags["source"] then 77 if next(binaries) and not args.source then
75 util.title("Binary and pure-Lua rocks:", porcelain) 78 util.title("Binary and pure-Lua rocks:", porcelain)
76 search.print_result_tree(binaries, porcelain) 79 search.print_result_tree(binaries, porcelain)
77 end 80 end
diff --git a/src/luarocks/cmd/show.lua b/src/luarocks/cmd/show.lua
index 37c2c55e..db7aed54 100644
--- a/src/luarocks/cmd/show.lua
+++ b/src/luarocks/cmd/show.lua
@@ -13,24 +13,33 @@ local fetch = require("luarocks.fetch")
13local manif = require("luarocks.manif") 13local manif = require("luarocks.manif")
14local repos = require("luarocks.repos") 14local repos = require("luarocks.repos")
15 15
16show.help_summary = "Show information about an installed rock." 16function show.add_to_parser(parser)
17 local cmd = parser:command("show", [[
18Show information about an installed rock.
17 19
18show.help = [[
19<argument> is an existing package name.
20Without any flags, show all module information. 20Without any flags, show all module information.
21With these flags, return only the desired information: 21With flags, return only the desired information.]], util.see_also())
22 :summary("Show information about an installed rock.")
22 23
23--home home page of project 24 cmd:argument("rock", "Name of an installed rock.")
24--modules all modules provided by this package as used by require() 25 cmd:argument("version", "Rock version.")
25--deps packages this package depends on 26 :args("?")
26--build-deps build-only dependencies for this package
27--test-deps dependencies for testing this package
28--rockspec the full path of the rockspec file
29--mversion the package version
30--rock-tree local tree where rock is installed
31--rock-dir data directory of the installed rock
32]]
33 27
28 cmd:flag("--home", "Show home page of project.")
29 cmd:flag("--modules", "Show all modules provided by the package as used by require().")
30 cmd:flag("--deps", "Show packages the package depends on.")
31 cmd:flag("--build-deps", "Show build-only dependencies for the package.")
32 cmd:flag("--test-deps", "Show dependencies for testing the package.")
33 cmd:flag("--rockspec", "Show the full path of the rockspec file.")
34 cmd:flag("--mversion", "Show the package version.")
35 cmd:flag("--rock-tree", "Show local tree where rock is installed.")
36 cmd:flag("--rock-namespace", "Show rock namespace.")
37 cmd:flag("--rock-dir", "Show data directory of the installed rock.")
38 cmd:flag("--rock-license", "Show rock license.")
39 cmd:flag("--issues", "Show URL for project's issue tracker.")
40 cmd:flag("--labels", "List the labels of the rock.")
41 cmd:flag("--porcelain", "Produce machine-friendly output.")
42end
34 43
35local friendly_template = [[ 44local friendly_template = [[
36 : 45 :
@@ -249,19 +258,14 @@ local function show_rock(template, namespace, name, version, rockspec, repo, min
249end 258end
250 259
251--- Driver function for "show" command. 260--- Driver function for "show" command.
252-- @param name or nil: an existing package name.
253-- @param version string or nil: a version may also be passed.
254-- @return boolean: True if succeeded, nil on errors. 261-- @return boolean: True if succeeded, nil on errors.
255function show.command(flags, name, version) 262function show.command(args)
256 if not name then 263 local name = util.adjust_name_and_namespace(args.rock, args)
257 return nil, "Argument missing. "..util.see_help("show") 264 local version = args.version
258 end
259
260 name = util.adjust_name_and_namespace(name, flags)
261 local query = queries.new(name, version) 265 local query = queries.new(name, version)
262 266
263 local repo, repo_url 267 local repo, repo_url
264 name, version, repo, repo_url = search.pick_installed_rock(query, flags["tree"]) 268 name, version, repo, repo_url = search.pick_installed_rock(query, args.tree)
265 if not name then 269 if not name then
266 return nil, version 270 return nil, version
267 end 271 end
@@ -277,32 +281,32 @@ function show.command(flags, name, version)
277 if not manifest then return nil,err end 281 if not manifest then return nil,err end
278 local minfo = manifest.repository[name][version][1] 282 local minfo = manifest.repository[name][version][1]
279 283
280 if flags["rock-tree"] then util.printout(tree) 284 if args.rock_tree then util.printout(tree)
281 elseif flags["rock-namespace"] then util.printout(namespace) 285 elseif args.rock_namespace then util.printout(namespace)
282 elseif flags["rock-dir"] then util.printout(directory) 286 elseif args.rock_dir then util.printout(directory)
283 elseif flags["home"] then util.printout(descript.homepage) 287 elseif args.home then util.printout(descript.homepage)
284 elseif flags["rock-license"] then util.printout(descript.license) 288 elseif args.rock_license then util.printout(descript.license)
285 elseif flags["issues"] then util.printout(descript.issues_url) 289 elseif args.issues then util.printout(descript.issues_url)
286 elseif flags["labels"] then util.printout(descript.labels and table.concat(descript.labels, "\n")) 290 elseif args.labels then util.printout(descript.labels and table.concat(descript.labels, "\n"))
287 elseif flags["modules"] then util.printout(keys_as_string(minfo.modules, "\n")) 291 elseif args.modules then util.printout(keys_as_string(minfo.modules, "\n"))
288 elseif flags["deps"] then 292 elseif args.deps then
289 for _, dep in ipairs(rockspec.dependencies) do 293 for _, dep in ipairs(rockspec.dependencies) do
290 util.printout(tostring(dep)) 294 util.printout(tostring(dep))
291 end 295 end
292 elseif flags["build-deps"] then 296 elseif args.build_deps then
293 for _, dep in ipairs(rockspec.build_dependencies) do 297 for _, dep in ipairs(rockspec.build_dependencies) do
294 util.printout(tostring(dep)) 298 util.printout(tostring(dep))
295 end 299 end
296 elseif flags["test-deps"] then 300 elseif args.test_deps then
297 for _, dep in ipairs(rockspec.test_dependencies) do 301 for _, dep in ipairs(rockspec.test_dependencies) do
298 util.printout(tostring(dep)) 302 util.printout(tostring(dep))
299 end 303 end
300 elseif flags["rockspec"] then util.printout(rockspec_file) 304 elseif args.rockspec then util.printout(rockspec_file)
301 elseif flags["mversion"] then util.printout(version) 305 elseif args.mversion then util.printout(version)
302 elseif flags["porcelain"] then 306 elseif args.porcelain then
303 show_rock(porcelain_template, namespace, name, version, rockspec, repo, minfo, flags["tree"]) 307 show_rock(porcelain_template, namespace, name, version, rockspec, repo, minfo, args.tree)
304 else 308 else
305 show_rock(friendly_template, namespace, name, version, rockspec, repo, minfo, flags["tree"]) 309 show_rock(friendly_template, namespace, name, version, rockspec, repo, minfo, args.tree)
306 end 310 end
307 return true 311 return true
308end 312end
diff --git a/src/luarocks/cmd/test.lua b/src/luarocks/cmd/test.lua
index 413a029c..7a1ffda2 100644
--- a/src/luarocks/cmd/test.lua
+++ b/src/luarocks/cmd/test.lua
@@ -6,42 +6,42 @@ local cmd_test = {}
6local util = require("luarocks.util") 6local util = require("luarocks.util")
7local test = require("luarocks.test") 7local test = require("luarocks.test")
8 8
9cmd_test.help_summary = "Run the test suite in the current directory." 9function cmd_test.add_to_parser(parser)
10cmd_test.help_arguments = "[--test-type=<type>] [<rockspec>] [-- <args>]" 10 local cmd = parser:command("test", [[
11cmd_test.help = [[
12Run the test suite for the Lua project in the current directory. 11Run the test suite for the Lua project in the current directory.
13If the first argument is a rockspec, it will use it to determine
14the parameters for running tests; otherwise, it will attempt to
15detect the rockspec.
16 12
17Any additional arguments are forwarded to the test suite. 13If the first argument is a rockspec, it will use it to determine the parameters
18To make sure that any flags passed in <args> are not interpreted 14for running tests; otherwise, it will attempt to detect the rockspec.
19as LuaRocks flags, use -- to separate LuaRocks arguments from
20test suite arguments.
21 15
22--test-type=<type> Specify the test suite type manually if it was not 16Any additional arguments are forwarded to the test suite.
23 specified in the rockspec and it could not be 17To make sure that test suite flags are not interpreted as LuaRocks flags, use --
24 auto-detected. 18to separate LuaRocks arguments from test suite arguments.]],
19 util.see_also())
20 :summary("Run the test suite in the current directory.")
25 21
26]]..util.deps_mode_help() 22 cmd:argument("rockspec", "Project rockspec.")
23 :args("?")
24 cmd:argument("args", "Test suite arguments.")
25 :args("*")
27 26
28function cmd_test.command(flags, argument, ...) 27 cmd:option("--test-type", "Specify the test suite type manually if it was "..
29 assert(type(argument) == "string" or not argument) 28 "not specified in the rockspec and it could not be auto-detected.")
30 29 :argname("<type>")
31 local arguments = { ... } 30end
32 31
33 if argument and argument:match("rockspec$") then 32function cmd_test.command(args)
34 return test.run_test_suite(argument, flags["test-type"], arguments) 33 if args.rockspec and args.rockspec:match("rockspec$") then
34 return test.run_test_suite(args.rockspec, args.test_type, args.args)
35 end 35 end
36 36
37 table.insert(arguments, 1, argument) 37 table.insert(args.args, 1, args.rockspec)
38 38
39 local rockspec, err = util.get_default_rockspec() 39 local rockspec, err = util.get_default_rockspec()
40 if not rockspec then 40 if not rockspec then
41 return nil, err 41 return nil, err
42 end 42 end
43 43
44 return test.run_test_suite(rockspec, flags["test-type"], arguments) 44 return test.run_test_suite(rockspec, args.test_type, args.args)
45end 45end
46 46
47return cmd_test 47return cmd_test
diff --git a/src/luarocks/cmd/unpack.lua b/src/luarocks/cmd/unpack.lua
index 99263f49..fe0535e4 100644
--- a/src/luarocks/cmd/unpack.lua
+++ b/src/luarocks/cmd/unpack.lua
@@ -10,15 +10,20 @@ local build = require("luarocks.build")
10local dir = require("luarocks.dir") 10local dir = require("luarocks.dir")
11local search = require("luarocks.search") 11local search = require("luarocks.search")
12 12
13unpack.help_summary = "Unpack the contents of a rock." 13function unpack.add_to_parser(parser)
14unpack.help_arguments = "[--force] {<rock>|<name> [<version>]}" 14 local cmd = parser:command("unpack", [[
15unpack.help = [[
16Unpacks the contents of a rock in a newly created directory. 15Unpacks the contents of a rock in a newly created directory.
17Argument may be a rock file, or the name of a rock in a rocks server. 16Argument may be a rock file, or the name of a rock in a rocks server.
18In the latter case, the app version may be given as a second argument. 17In the latter case, the rock version may be given as a second argument.]],
18 util.see_also())
19 :summary("Unpack the contents of a rock.")
19 20
20--force Unpack files even if the output directory already exists. 21 cmd:argument("rock", "A rock file or the name of a rock.")
21]] 22 cmd:argument("version", "Rock version.")
23 :args("?")
24
25 cmd:flag("--force", "Unpack files even if the output directory already exists.")
26end
22 27
23--- Load a rockspec file to the given directory, fetches the source 28--- Load a rockspec file to the given directory, fetches the source
24-- files specified in the rockspec, and unpack them inside the directory. 29-- files specified in the rockspec, and unpack them inside the directory.
@@ -141,31 +146,22 @@ local function run_unpacker(file, force)
141end 146end
142 147
143--- Driver function for the "unpack" command. 148--- Driver function for the "unpack" command.
144-- @param ns_name string: may be a rock filename, for unpacking a
145-- rock file or the name of a rock to be fetched and unpacked.
146-- @param version string or nil: if the name of a package is given, a
147-- version may also be passed.
148-- @return boolean or (nil, string): true if successful or nil followed 149-- @return boolean or (nil, string): true if successful or nil followed
149-- by an error message. 150-- by an error message.
150function unpack.command(flags, ns_name, version) 151function unpack.command(args)
151 assert(type(version) == "string" or not version) 152 local ns_name = util.adjust_name_and_namespace(args.rock, args)
152 if type(ns_name) ~= "string" then
153 return nil, "Argument missing. "..util.see_help("unpack")
154 end
155
156 ns_name = util.adjust_name_and_namespace(ns_name, flags)
157 153
158 local url, err 154 local url, err
159 if ns_name:match(".*%.rock") or ns_name:match(".*%.rockspec") then 155 if ns_name:match(".*%.rock") or ns_name:match(".*%.rockspec") then
160 url = ns_name 156 url = ns_name
161 else 157 else
162 url, err = search.find_src_or_rockspec(ns_name, version, true) 158 url, err = search.find_src_or_rockspec(ns_name, args.version, true)
163 if not url then 159 if not url then
164 return nil, err 160 return nil, err
165 end 161 end
166 end 162 end
167 163
168 return run_unpacker(url, flags["force"]) 164 return run_unpacker(url, args.force)
169end 165end
170 166
171return unpack 167return unpack
diff --git a/src/luarocks/cmd/upload.lua b/src/luarocks/cmd/upload.lua
index b052500e..6e3877ba 100644
--- a/src/luarocks/cmd/upload.lua
+++ b/src/luarocks/cmd/upload.lua
@@ -8,37 +8,33 @@ local pack = require("luarocks.pack")
8local cfg = require("luarocks.core.cfg") 8local cfg = require("luarocks.core.cfg")
9local Api = require("luarocks.upload.api") 9local Api = require("luarocks.upload.api")
10 10
11upload.help_summary = "Upload a rockspec to the public rocks repository." 11function upload.add_to_parser(parser)
12upload.help_arguments = "[--skip-pack] [--api-key=<key>] [--force] <rockspec>" 12 local cmd = parser:command("upload", "Pack a source rock file (.src.rock extension) "..
13upload.help = [[ 13 "and upload it and the rockspec to the public rocks repository.", util.see_also())
14<rockspec> Pack a source rock file (.src.rock extension), 14 :summary("Upload a rockspec to the public rocks repository.")
15 upload rockspec and source rock to server. 15
16 16 cmd:argument("rockspec", "Rockspec for the rock to upload.")
17--skip-pack Do not pack and send source rock. 17
18 18 cmd:flag("--skip-pack", "Do not pack and send source rock.")
19--api-key=<key> Give it an API key. It will be stored for subsequent uses. 19 cmd:option("--api-key", "Pass an API key. It will be stored for subsequent uses.")
20 20 :argname("<key>")
21--temp-key=<key> Use the given a temporary API key in this invocation only. 21 cmd:option("--temp-key", "Use the given a temporary API key in this "..
22 It will not be stored. 22 "invocation only. It will not be stored.")
23 23 :argname("<key>")
24--force Replace existing rockspec if the same revision of 24 cmd:flag("--force", "Replace existing rockspec if the same revision of a "..
25 a module already exists. This should be used only 25 "module already exists. This should be used only in case of upload "..
26 in case of upload mistakes: when updating a rockspec, 26 "mistakes: when updating a rockspec, increment the revision number "..
27 increment the revision number instead. 27 "instead.")
28 28 cmd:flag("--sign", "Upload a signature file alongside each file as well.")
29--sign Upload a signature file alongside each file as well. 29 cmd:flag("--debug"):hidden(true)
30]] 30end
31 31
32local function is_dev_version(version) 32local function is_dev_version(version)
33 return version:match("^dev") or version:match("^scm") 33 return version:match("^dev") or version:match("^scm")
34end 34end
35 35
36function upload.command(flags, fname) 36function upload.command(args)
37 if not fname then 37 local api, err = Api.new(args)
38 return nil, "Missing rockspec. "..util.see_help("upload")
39 end
40
41 local api, err = Api.new(flags)
42 if not api then 38 if not api then
43 return nil, err 39 return nil, err
44 end 40 end
@@ -46,12 +42,12 @@ function upload.command(flags, fname)
46 api.debug = true 42 api.debug = true
47 end 43 end
48 44
49 local rockspec, err, errcode = fetch.load_rockspec(fname) 45 local rockspec, err, errcode = fetch.load_rockspec(args.rockspec)
50 if err then 46 if err then
51 return nil, err, errcode 47 return nil, err, errcode
52 end 48 end
53 49
54 util.printout("Sending " .. tostring(fname) .. " ...") 50 util.printout("Sending " .. tostring(args.rockspec) .. " ...")
55 local res, err = api:method("check_rockspec", { 51 local res, err = api:method("check_rockspec", {
56 package = rockspec.package, 52 package = rockspec.package,
57 version = rockspec.version 53 version = rockspec.version
@@ -61,15 +57,15 @@ function upload.command(flags, fname)
61 if not res.module then 57 if not res.module then
62 util.printout("Will create new module (" .. tostring(rockspec.package) .. ")") 58 util.printout("Will create new module (" .. tostring(rockspec.package) .. ")")
63 end 59 end
64 if res.version and not flags["force"] then 60 if res.version and not args.force then
65 return nil, "Revision "..rockspec.version.." already exists on the server. "..util.see_help("upload") 61 return nil, "Revision "..rockspec.version.." already exists on the server. "..util.see_help("upload")
66 end 62 end
67 63
68 local sigfname 64 local sigfname
69 local rock_sigfname 65 local rock_sigfname
70 66
71 if flags["sign"] then 67 if args.sign then
72 sigfname, err = signing.sign_file(fname) 68 sigfname, err = signing.sign_file(args.rockspec)
73 if err then 69 if err then
74 return nil, "Failed signing rockspec: " .. err 70 return nil, "Failed signing rockspec: " .. err
75 end 71 end
@@ -77,13 +73,13 @@ function upload.command(flags, fname)
77 end 73 end
78 74
79 local rock_fname 75 local rock_fname
80 if not flags["skip-pack"] and not is_dev_version(rockspec.version) then 76 if not args.skip_pack and not is_dev_version(rockspec.version) then
81 util.printout("Packing " .. tostring(rockspec.package)) 77 util.printout("Packing " .. tostring(rockspec.package))
82 rock_fname, err = pack.pack_source_rock(fname) 78 rock_fname, err = pack.pack_source_rock(args.rockspec)
83 if not rock_fname then 79 if not rock_fname then
84 return nil, err 80 return nil, err
85 end 81 end
86 if flags["sign"] then 82 if args.sign then
87 rock_sigfname, err = signing.sign_file(rock_fname) 83 rock_sigfname, err = signing.sign_file(rock_fname)
88 if err then 84 if err then
89 return nil, "Failed signing rock: " .. err 85 return nil, "Failed signing rock: " .. err
@@ -95,7 +91,7 @@ function upload.command(flags, fname)
95 local multipart = require("luarocks.upload.multipart") 91 local multipart = require("luarocks.upload.multipart")
96 92
97 res, err = api:method("upload", nil, { 93 res, err = api:method("upload", nil, {
98 rockspec_file = multipart.new_file(fname), 94 rockspec_file = multipart.new_file(args.rockspec),
99 rockspec_sig = sigfname and multipart.new_file(sigfname), 95 rockspec_sig = sigfname and multipart.new_file(sigfname),
100 }) 96 })
101 if not res then return nil, err end 97 if not res then return nil, err end
diff --git a/src/luarocks/cmd/which.lua b/src/luarocks/cmd/which.lua
index 56db5254..9a363e85 100644
--- a/src/luarocks/cmd/which.lua
+++ b/src/luarocks/cmd/which.lua
@@ -8,20 +8,20 @@ local cfg = require("luarocks.core.cfg")
8local util = require("luarocks.util") 8local util = require("luarocks.util")
9local fs = require("luarocks.fs") 9local fs = require("luarocks.fs")
10 10
11which_cmd.help_summary = "Tell which file corresponds to a given module name." 11function which_cmd.add_to_parser(parser)
12which_cmd.help_arguments = "<modname>" 12 local cmd = parser:command("which", 'Given a module name like "foo.bar", '..
13which_cmd.help = [[ 13 "output which file would be loaded to resolve that module by "..
14Given a module name like "foo.bar", output which file would be loaded to resolve 14 'luarocks.loader, like "/usr/local/lua/'..cfg.lua_version..'/foo/bar.lua".',
15that module by luarocks.loader, like "/usr/local/lua/]]..cfg.lua_version..[[/foo/bar.lua". 15 util.see_also())
16]] 16 :summary("Tell which file corresponds to a given module name.")
17 17
18--- Driver function for "lua" command. 18 cmd:argument("modname", "Module name.")
19end
20
21--- Driver function for "which" command.
19-- @return boolean This function terminates the interpreter. 22-- @return boolean This function terminates the interpreter.
20function which_cmd.command(_, modname) 23function which_cmd.command(args)
21 if modname == nil then 24 local pathname, rock_name, rock_version = loader.which(args.modname)
22 return nil, "Missing module name. " .. util.see_help("which")
23 end
24 local pathname, rock_name, rock_version = loader.which(modname)
25 25
26 if pathname then 26 if pathname then
27 util.printout(pathname) 27 util.printout(pathname)
@@ -29,7 +29,7 @@ function which_cmd.command(_, modname)
29 return true 29 return true
30 end 30 end
31 31
32 local modpath = modname:gsub("%.", "/") 32 local modpath = args.modname:gsub("%.", "/")
33 for _, v in ipairs({"path", "cpath"}) do 33 for _, v in ipairs({"path", "cpath"}) do
34 for p in package[v]:gmatch("([^;]+)") do 34 for p in package[v]:gmatch("([^;]+)") do
35 local pathname = p:gsub("%?", modpath) 35 local pathname = p:gsub("%?", modpath)
@@ -41,7 +41,7 @@ function which_cmd.command(_, modname)
41 end 41 end
42 end 42 end
43 43
44 return nil, "Module '" .. modname .. "' not found." 44 return nil, "Module '" .. args.modname .. "' not found."
45end 45end
46 46
47return which_cmd 47return which_cmd
diff --git a/src/luarocks/cmd/write_rockspec.lua b/src/luarocks/cmd/write_rockspec.lua
index aad91910..ee825ce4 100644
--- a/src/luarocks/cmd/write_rockspec.lua
+++ b/src/luarocks/cmd/write_rockspec.lua
@@ -11,36 +11,69 @@ local rockspecs = require("luarocks.rockspecs")
11local type_rockspec = require("luarocks.type.rockspec") 11local type_rockspec = require("luarocks.type.rockspec")
12local util = require("luarocks.util") 12local util = require("luarocks.util")
13 13
14write_rockspec.help_summary = "Write a template for a rockspec file." 14local lua_versions = {
15write_rockspec.help_arguments = "[--output=<file> ...] [<name>] [<version>] [<url>|<path>]" 15 "5.1",
16write_rockspec.help = [[ 16 "5.2",
17 "5.3",
18 "5.4",
19 "5.1,5.2",
20 "5.2,5.3",
21 "5.3,5.4",
22 "5.1,5.2,5.3",
23 "5.2,5.3,5.4",
24 "5.1,5.2,5.3,5.4"
25}
26
27function write_rockspec.cmd_options(parser)
28 return parser:option("--output", "Write the rockspec with the given filename.\n"..
29 "If not given, a file is written in the current directory with a "..
30 "filename based on given name and version.")
31 :argname("<file>"),
32 parser:option("--license", 'A license string, such as "MIT/X11" or "GNU GPL v3".')
33 :argname("<string>"),
34 parser:option("--summary", "A short one-line description summary.")
35 :argname("<txt>"),
36 parser:option("--detailed", "A longer description string.")
37 :argname("<txt>"),
38 parser:option("--homepage", "Project homepage.")
39 :argname("<txt>"),
40 parser:option("--lua-versions", 'Supported Lua versions. Accepted values are: "'..
41 table.concat(lua_versions, '", "')..'".')
42 :argname("<ver>")
43 :choices(lua_versions),
44 parser:option("--rockspec-format", 'Rockspec format version, such as "1.0" or "1.1".')
45 :argname("<ver>"),
46 parser:option("--tag", "Tag to use. Will attempt to extract version number from it."),
47 parser:option("--lib", "A comma-separated list of libraries that C files need to link to.")
48 :argname("<libs>")
49end
50
51function write_rockspec.add_to_parser(parser)
52 local cmd = parser:command("write_rockspec", [[
17This command writes an initial version of a rockspec file, 53This command writes an initial version of a rockspec file,
18based on a name, a version, and a location (an URL or a local path). 54based on a name, a version, and a location (an URL or a local path).
19If only two arguments are given, the first one is considered the name and the 55If only two arguments are given, the first one is considered the name and the
20second one is the location. 56second one is the location.
21If only one argument is given, it must be the location. 57If only one argument is given, it must be the location.
22If no arguments are given, current directory is used as location. 58If no arguments are given, current directory is used as the location.
23LuaRocks will attempt to infer name and version if not given, 59LuaRocks will attempt to infer name and version if not given,
24using 'dev' as a fallback default version. 60using 'dev' as a fallback default version.
25 61
26Note that the generated file is a _starting point_ for writing a 62Note that the generated file is a _starting point_ for writing a
27rockspec, and is not guaranteed to be complete or correct. 63rockspec, and is not guaranteed to be complete or correct. ]], util.see_also())
28 64 :summary("Write a template for a rockspec file.")
29--output=<file> Write the rockspec with the given filename. 65
30 If not given, a file is written in the current 66 parser:command("write-rockspec"):hidden(true):action(function(args) args.command = "write_rockspec" end)
31 directory with a filename based on given name and version. 67
32--license="<string>" A license string, such as "MIT/X11" or "GNU GPL v3". 68 cmd:argument("name", "Name of the rock.")
33--summary="<txt>" A short one-line description summary. 69 :args("?")
34--detailed="<txt>" A longer description string. 70 cmd:argument("version", "Rock version.")
35--homepage=<url> Project homepage. 71 :args("?")
36--lua-versions=<ver> Supported Lua versions. Accepted values are: "5.1", "5.2", 72 cmd:argument("location", "URL or path to the rock sources.")
37 "5.3", "5.4", "5.1,5.2", "5.2,5.3", "5.3,5.4", "5.1,5.2,5.3", 73 :args("?")
38 "5.2,5.3,5.4", or "5.1,5.2,5.3,5.4" 74
39--rockspec-format=<ver> Rockspec format version, such as "1.0" or "1.1". 75 write_rockspec.cmd_options(cmd)
40--tag=<tag> Tag to use. Will attempt to extract version number from it. 76end
41--lib=<lib>[,<lib>] A comma-separated list of libraries that C files need to
42 link to.
43]]
44 77
45local function open_file(name) 78local function open_file(name)
46 return io.open(dir.path(fs.current_dir(), name), "r") 79 return io.open(dir.path(fs.current_dir(), name), "r")
@@ -229,40 +262,42 @@ local function rockspec_cleanup(rockspec)
229 end 262 end
230end 263end
231 264
232function write_rockspec.command(flags, name, version, url_or_dir) 265function write_rockspec.command(args)
233 266
234 name = util.adjust_name_and_namespace(name, flags) 267 local name = util.adjust_name_and_namespace(args.name, args)
268 local version = args.version
269 local location = args.location
235 270
236 if not name then 271 if not name then
237 url_or_dir = "." 272 location = "."
238 elseif not version then 273 elseif not version then
239 url_or_dir = name 274 location = name
240 name = nil 275 name = nil
241 elseif not url_or_dir then 276 elseif not location then
242 url_or_dir = version 277 location = version
243 version = nil 278 version = nil
244 end 279 end
245 280
246 if flags["tag"] then 281 if args.tag then
247 if not version then 282 if not version then
248 version = flags["tag"]:gsub("^v", "") 283 version = args.tag:gsub("^v", "")
249 end 284 end
250 end 285 end
251 286
252 local protocol, pathname = dir.split_url(url_or_dir) 287 local protocol, pathname = dir.split_url(location)
253 if protocol == "file" then 288 if protocol == "file" then
254 if pathname == "." then 289 if pathname == "." then
255 name = name or dir.base_name(fs.current_dir()) 290 name = name or dir.base_name(fs.current_dir())
256 end 291 end
257 elseif dir.is_basic_protocol(protocol) then 292 elseif dir.is_basic_protocol(protocol) then
258 local filename = dir.base_name(url_or_dir) 293 local filename = dir.base_name(location)
259 local newname, newversion = filename:match("(.*)-([^-]+)") 294 local newname, newversion = filename:match("(.*)-([^-]+)")
260 if newname then 295 if newname then
261 name = name or newname 296 name = name or newname
262 version = version or newversion:gsub("%.[a-z]+$", ""):gsub("%.tar$", "") 297 version = version or newversion:gsub("%.[a-z]+$", ""):gsub("%.tar$", "")
263 end 298 end
264 else 299 else
265 name = name or dir.base_name(url_or_dir):gsub("%.[^.]+$", "") 300 name = name or dir.base_name(location):gsub("%.[^.]+$", "")
266 end 301 end
267 302
268 if not name then 303 if not name then
@@ -270,27 +305,27 @@ function write_rockspec.command(flags, name, version, url_or_dir)
270 end 305 end
271 version = version or "dev" 306 version = version or "dev"
272 307
273 local filename = flags["output"] or dir.path(fs.current_dir(), name:lower().."-"..version.."-1.rockspec") 308 local filename = args.output or dir.path(fs.current_dir(), name:lower().."-"..version.."-1.rockspec")
274 309
275 local url = detect_url(url_or_dir) 310 local url = detect_url(location)
276 local homepage = detect_homepage(url, flags["homepage"]) 311 local homepage = detect_homepage(url, args.homepage)
277 312
278 local rockspec, err = rockspecs.from_persisted_table(filename, { 313 local rockspec, err = rockspecs.from_persisted_table(filename, {
279 rockspec_format = flags["rockspec-format"], 314 rockspec_format = args.rockspec_format,
280 package = name, 315 package = name,
281 version = version.."-1", 316 version = version.."-1",
282 source = { 317 source = {
283 url = url, 318 url = url,
284 tag = flags["tag"], 319 tag = args.tag,
285 }, 320 },
286 description = { 321 description = {
287 summary = flags["summary"] or "*** please specify description summary ***", 322 summary = args.summary or "*** please specify description summary ***",
288 detailed = flags["detailed"] or "*** please enter a detailed description ***", 323 detailed = args.detailed or "*** please enter a detailed description ***",
289 homepage = homepage, 324 homepage = homepage,
290 license = flags["license"] or "*** please specify a license ***", 325 license = args.license or "*** please specify a license ***",
291 }, 326 },
292 dependencies = { 327 dependencies = {
293 lua_version_dep[flags["lua-versions"]], 328 lua_version_dep[args.lua_versions],
294 }, 329 },
295 build = {}, 330 build = {},
296 }) 331 })
@@ -301,19 +336,19 @@ function write_rockspec.command(flags, name, version, url_or_dir)
301 util.warning("Please specify supported Lua versions with --lua-versions=<ver>. "..util.see_help("write_rockspec")) 336 util.warning("Please specify supported Lua versions with --lua-versions=<ver>. "..util.see_help("write_rockspec"))
302 end 337 end
303 338
304 local local_dir = url_or_dir 339 local local_dir = location
305 340
306 if url_or_dir:match("://") then 341 if location:match("://") then
307 rockspec.source.file = dir.base_name(url_or_dir) 342 rockspec.source.file = dir.base_name(location)
308 if not dir.is_basic_protocol(rockspec.source.protocol) then 343 if not dir.is_basic_protocol(rockspec.source.protocol) then
309 if version ~= "dev" then 344 if version ~= "dev" then
310 rockspec.source.tag = flags["tag"] or "v" .. version 345 rockspec.source.tag = args.tag or "v" .. version
311 end 346 end
312 end 347 end
313 rockspec.source.dir = nil 348 rockspec.source.dir = nil
314 local ok, base_dir, temp_dir = fetch_url(rockspec) 349 local ok, base_dir, temp_dir = fetch_url(rockspec)
315 if ok then 350 if ok then
316 if base_dir ~= dir.base_name(url_or_dir) then 351 if base_dir ~= dir.base_name(location) then
317 rockspec.source.dir = base_dir 352 rockspec.source.dir = base_dir
318 end 353 end
319 end 354 end
@@ -329,10 +364,10 @@ function write_rockspec.command(flags, name, version, url_or_dir)
329 end 364 end
330 365
331 local libs = nil 366 local libs = nil
332 if flags["lib"] then 367 if args.lib then
333 libs = {} 368 libs = {}
334 rockspec.external_dependencies = {} 369 rockspec.external_dependencies = {}
335 for lib in flags["lib"]:gmatch("([^,]+)") do 370 for lib in args.lib:gmatch("([^,]+)") do
336 table.insert(libs, lib) 371 table.insert(libs, lib)
337 rockspec.external_dependencies[lib:upper()] = { 372 rockspec.external_dependencies[lib:upper()] = {
338 library = lib 373 library = lib
@@ -343,13 +378,13 @@ function write_rockspec.command(flags, name, version, url_or_dir)
343 local ok, err = fs.change_dir(local_dir) 378 local ok, err = fs.change_dir(local_dir)
344 if not ok then return nil, "Failed reaching files from project - error entering directory "..local_dir end 379 if not ok then return nil, "Failed reaching files from project - error entering directory "..local_dir end
345 380
346 if not (flags["summary"] and flags["detailed"]) then 381 if not (args.summary and args.detailed) then
347 local summary, detailed = detect_description() 382 local summary, detailed = detect_description()
348 rockspec.description.summary = flags["summary"] or summary 383 rockspec.description.summary = args.summary or summary
349 rockspec.description.detailed = flags["detailed"] or detailed 384 rockspec.description.detailed = args.detailed or detailed
350 end 385 end
351 386
352 if not flags["license"] then 387 if not args.license then
353 local license, fulltext = check_license() 388 local license, fulltext = check_license()
354 if license then 389 if license then
355 rockspec.description.license = license 390 rockspec.description.license = license
diff --git a/src/luarocks/deps.lua b/src/luarocks/deps.lua
index 9ee21772..cb85764e 100644
--- a/src/luarocks/deps.lua
+++ b/src/luarocks/deps.lua
@@ -170,12 +170,13 @@ function deps.fulfill_dependency(dep, deps_mode, name, version, rocks_provided,
170 return nil, "Could not satisfy dependency "..tostring(dep)..": "..search_err 170 return nil, "Could not satisfy dependency "..tostring(dep)..": "..search_err
171 end 171 end
172 util.printout("Installing "..url) 172 util.printout("Installing "..url)
173 local install_flags = { 173 local install_args = {
174 rock = url,
174 deps_mode = deps_mode, 175 deps_mode = deps_mode,
175 namespace = dep.namespace, 176 namespace = dep.namespace,
176 verify = verify, 177 verify = verify,
177 } 178 }
178 local ok, install_err, errcode = install.command(install_flags, url) 179 local ok, install_err, errcode = install.command(install_args)
179 if not ok then 180 if not ok then
180 return nil, "Failed installing dependency: "..url.." - "..install_err, errcode 181 return nil, "Failed installing dependency: "..url.." - "..install_err, errcode
181 end 182 end
@@ -569,23 +570,8 @@ function deps.check_lua_libdir(vars)
569 return nil, "Failed finding Lua library. You may need to configure LUA_LIBDIR.", "dependency" 570 return nil, "Failed finding Lua library. You may need to configure LUA_LIBDIR.", "dependency"
570end 571end
571 572
572local valid_deps_modes = { 573function deps.get_deps_mode(args)
573 one = true, 574 return args.deps_mode or cfg.deps_mode
574 order = true,
575 all = true,
576 none = true,
577}
578
579function deps.check_deps_mode_flag(flag)
580 return valid_deps_modes[flag]
581end
582
583function deps.get_deps_mode(flags)
584 if flags["deps-mode"] then
585 return flags["deps-mode"]
586 else
587 return cfg.deps_mode
588 end
589end 575end
590 576
591return deps 577return deps
diff --git a/src/luarocks/fs/lua.lua b/src/luarocks/fs/lua.lua
index 56583676..6028a925 100644
--- a/src/luarocks/fs/lua.lua
+++ b/src/luarocks/fs/lua.lua
@@ -1092,10 +1092,10 @@ end
1092 1092
1093--- Check if user has write permissions for the command. 1093--- Check if user has write permissions for the command.
1094-- Assumes the configuration variables under cfg have been previously set up. 1094-- Assumes the configuration variables under cfg have been previously set up.
1095-- @param flags table: the flags table passed to run() drivers. 1095-- @param args table: the args table passed to run() drivers.
1096-- @return boolean or (boolean, string): true on success, false on failure, 1096-- @return boolean or (boolean, string): true on success, false on failure,
1097-- plus an error message. 1097-- plus an error message.
1098function fs_lua.check_command_permissions(flags) 1098function fs_lua.check_command_permissions(args)
1099 local ok = true 1099 local ok = true
1100 local err = "" 1100 local err = ""
1101 for _, directory in ipairs { cfg.rocks_dir, cfg.deploy_lua_dir, cfg.deploy_bin_dir, cfg.deploy_lua_dir } do 1101 for _, directory in ipairs { cfg.rocks_dir, cfg.deploy_lua_dir, cfg.deploy_bin_dir, cfg.deploy_lua_dir } do
@@ -1124,7 +1124,7 @@ function fs_lua.check_command_permissions(flags)
1124 if ok then 1124 if ok then
1125 return true 1125 return true
1126 else 1126 else
1127 if flags["local"] or cfg.local_by_default then 1127 if args["local"] or cfg.local_by_default then
1128 err = err .. " \n-- please check your permissions." 1128 err = err .. " \n-- please check your permissions."
1129 else 1129 else
1130 err = err .. " \n-- you may want to run as a privileged user or use your local tree with --local." 1130 err = err .. " \n-- you may want to run as a privileged user or use your local tree with --local."
diff --git a/src/luarocks/manif/writer.lua b/src/luarocks/manif/writer.lua
index 23ba2532..d6b70a5f 100644
--- a/src/luarocks/manif/writer.lua
+++ b/src/luarocks/manif/writer.lua
@@ -279,7 +279,7 @@ function writer.make_rock_manifest(name, version)
279end 279end
280 280
281-- Writes a 'rock_namespace' file in a locally installed rock directory. 281-- Writes a 'rock_namespace' file in a locally installed rock directory.
282-- @param name string: the rock name (may be in user/rock format) 282-- @param name string: the rock name, without a namespace
283-- @param version string: the rock version 283-- @param version string: the rock version
284-- @param namespace string?: the namespace 284-- @param namespace string?: the namespace
285-- @return true if successful (or unnecessary, if there is no namespace), 285-- @return true if successful (or unnecessary, if there is no namespace),
@@ -288,8 +288,6 @@ function writer.make_namespace_file(name, version, namespace)
288 assert(type(name) == "string" and not name:match("/")) 288 assert(type(name) == "string" and not name:match("/"))
289 assert(type(version) == "string") 289 assert(type(version) == "string")
290 assert(type(namespace) == "string" or not namespace) 290 assert(type(namespace) == "string" or not namespace)
291 name = util.adjust_name_and_namespace(name, { namespace = namespace })
292 name, namespace = util.split_namespace(name)
293 if not namespace then 291 if not namespace then
294 return true 292 return true
295 end 293 end
diff --git a/src/luarocks/upload/api.lua b/src/luarocks/upload/api.lua
index 0e71432f..a28b517a 100644
--- a/src/luarocks/upload/api.lua
+++ b/src/luarocks/upload/api.lua
@@ -245,20 +245,20 @@ end
245 245
246end 246end
247 247
248function api.new(flags) 248function api.new(args)
249 local self = {} 249 local self = {}
250 setmetatable(self, { __index = Api }) 250 setmetatable(self, { __index = Api })
251 self.config = self:load_config() or {} 251 self.config = self:load_config() or {}
252 self.config.server = flags["server"] or self.config.server or cfg.upload.server 252 self.config.server = args.server or self.config.server or cfg.upload.server
253 self.config.version = self.config.version or cfg.upload.version 253 self.config.version = self.config.version or cfg.upload.version
254 self.config.key = flags["temp-key"] or flags["api-key"] or self.config.key 254 self.config.key = args.temp_key or args.api_key or self.config.key
255 self.debug = flags["debug"] 255 self.debug = args.debug
256 if not self.config.key then 256 if not self.config.key then
257 return nil, "You need an API key to upload rocks.\n" .. 257 return nil, "You need an API key to upload rocks.\n" ..
258 "Navigate to "..self.config.server.."/settings to get a key\n" .. 258 "Navigate to "..self.config.server.."/settings to get a key\n" ..
259 "and then pass it through the --api-key=<key> flag." 259 "and then pass it through the --api-key=<key> flag."
260 end 260 end
261 if flags["api-key"] then 261 if args.api_key then
262 self:save_config() 262 self:save_config()
263 end 263 end
264 return self 264 return self
diff --git a/src/luarocks/util.lua b/src/luarocks/util.lua
index 8b84df4d..d4461c92 100644
--- a/src/luarocks/util.lua
+++ b/src/luarocks/util.lua
@@ -77,162 +77,6 @@ function util.matchquote(s)
77 return (s:gsub("[?%-+*%[%].%%()$^]","%%%1")) 77 return (s:gsub("[?%-+*%[%].%%()$^]","%%%1"))
78end 78end
79 79
80--- List of supported arguments.
81-- Arguments that take no parameters are marked with the boolean true.
82-- Arguments that take a parameter are marked with a descriptive string.
83-- Arguments that may take an empty string are described in quotes,
84-- (as in the value for --detailed="<text>").
85-- For all other string values, it means the parameter is mandatory.
86local supported_flags = {
87 ["all"] = true,
88 ["api-key"] = "<key>",
89 ["append"] = true,
90 ["arch"] = "<arch>",
91 ["bin"] = true,
92 ["binary"] = true,
93 ["branch"] = "<branch-name>",
94 ["build-deps"] = true,
95 ["debug"] = true,
96 ["deps"] = true,
97 ["deps-mode"] = "<mode>",
98 ["detailed"] = "\"<text>\"",
99 ["dev"] = true,
100 ["dir"] = "<path>",
101 ["force"] = true,
102 ["force-fast"] = true,
103 ["from"] = "<server>",
104 ["global"] = true,
105 ["help"] = true,
106 ["home"] = true,
107 ["homepage"] = "\"<url>\"",
108 ["index"] = true,
109 ["issues"] = true,
110 ["json"] = true,
111 ["keep"] = true,
112 ["labels"] = true,
113 ["lib"] = "<library>",
114 ["license"] = "\"<text>\"",
115 ["list"] = true,
116 ["local"] = true,
117 ["local-tree"] = true,
118 ["lr-bin"] = true,
119 ["lr-cpath"] = true,
120 ["lr-path"] = true,
121 ["lua-dir"] = "<path>",
122 ["lua-version"] = "<vers>",
123 ["lua-versions"] = "<versions>",
124 ["lua-ver"] = true,
125 ["lua-incdir"] = true,
126 ["lua-libdir"] = true,
127 ["modules"] = true,
128 ["mversion"] = true,
129 ["namespace"] = "<namespace>",
130 ["no-bin"] = true,
131 ["no-doc"] = true,
132 ["no-refresh"] = true,
133 ["nodeps"] = true,
134 ["old-versions"] = true,
135 ["only-deps"] = true,
136 ["only-from"] = "<server>",
137 ["only-server"] = "<server>",
138 ["only-sources"] = "<url>",
139 ["only-sources-from"] = "<url>",
140 ["outdated"] = true,
141 ["output"] = "<file>",
142 ["pack-binary-rock"] = true,
143 ["porcelain"] = true,
144 ["project-tree"] = "<tree>",
145 ["quick"] = true,
146 ["reset"] = true,
147 ["rock-dir"] = true,
148 ["rock-license"] = true,
149 ["rock-namespace"] = true,
150 ["rock-tree"] = true,
151 ["rock-trees"] = true,
152 ["rockspec"] = true,
153 ["rockspec-format"] = "<ver>",
154 ["scope"] = "<system|user|project>",
155 ["server"] = "<server>",
156 ["sign"] = true,
157 ["skip-pack"] = true,
158 ["source"] = true,
159 ["summary"] = "\"<text>\"",
160 ["system-config"] = true,
161 ["tag"] = "<tag>",
162 ["test-type"] = "<type>",
163 ["temp-key"] = "<key>",
164 ["timeout"] = "<seconds>",
165 ["to"] = "<path>",
166 ["tree"] = "<path>",
167 ["unset"] = true,
168 ["user-config"] = true,
169 ["verbose"] = true,
170 ["verify"] = true,
171 ["version"] = true,
172}
173
174--- Extract flags from an arguments list.
175-- Given string arguments, extract flag arguments into a flags set.
176-- For example, given "foo", "--tux=beep", "--bla", "bar", "--baz",
177-- it would return the following:
178-- {["bla"] = true, ["tux"] = "beep", ["baz"] = true}, "foo", "bar".
179function util.parse_flags(...)
180 local args = {...}
181 local flags = {}
182 local i = 1
183 local out = {}
184 local state = "initial"
185 while i <= #args do
186 local flag = args[i]:match("^%-%-(.*)")
187 if state == "initial" and flag == "" then
188 state = "ignore_flags"
189 elseif state == "initial" and flag then
190 local var,val = flag:match("([a-z_%-]*)=(.*)")
191 if val then
192 local vartype = supported_flags[var]
193 if type(vartype) == "string" then
194 if val == "" and vartype:sub(1,1) ~= '"' then
195 return { ERROR = "Invalid argument: parameter to flag --"..var.."="..vartype.." cannot be empty." }
196 end
197 flags[var] = val
198 else
199 if vartype then
200 return { ERROR = "Invalid argument: flag --"..var.." does not take an parameter." }
201 else
202 return { ERROR = "Invalid argument: unknown flag --"..var.."." }
203 end
204 end
205 else
206 local var = flag
207 local vartype = supported_flags[var]
208 if type(vartype) == "string" then
209 i = i + 1
210 local val = args[i]
211 if not val then
212 return { ERROR = "Invalid argument: flag --"..var.."="..vartype.." expects a parameter." }
213 end
214 if val:match("^%-%-.*") then
215 return { ERROR = "Invalid argument: flag --"..var.."="..vartype.." expects a parameter (if you really want to pass "..val.." as an argument to --"..var..", use --"..var.."="..val..")." }
216 else
217 if val == "" and vartype:sub(1,1) ~= '"' then
218 return { ERROR = "Invalid argument: parameter to flag --"..var.."="..vartype.." cannot be empty." }
219 end
220 flags[var] = val
221 end
222 elseif vartype == true then
223 flags[var] = true
224 else
225 return { ERROR = "Invalid argument: unknown flag --"..var.."." }
226 end
227 end
228 elseif state == "ignore_flags" or (state == "initial" and not flag) then
229 table.insert(out, args[i])
230 end
231 i = i + 1
232 end
233 return flags, unpack(out)
234end
235
236local var_format_pattern = "%$%((%a[%a%d_]+)%)" 80local var_format_pattern = "%$%((%a[%a%d_]+)%)"
237 81
238-- Check if a set of needed variables are referenced 82-- Check if a set of needed variables are referenced
@@ -371,29 +215,36 @@ function util.this_program(default)
371 return prog 215 return prog
372end 216end
373 217
374function util.deps_mode_help(program) 218function util.deps_mode_option(parser, program)
375 local cfg = require("luarocks.core.cfg") 219 local cfg = require("luarocks.core.cfg")
376 return [[ 220
377--deps-mode=<mode> How to handle dependencies. Four modes are supported: 221 parser:option("--deps-mode", "How to handle dependencies. Four modes are supported:\n"..
378 * all - use all trees from the rocks_trees list 222 "* all - use all trees from the rocks_trees list for finding dependencies\n"..
379 for finding dependencies 223 "* one - use only the current tree (possibly set with --tree)\n"..
380 * one - use only the current tree (possibly set 224 "* order - use trees based on order (use the current tree and all "..
381 with --tree) 225 "trees below it on the rocks_trees list)\n"..
382 * order - use trees based on order (use the current 226 "* none - ignore dependencies altogether.\n"..
383 tree and all trees below it on the rocks_trees list) 227 "The default mode may be set with the deps_mode entry in the configuration file.\n"..
384 * none - ignore dependencies altogether. 228 'The current default is "'..cfg.deps_mode..'".\n'..
385 The default mode may be set with the deps_mode entry 229 "Type '"..util.this_program(program or "luarocks").."' with no "..
386 in the configuration file. 230 "arguments to see your list of rocks trees.")
387 The current default is "]]..cfg.deps_mode..[[". 231 :argname("<mode>")
388 Type ']]..util.this_program(program or "luarocks")..[[' with no arguments to see 232 :choices({"all", "one", "order", "none"})
389 your list of rocks trees. 233 parser:flag("--nodeps"):hidden(true)
390]]
391end 234end
392 235
393function util.see_help(command, program) 236function util.see_help(command, program)
394 return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'." 237 return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'."
395end 238end
396 239
240function util.see_also(text)
241 local see_also = "See also:\n"
242 if text then
243 see_also = see_also..text.."\n"
244 end
245 return see_also.." '"..util.this_program("luarocks").." help' for general options and configuration."
246end
247
397function util.announce_install(rockspec) 248function util.announce_install(rockspec)
398 local cfg = require("luarocks.core.cfg") 249 local cfg = require("luarocks.core.cfg")
399 local path = require("luarocks.path") 250 local path = require("luarocks.path")
@@ -487,13 +338,13 @@ function util.LQ(s)
487 return ("%q"):format(s) 338 return ("%q"):format(s)
488end 339end
489 340
490--- Normalize the --namespace flag and the user/rock syntax for namespaces. 341--- Normalize the --namespace option and the user/rock syntax for namespaces.
491-- If a namespace is given in user/rock syntax, update the --namespace flag; 342-- If a namespace is given in user/rock syntax, update the --namespace option;
492-- If a namespace is given in --namespace flag, update the user/rock syntax. 343-- If a namespace is given in --namespace option, update the user/rock syntax.
493-- In case of conflicts, the user/rock syntax takes precedence. 344-- In case of conflicts, the user/rock syntax takes precedence.
494function util.adjust_name_and_namespace(ns_name, flags) 345function util.adjust_name_and_namespace(ns_name, args)
495 assert(type(ns_name) == "string" or not ns_name) 346 assert(type(ns_name) == "string" or not ns_name)
496 assert(type(flags) == "table") 347 assert(type(args) == "table")
497 348
498 if not ns_name then 349 if not ns_name then
499 return 350 return
@@ -503,10 +354,10 @@ function util.adjust_name_and_namespace(ns_name, flags)
503 354
504 local name, namespace = util.split_namespace(ns_name) 355 local name, namespace = util.split_namespace(ns_name)
505 if namespace then 356 if namespace then
506 flags["namespace"] = namespace 357 args.namespace = namespace
507 end 358 end
508 if flags["namespace"] then 359 if args.namespace then
509 name = flags["namespace"] .. "/" .. name 360 name = args.namespace .. "/" .. name
510 end 361 end
511 return name:lower() 362 return name:lower()
512end 363end