Skip to content

Commit 03d68f0

Browse files
committed
Merge pull request #40 from o-lim/arg-callback
Add callbacks for argument parsing
2 parents ed258b5 + 73133b3 commit 03d68f0

File tree

2 files changed

+122
-26
lines changed

2 files changed

+122
-26
lines changed

spec/cliargs_parsing_spec.lua

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -315,13 +315,14 @@ describe("Testing cliargs library parsing commandlines", function()
315315
assert.are.equal("", result.compress)
316316
end)
317317

318-
describe("Tests parsing with callback", function()
318+
describe("Tests options parsing with callback", function()
319319
local cb = {}
320320

321321
local function callback(key, value, altkey, opt)
322322
cb.key, cb.value, cb.altkey = key, value, altkey
323323
return true
324324
end
325+
325326
local function callback_fail(key, value, altkey, opt)
326327
return nil, "bad argument to " .. opt
327328
end
@@ -331,42 +332,124 @@ describe("Testing cliargs library parsing commandlines", function()
331332
end)
332333

333334
it("tests short-key option", function()
334-
cli:add_option("-k, --long-key=VALUE", "key descriptioin", "", callback)
335+
cli:add_option("-k, --long-key=VALUE", "key description", "", callback)
335336
local expected = { k = "myvalue", ["long-key"] = "myvalue" }
336337
local result = cli:parse({ "-k", "myvalue" })
337338
assert.are.same(expected, result)
338-
assert.are.equal(cb.key, "k")
339-
assert.are.equal(cb.value, "myvalue")
340-
assert.are.equal(cb.altkey, "long-key")
339+
assert.are.equal("k", cb.key)
340+
assert.are.equal("myvalue", cb.value)
341+
assert.are.equal("long-key", cb.altkey)
341342
end)
342343

343344
it("tests expanded-key option", function()
344-
cli:add_option("-k, --long-key=VALUE", "key descriptioin", "", callback)
345+
cli:add_option("-k, --long-key=VALUE", "key description", "", callback)
345346
local expected = { k = "val", ["long-key"] = "val" }
346347
local result = cli:parse({ "--long-key", "val" })
347348
assert.are.same(expected, result)
348-
assert.are.equal(cb.key, "long-key")
349-
assert.are.equal(cb.value, "val")
350-
assert.are.equal(cb.altkey, "k")
349+
assert.are.equal("long-key", cb.key)
350+
assert.are.equal("val", cb.value)
351+
assert.are.equal("k", cb.altkey)
351352
end)
352353

353354
it("tests expanded-key flag with not short-key", function()
354355
cli:add_flag("--version", "prints the version and exits", callback)
355356
local expected = { version = true }
356357
local result = cli:parse({ "--version" })
357358
assert.are.same(expected, result)
358-
assert.are.equal(cb.key, "version")
359-
assert.are.equal(cb.value, true)
360-
assert.are.equal(cb.altkey, nil)
359+
assert.are.equal("version", cb.key)
360+
assert.are.equal(true, cb.value)
361+
assert.are.equal(nil, cb.altkey)
361362
end)
362363

363364
it("tests callback returning error", function()
364365
cli:set_name('myapp')
365-
cli:add_option("-k, --long-key=VALUE", "key descriptioin", "", callback_fail)
366+
cli:add_option("-k, --long-key=VALUE", "key description", "", callback_fail)
366367
local result, err = cli:parse({ "--long-key", "val" }, true --[[no print]])
367368
assert(result == nil, "Failure in callback returns nil")
368369
assert(type(err) == "string", "Expected an error string")
369370
assert.are.equal(err, "myapp: error: bad argument to --long-key; re-run with --help for usage.")
370371
end)
371372
end)
373+
374+
describe("Tests argument parsing with callback", function()
375+
local cb = {}
376+
377+
local function callback(key, value)
378+
cb.key, cb.value = key, value
379+
return true
380+
end
381+
382+
local function callback_arg(key, value)
383+
table.insert(cb, { key = key, value = value })
384+
return true
385+
end
386+
387+
local function callback_fail(key, value)
388+
return nil, "bad argument for " .. key
389+
end
390+
391+
before_each(function()
392+
cb = {}
393+
end)
394+
395+
it("tests one required argument", function()
396+
cli:add_arg("ARG", "arg description", callback)
397+
local expected = { ARG = "arg_val" }
398+
local result = cli:parse({ "arg_val" })
399+
assert.are.same(expected, result)
400+
assert.are.equal("ARG", cb.key)
401+
assert.are.equal("arg_val", cb.value)
402+
end)
403+
404+
it("tests required argument callback returning error", function()
405+
cli:set_name('myapp')
406+
cli:add_arg("ARG", "arg description", callback_fail)
407+
local expected = { ARG = "arg_val" }
408+
local result, err = cli:parse({ "arg_val" }, true --[[no print]])
409+
assert(result == nil, "Failure in callback returns nil")
410+
assert(type(err) == "string", "Expected an error string")
411+
assert.are.equal(err, "myapp: error: bad argument for ARG; re-run with --help for usage.")
412+
end)
413+
414+
it("tests many required arguments", function()
415+
cli:add_arg("ARG1", "arg1 description", callback_arg)
416+
cli:add_arg("ARG2", "arg2 description", callback_arg)
417+
cli:add_arg("ARG3", "arg3 description", callback_arg)
418+
local expected = { ARG1 = "arg1_val", ARG2 = "arg2_val", ARG3 = "arg3_val" }
419+
local result = cli:parse({ "arg1_val", "arg2_val", "arg3_val" })
420+
assert.are.same(expected, result)
421+
assert.are.same({ key = "ARG1", value = "arg1_val"}, cb[1])
422+
assert.are.same({ key = "ARG2", value = "arg2_val"}, cb[2])
423+
assert.are.same({ key = "ARG3", value = "arg3_val"}, cb[3])
424+
end)
425+
426+
it("tests one optional argument", function()
427+
cli:optarg("OPTARG", "optional arg description", nil, 1, callback)
428+
local expected = { OPTARG = "opt_arg" }
429+
local result = cli:parse({ "opt_arg" })
430+
assert.are.same(expected, result)
431+
assert.are.equal("OPTARG", cb.key)
432+
assert.are.equal("opt_arg", cb.value)
433+
end)
434+
435+
it("tests optional argument callback returning error", function()
436+
cli:set_name('myapp')
437+
cli:optarg("OPTARG", "optinoal arg description", nil, 1, callback_fail)
438+
local expected = { ARG = "arg_val" }
439+
local result, err = cli:parse({ "opt_arg" }, true --[[no print]])
440+
assert(result == nil, "Failure in callback returns nil")
441+
assert(type(err) == "string", "Expected an error string")
442+
assert.are.equal(err, "myapp: error: bad argument for OPTARG; re-run with --help for usage.")
443+
end)
444+
445+
it("tests many optional arguments", function()
446+
cli:optarg("OPTARG", "optional arg description", nil, 3, callback_arg)
447+
local expected = { OPTARG = { "opt_arg1", "opt_arg2", "opt_arg3" } }
448+
local result = cli:parse({ "opt_arg1", "opt_arg2", "opt_arg3" })
449+
assert.are.same(expected, result)
450+
assert.are.same({ key = "OPTARG", value = "opt_arg1"}, cb[1])
451+
assert.are.same({ key = "OPTARG", value = "opt_arg2"}, cb[2])
452+
assert.are.same({ key = "OPTARG", value = "opt_arg3"}, cb[3])
453+
end)
454+
end)
372455
end)

src/cliargs.lua

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ local function disect(key)
9595
return k,ek,v
9696
end
9797

98+
local function callable(fn)
99+
return type(fn) == "function" or (getmetatable(fn) or {}).__call
100+
end
101+
98102

99103
function cli_error(msg, noprint)
100104
local msg = cli.name .. ": error: " .. msg .. '; re-run with --help for usage.'
@@ -140,23 +144,25 @@ end
140144
--- ### Parameters
141145
--- 1. **key**: the argument's "name" that will be displayed to the user
142146
--- 2. **desc**: a description of the argument
147+
--- 3. **callback**: *optional*; specify a function to call when this argument is parsed (the default is nil)
143148
---
144149
--- ### Usage example
145150
--- The following will parse the argument (if specified) and set its value in `args["root"]`:
146151
--- `cli:add_arg("root", "path to where root scripts can be found")`
147-
function cli:add_arg(key, desc)
152+
function cli:add_arg(key, desc, callback)
148153
assert(type(key) == "string" and type(desc) == "string", "Key and description are mandatory arguments (Strings)")
154+
assert(callable(callback) or callback == nil, "Callback argument: expected a function or nil")
149155

150156
if self:__lookup(key, nil, self.required) then
151157
error("Duplicate argument: " .. key .. ", please rename one of them.")
152158
end
153159

154-
table.insert(self.required, { key = key, desc = desc, value = nil })
160+
table.insert(self.required, { key = key, desc = desc, value = nil, callback = callback })
155161
if #key > self.maxlabel then self.maxlabel = #key end
156162
end
157163

158164
--- Defines an optional argument (or more than one).
159-
--- There can be only 1 optional argument, and is has to be the last one on the argumentlist.
165+
--- There can be only 1 optional argument, and it has to be the last one on the argumentlist.
160166
--- *Note:* the value will be stored in `args[@key]`. The value will be a 'string' if 'maxcount == 1',
161167
--- or a table if 'maxcount > 1'
162168
---
@@ -165,19 +171,21 @@ end
165171
--- 2. **desc**: a description of the argument
166172
--- 3. **default**: *optional*; specify a default value (the default is nil)
167173
--- 4. **maxcount**: *optional*; specify the maximum number of occurences allowed (default is 1)
174+
--- 5. **callback**: *optional*; specify a function to call when this argument is parsed (the default is nil)
168175
---
169176
--- ### Usage example
170177
--- The following will parse the argument (if specified) and set its value in `args["root"]`:
171178
--- `cli:add_arg("root", "path to where root scripts can be found", "", 2)`
172179
--- The value returned will be a table with at least 1 entry and a maximum of 2 entries
173-
function cli:optarg(key, desc, default, maxcount)
180+
function cli:optarg(key, desc, default, maxcount, callback)
174181
assert(type(key) == "string" and type(desc) == "string", "Key and description are mandatory arguments (Strings)")
175182
assert(type(default) == "string" or default == nil, "Default value must either be omitted or be a string")
176183
maxcount = maxcount or 1
177184
maxcount = tonumber(maxcount)
178185
assert(maxcount and maxcount>0 and maxcount<1000,"Maxcount must be a number from 1 to 999")
186+
assert(callable(callback) or callback == nil, "Callback argument: expected a function or nil")
179187

180-
self.optargument = { key = key, desc = desc, default = default, maxcount = maxcount, value = nil }
188+
self.optargument = { key = key, desc = desc, default = default, maxcount = maxcount, value = nil, callback = callback }
181189
if #key > self.maxlabel then self.maxlabel = #key end
182190
end
183191

@@ -210,6 +218,7 @@ function cli:add_opt(key, desc, default, callback)
210218
-- 8. --expanded=VALUE
211219

212220
assert(type(key) == "string" and type(desc) == "string", "Key and description are mandatory arguments (Strings)")
221+
assert(callable(callback) or callback == nil, "Callback argument: expected a function or nil")
213222
assert(
214223
(
215224
type(default) == "string"
@@ -219,14 +228,6 @@ function cli:add_opt(key, desc, default, callback)
219228
),
220229
"Default argument: expected a string, nil, or {}"
221230
)
222-
assert(
223-
(
224-
type(callback) == "function"
225-
or callback == nil
226-
or (getmetatable(callback) or {}).__call
227-
),
228-
"Callback argument: expected a function or nil"
229-
)
230231

231232
local k, ek, v = disect(key)
232233

@@ -409,6 +410,12 @@ function cli:parse(arguments, noprint, dump)
409410
-- deal with required args here
410411
for i, entry in ipairs(self.required) do
411412
entry.value = args[1]
413+
if entry.callback then
414+
local status, err = entry.callback(entry.key, entry.value)
415+
if status == nil and err then
416+
return cli_error(err, noprint)
417+
end
418+
end
412419
table.remove(args, 1)
413420
end
414421
-- deal with the last optional argument
@@ -419,6 +426,12 @@ function cli:parse(arguments, noprint, dump)
419426
else
420427
self.optargument.value = args[1]
421428
end
429+
if self.optargument.callback then
430+
local status, err = self.optargument.callback(self.optargument.key, args[1])
431+
if status == nil and err then
432+
return cli_error(err, noprint)
433+
end
434+
end
422435
table.remove(args,1)
423436
end
424437
-- if necessary set the defaults for the last optional argument here

0 commit comments

Comments
 (0)