1before: 2 hell = require "specl.shell" 3 4specify std.optparse: 5- before: | 6 OptionParser = require "std.optparse" 7 8 help = [[ 9 parseme (stdlib spec) 0α1 10 11 Copyright © 2015 Gary V. Vaughan 12 This test program comes with ABSOLUTELY NO WARRANTY. 13 14 Usage: parseme [<options>] <file>... 15 16 Banner text. 17 18 Long description. 19 20 Options: 21 22 -h, --help display this help, then exit 23 --version display version information, then exit 24 -b a short option with no long option 25 --long a long option with no short option 26 --another-long a long option with internal hypen 27 --true a Lua keyword as an option name 28 -v, --verbose a combined short and long option 29 -n, --dryrun, --dry-run several spellings of the same option 30 -u, --name=USER require an argument 31 -o, --output=[FILE] accept an optional argument 32 -- end of options 33 34 Footer text. 35 36 Please report bugs at <http://github.com/lua-stdlib/lua-stdlib/issues>. 37 ]] 38 39 -- strip off the leading whitespace required for YAML 40 parser = OptionParser (help:gsub ("^ ", "")) 41 42- context when required: 43 - context by name: 44 - it does not touch the global table: 45 expect (show_apis {added_to="_G", by="std.optparse"}). 46 to_equal {} 47 48- describe OptionParser: 49 - it recognises the program name: 50 expect (parser.program).to_be "parseme" 51 - it recognises the version number: 52 expect (parser.version).to_be "0α1" 53 - it recognises the version text: 54 expect (parser.versiontext). 55 to_match "^parseme .*Copyright .*NO WARRANTY%." 56 - it recognises the help text: | 57 expect (parser.helptext). 58 to_match ("^Usage: parseme .*Banner .*Long .*Options:.*" .. 59 "Footer .*/issues>%.") 60 - it diagnoses incorrect input text: 61 expect (OptionParser "garbage in").to_raise "argument must match" 62 63- describe parser: 64 - before: | 65 code = [[ 66 package.path = "]] .. package.path .. [[" 67 local OptionParser = require 'std.optparse' 68 local help = [=[]] .. help .. [[]=] 69 help = help:match ("^[%s\n]*(.-)[%s\n]*$") 70 71 local parser = OptionParser (help) 72 local arg, opts = parser:parse (_G.arg) 73 74 o = {} 75 for k, v in pairs (opts) do 76 table.insert (o, k .. " = " .. tostring (v)) 77 end 78 if #o > 0 then 79 table.sort (o) 80 print ("opts = { " .. table.concat (o, ", ") .. " }") 81 end 82 if #arg > 0 then 83 print ("args = { " .. table.concat (arg, ", ") .. " }") 84 end 85 ]] 86 parse = bind (luaproc, {code}) 87 88 - it responds to --version with version text: 89 expect (parse {"--version"}). 90 to_match_output "^%s*parseme .*Copyright .*NO WARRANTY%.\n$" 91 - it responds to --help with help text: | 92 expect (parse {"--help"}). 93 to_match_output ("^%s*Usage: parseme .*Banner.*Long.*" .. 94 "Options:.*Footer.*/issues>%.\n$") 95 - it leaves behind unrecognised short options: 96 expect (parse {"-x"}).to_output "args = { -x }\n" 97 - it recognises short options: 98 expect (parse {"-b"}).to_output "opts = { b = true }\n" 99 - it leaves behind unrecognised options: 100 expect (parse {"--not-an-option"}). 101 to_output "args = { --not-an-option }\n" 102 - it recognises long options: 103 expect (parse {"--long"}).to_output "opts = { long = true }\n" 104 - it recognises long options with hyphens: 105 expect (parse {"--another-long"}). 106 to_output "opts = { another_long = true }\n" 107 - it recognises long options named after Lua keywords: 108 expect (parse {"--true"}).to_output "opts = { true = true }\n" 109 - it recognises combined short and long option specs: 110 expect (parse {"-v"}).to_output "opts = { verbose = true }\n" 111 expect (parse {"--verbose"}).to_output "opts = { verbose = true }\n" 112 - it recognises options with several spellings: 113 expect (parse {"-n"}).to_output "opts = { dry_run = true }\n" 114 expect (parse {"--dry-run"}).to_output "opts = { dry_run = true }\n" 115 expect (parse {"--dryrun"}).to_output "opts = { dry_run = true }\n" 116 - it recognises end of options marker: 117 expect (parse {"-- -n"}).to_output "args = { -n }\n" 118 - context given an unhandled long option: 119 - it leaves behind unmangled argument: 120 expect (parse {"--not-an-option=with-an-argument"}). 121 to_output "args = { --not-an-option=with-an-argument }\n" 122 - context given an option with a required argument: 123 - it records an argument to a long option following an '=' delimiter: 124 expect (parse {"--name=Gary"}). 125 to_output "opts = { name = Gary }\n" 126 - it records an argument to a short option without a space: 127 expect (parse {"-uGary"}). 128 to_output "opts = { name = Gary }\n" 129 - it records an argument to a long option following a space: 130 expect (parse {"--name Gary"}). 131 to_output "opts = { name = Gary }\n" 132 - it records an argument to a short option following a space: 133 expect (parse {"-u Gary"}). 134 to_output "opts = { name = Gary }\n" 135 - it diagnoses a missing argument: 136 expect (parse {"--name"}). 137 to_contain_error "'--name' requires an argument" 138 expect (parse {"-u"}). 139 to_contain_error "'-u' requires an argument" 140 - context given an option with an optional argument: 141 - it records an argument to a long option following an '=' delimiter: 142 expect (parse {"--output=filename"}). 143 to_output "opts = { output = filename }\n" 144 - it records an argument to a short option without a space: 145 expect (parse {"-ofilename"}). 146 to_output "opts = { output = filename }\n" 147 - it records an argument to a long option following a space: 148 expect (parse {"--output filename"}). 149 to_output "opts = { output = filename }\n" 150 - it records an argument to a short option following a space: 151 expect (parse {"-o filename"}). 152 to_output "opts = { output = filename }\n" 153 - it doesn't consume the following option: 154 expect (parse {"--output -v"}). 155 to_output "opts = { output = true, verbose = true }\n" 156 expect (parse {"-o -v"}). 157 to_output "opts = { output = true, verbose = true }\n" 158 - context when splitting combined short options: 159 - it separates non-argument options: 160 expect (parse {"-bn"}). 161 to_output "opts = { b = true, dry_run = true }\n" 162 expect (parse {"-vbn"}). 163 to_output "opts = { b = true, dry_run = true, verbose = true }\n" 164 - it stops separating at a required argument option: 165 expect (parse {"-vuname"}). 166 to_output "opts = { name = name, verbose = true }\n" 167 expect (parse {"-vuob"}). 168 to_output "opts = { name = ob, verbose = true }\n" 169 - it stops separating at an optional argument option: 170 expect (parse {"-vofilename"}). 171 to_output "opts = { output = filename, verbose = true }\n" 172 expect (parse {"-vobn"}). 173 to_output "opts = { output = bn, verbose = true }\n" 174 - it leaves behind unsplittable short options: 175 expect (parse {"-xvb"}).to_output "args = { -xvb }\n" 176 expect (parse {"-vxb"}).to_output "args = { -vxb }\n" 177 expect (parse {"-vbx"}).to_output "args = { -vbx }\n" 178 - it separates short options before unsplittable options: 179 expect (parse {"-vb -xvb"}). 180 to_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" 181 expect (parse {"-vb -vxb"}). 182 to_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" 183 expect (parse {"-vb -vbx"}). 184 to_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" 185 - it separates short options after unsplittable options: 186 expect (parse {"-xvb -vb"}). 187 to_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" 188 expect (parse {"-vxb -vb"}). 189 to_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" 190 expect (parse {"-vbx -vb"}). 191 to_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" 192 193 - context with option defaults: 194 - before: | 195 function main (arg) 196 local OptionParser = require "std.optparse" 197 local parser = OptionParser ("program 0\nUsage: program\n" .. 198 " -x set x\n" .. 199 " -y set y\n" .. 200 " -z set z\n") 201 local state = { arg = {}, opts = { x={"t"}, y=false }} 202 state.arg, state.opts = parser:parse (arg, state.opts) 203 return state 204 end 205 - it prefers supplied argument: 206 expect (main {"-x", "-y"}). 207 to_equal { arg = {}, opts = { x=true, y=true }} 208 expect (main {"-x", "-y", "-z"}). 209 to_equal { arg = {}, opts = { x=true, y=true, z=true }} 210 expect (main {"-w", "-x", "-y"}). 211 to_equal { arg = {"-w"}, opts = { x=true, y=true }} 212 - it defers to default value: 213 expect (main {}). 214 to_equal { arg = {}, opts = { x={"t"}, y=false }} 215 expect (main {"-z"}). 216 to_equal { arg = {}, opts = { x={"t"}, y=false, z=true }} 217 expect (main {"-w"}). 218 to_equal { arg = {"-w"}, opts = { x={"t"}, y=false }} 219 220 - context with io.die: 221 - before: | 222 runscript = function (code) 223 return luaproc ([[ 224 local OptionParser = require "std.optparse" 225 local parser = OptionParser ("program 0\nUsage: program\n") 226 _G.arg, _G.opts = parser:parse (_G.arg) 227 ]] .. code .. [[ 228 require "std.io".die "By 'eck!" 229 ]]) 230 end 231 - it prefers `prog.name` to `opts.program`: | 232 code = [[prog = { file = "file", name = "name" }]] 233 expect (runscript (code)).to_fail_while_matching ": name: By 'eck!\n" 234 - it prefers `prog.file` to `opts.program`: | 235 code = [[prog = { file = "file" }]] 236 expect (runscript (code)).to_fail_while_matching ": file: By 'eck!\n" 237 - it appends `prog.line` if any to `prog.file` over using `opts`: | 238 code = [[ 239 prog = { file = "file", line = 125 }; opts.line = 99]] 240 expect (runscript (code)). 241 to_fail_while_matching ": file:125: By 'eck!\n" 242 - it prefixes `opts.program` if any: | 243 expect (runscript ("")).to_fail_while_matching ": program: By 'eck!\n" 244 - it appends `opts.line` if any, to `opts.program`: | 245 code = [[opts.line = 99]] 246 expect (runscript (code)). 247 to_fail_while_matching ": program:99: By 'eck!\n" 248 249 - context with io.warn: 250 - before: | 251 runscript = function (code) 252 return luaproc ([[ 253 local OptionParser = require "std.optparse" 254 local parser = OptionParser ("program 0\nUsage: program\n") 255 _G.arg, _G.opts = parser:parse (_G.arg) 256 ]] .. code .. [[ 257 require "std.io".warn "Ayup!" 258 ]]) 259 end 260 - it prefers `prog.name` to `opts.program`: | 261 code = [[prog = { file = "file", name = "name" }]] 262 expect (runscript (code)).to_output_error "name: Ayup!\n" 263 - it prefers `prog.file` to `opts.program`: | 264 code = [[prog = { file = "file" }]] 265 expect (runscript (code)).to_output_error "file: Ayup!\n" 266 - it appends `prog.line` if any to `prog.file` over using `opts`: | 267 code = [[ 268 prog = { file = "file", line = 125 }; opts.line = 99]] 269 expect (runscript (code)). 270 to_output_error "file:125: Ayup!\n" 271 - it prefixes `opts.program` if any: | 272 expect (runscript ("")).to_output_error "program: Ayup!\n" 273 - it appends `opts.line` if any, to `opts.program`: | 274 code = [[opts.line = 99]] 275 expect (runscript (code)). 276 to_output_error "program:99: Ayup!\n" 277 278- describe parser:on: 279 - before: | 280 function parseargs (onargstr, arglist) 281 code = [[ 282 package.path = "]] .. package.path .. [[" 283 local OptionParser = require 'std.optparse' 284 local help = [=[]] .. help .. [[]=] 285 help = help:match ("^[%s\n]*(.-)[%s\n]*$") 286 287 local parser = OptionParser (help) 288 289 parser:on (]] .. onargstr .. [[) 290 291 _G.arg, _G.opts = parser:parse (_G.arg) 292 293 o = {} 294 for k, v in pairs (opts) do 295 table.insert (o, k .. " = " .. tostring (v)) 296 end 297 if #o > 0 then 298 table.sort (o) 299 print ("opts = { " .. table.concat (o, ", ") .. " }") 300 end 301 if #arg > 0 then 302 print ("args = { " .. table.concat (arg, ", ") .. " }") 303 end 304 ]] 305 306 return luaproc (code, arglist) 307 end 308 309 - it recognises short options: 310 expect (parseargs ([["x"]], {"-x"})). 311 to_output "opts = { x = true }\n" 312 - it recognises long options: 313 expect (parseargs([["something"]], {"--something"})). 314 to_output "opts = { something = true }\n" 315 - it recognises long options with hyphens: 316 expect (parseargs([["some-thing"]], {"--some-thing"})). 317 to_output "opts = { some_thing = true }\n" 318 - it recognises long options named after Lua keywords: 319 expect (parseargs ([["if"]], {"--if"})). 320 to_output "opts = { if = true }\n" 321 - it recognises combined short and long option specs: 322 expect (parseargs ([[{"x", "if"}]], {"-x"})). 323 to_output "opts = { if = true }\n" 324 expect (parseargs ([[{"x", "if"}]], {"--if"})). 325 to_output "opts = { if = true }\n" 326 - it recognises options with several spellings: 327 expect (parseargs ([[{"x", "blah", "if"}]], {"-x"})). 328 to_output "opts = { if = true }\n" 329 expect (parseargs ([[{"x", "blah", "if"}]], {"--blah"})). 330 to_output "opts = { if = true }\n" 331 expect (parseargs ([[{"x", "blah", "if"}]], {"--if"})). 332 to_output "opts = { if = true }\n" 333 - it recognises end of options marker: 334 expect (parseargs ([["x"]], {"--", "-x"})). 335 to_output "args = { -x }\n" 336 - context given an option with a required argument: 337 - it records an argument to a short option without a space: 338 expect (parseargs ([["x", parser.required]], {"-y", "-xarg", "-b"})). 339 to_contain_output "opts = { b = true, x = arg }" 340 - it records an argument to a short option following a space: 341 expect (parseargs ([["x", parser.required]], {"-y", "-x", "arg", "-b"})). 342 to_contain_output "opts = { b = true, x = arg }\n" 343 - it records an argument to a long option following a space: 344 expect (parseargs ([["this", parser.required]], {"--this", "arg"})). 345 to_output "opts = { this = arg }\n" 346 - it records an argument to a long option following an '=' delimiter: 347 expect (parseargs ([["this", parser.required]], {"--this=arg"})). 348 to_output "opts = { this = arg }\n" 349 - it diagnoses a missing argument: 350 expect (parseargs ([[{"x", "this"}, parser.required]], {"-x"})). 351 to_contain_error "'-x' requires an argument" 352 expect (parseargs ([[{"x", "this"}, parser.required]], {"--this"})). 353 to_contain_error "'--this' requires an argument" 354 - context with a boolean handler function: 355 - it records a truthy argument: 356 for _, optarg in ipairs {"1", "TRUE", "true", "yes", "Yes", "y"} 357 do 358 expect (parseargs ([["x", parser.required, parser.boolean]], 359 {"-x", optarg})). 360 to_output "opts = { x = true }\n" 361 end 362 - it records a falsey argument: 363 for _, optarg in ipairs {"0", "FALSE", "false", "no", "No", "n"} 364 do 365 expect (parseargs ([["x", parser.required, parser.boolean]], 366 {"-x", optarg})). 367 to_output "opts = { x = false }\n" 368 end 369 - context with a file handler function: 370 - it records an existing file: 371 expect (parseargs ([["x", parser.required, parser.file]], 372 {"-x", "/dev/null"})). 373 to_output "opts = { x = /dev/null }\n" 374 - it diagnoses a missing file: | 375 expect (parseargs ([["x", parser.required, parser.file]], 376 {"-x", "/this/file/does/not/exist"})). 377 to_contain_error "error: /this/file/does/not/exist: " 378 - context with a custom handler function: 379 - it calls the handler: 380 expect (parseargs ([["x", parser.required, function (p,o,a) 381 return "custom" 382 end 383 ]], {"-x", "ignored"})). 384 to_output "opts = { x = custom }\n" 385 - it diagnoses a missing argument: 386 expect (parseargs ([["x", parser.required, function (p,o,a) 387 return "custom" 388 end 389 ]], {"-x"})). 390 to_contain_error "option '-x' requires an argument" 391 - context given an option with an optional argument: 392 - it records an argument to a short option without a space: 393 expect (parseargs ([["x", parser.optional]], {"-y", "-xarg", "-b"})). 394 to_contain_output "opts = { b = true, x = arg }" 395 - it records an argument to a short option following a space: 396 expect (parseargs ([["x", parser.optional]], {"-y", "-x", "arg", "-b"})). 397 to_contain_output "opts = { b = true, x = arg }\n" 398 - it records an argument to a long option following a space: 399 expect (parseargs ([["this", parser.optional]], {"--this", "arg"})). 400 to_output "opts = { this = arg }\n" 401 - it records an argument to a long option following an '=' delimiter: 402 expect (parseargs ([["this", parser.optional]], {"--this=arg"})). 403 to_output "opts = { this = arg }\n" 404 - it does't consume the following option: 405 expect (parseargs ([[{"x", "this"}, parser.optional]], {"-x", "-b"})). 406 to_output "opts = { b = true, this = true }\n" 407 expect (parseargs ([[{"x", "this"}, parser.optional]], {"--this", "-b"})). 408 to_output "opts = { b = true, this = true }\n" 409 - context with a boolean handler function: 410 - it records a truthy argument: 411 for _, optarg in ipairs {"1", "TRUE", "true", "yes", "Yes", "y"} 412 do 413 expect (parseargs ([["x", parser.optional, parser.boolean]], 414 {"-x", optarg})). 415 to_output "opts = { x = true }\n" 416 end 417 - it records a falsey argument: 418 for _, optarg in ipairs {"0", "FALSE", "false", "no", "No", "n"} 419 do 420 expect (parseargs ([["x", parser.optional, parser.boolean]], 421 {"-x", optarg})). 422 to_output "opts = { x = false }\n" 423 end 424 - it defaults to a truthy value: 425 expect (parseargs ([["x", parser.optional, parser.boolean]], 426 {"-x", "-b"})). 427 to_output "opts = { b = true, x = true }\n" 428 - context with a file handler function: 429 - it records an existing file: 430 expect (parseargs ([["x", parser.optional, parser.file]], 431 {"-x", "/dev/null"})). 432 to_output "opts = { x = /dev/null }\n" 433 - it diagnoses a missing file: | 434 expect (parseargs ([["x", parser.optional, parser.file]], 435 {"-x", "/this/file/does/not/exist"})). 436 to_contain_error "error: /this/file/does/not/exist: " 437 - context with a custom handler function: 438 - it calls the handler: 439 expect (parseargs ([["x", parser.optional, function (p,o,a) 440 return "custom" 441 end 442 ]], {"-x", "ignored"})). 443 to_output "opts = { x = custom }\n" 444 - it does not consume a following option: 445 expect (parseargs ([["x", parser.optional, function (p,o,a) 446 return a or "default" 447 end 448 ]], {"-x", "-b"})). 449 to_output "opts = { b = true, x = default }\n" 450 - context when splitting combined short options: 451 - it separates non-argument options: 452 expect (parseargs ([["x"]], {"-xb"})). 453 to_output "opts = { b = true, x = true }\n" 454 expect (parseargs ([["x"]], {"-vxb"})). 455 to_output "opts = { b = true, verbose = true, x = true }\n" 456 - it stops separating at a required argument option: 457 expect (parseargs ([[{"x", "this"}, parser.required]], {"-bxbit"})). 458 to_output "opts = { b = true, this = bit }\n" 459 - it stops separating at an optional argument option: 460 expect (parseargs ([[{"x", "this"}, parser.optional]], {"-bxbit"})). 461 to_output "opts = { b = true, this = bit }\n" 462