This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
MKlegoman357's profile picture

Argser - parsing program arguments with ease

Started by MKlegoman357, 20 June 2016 - 01:39 PM
MKlegoman357 #1
Posted 20 June 2016 - 03:39 PM
Argser

pastebin get 3jkDVune argser


This is an API I wrote to help with parsing program arguments. It's made to process such things like switches (myprogram -a -b valueforb anothervalueforb) and named arguments (myprogram –named1=valuefor1 –named2), as well as simple "normal" arguments (myprogram arg1 arg2 arg3). It can also automatically convert the passed arguments to certain Lua types and preprocess them using custom functions.

Loading the API

To load the API you can use dofile() function (os.loadAPI() will not work):

local argser = dofile("argser")

Documentation

The API returns a single function to which you pass the program's arguments, setup all the switches, named arguments, value types, defaults and aliases, and parse the arguments. The parsed arguments are then returned in a table which you can then use to get the arguments.

ExampleYou can see an example of all the features and usage of the API:

local args = dofile("argser")(...)
  :num(1, "arg1")
  :num(2, "arg2", "number"):default(1)
  :switch("files", true):alias("f"):func(shell.resolve):default({})
  :named("argser", true):func(shell.resolve)
  :named("print", true, "boolean"):alias("p"):default(false)
  :alias("argser", "a")
  :parse().args

for i, arg in ipairs(args) do
  print(i .. ". " .. arg)
end

if args.print then
  print()

  for name, arg in pairs(args) do
	print(name .. ": " .. textutils.serialize(arg))
  end
end

If this program was saved in the root directory of the computer as "test", running it like so:

test a --argser=/argser -f argser rom --print=t 2

…would print this onto the screen:

1. a
2. 2

1: "a"
2: 2
files: {
  "argser",
  "rom",
}
print: true
argser: "argser"
arg1: "a"
arg2: 2

Parser methods and variablesAfter loading the API you can call the function, passing all the program arguments to it. It then returns a parser with which you setup all the options. The parser exposes a few functions and variables. All the functions must be called using the colon notation (eg.: parser:num(…)) and they return the parser itself for method chaining.
I will be appending all example code to this:


local argser = dofile("argser")
local parser = argser(...)

:num(argumentNumber:number, [name:string], [valueType:string])
This function is meant for the "normal" numbered arguments. You can give them a name or a default value that way.
  • argumentNumber - the number of the argument (1 for the first argument, 2 for the second, etc..).
  • name - the name for the argument.
  • valueType - the type of the argument's value. Valid types are: string, number, boolean and table.
Example:

parser:num(1, "arg1") --# the first argument
parser:num(3, "arg3", "boolean") --# the third argument

:switch(name:string, [values:boolean|number], [valueType:string])
This function is used to define a switch: give it a name and whether it accepts values or how many values it accepts.
  • name - the name for the switch.
  • values - if passed a boolean: true for infinite amount of values, false for no values. If passed a number then that number specifies the maximum amount of values that the switch can accept.
  • valueType - the type of the argument's value. Valid types are: string, number, boolean and table.
Example:

parser:switch("switch1") --# creates a switch which doesn't accept any value
parser:switch("switch2", true, "number") --# creates a switch which accepts any amount of numbers
parser:switch("switch3", 3) --# creates a switch which accepts maximum of 3 values

:named(name:string, [hasValue:boolean], [valueType:string])
This function is used to define a named argument, which can accept a single value.
  • name - the name for the named argument.
  • hasValue - specifies whether the named argument accepts a value.
  • valueType - the type of the argument's value. Valid types are: string, number, boolean and table.
Example:

parser:named("named1") --# creates a named argument which doesn't accept any value
parser:named("named2", true, "boolean") --# creates a named argument which accepts a boolean

:alias([name:string ,] alias:string)
This function is used to give an alias for any argument type: numbered (normal), switch or named.
  • name - the name of the argument.
  • alias - the alias for the argument.
Example:

parser:alias("named1", "n1") --# specifies an alias for the named argument "named1"
parser:switch("switch2"):alias("s2") --# specifies an alias for the switch "switch2"

:default([name:string ,] default:any)
This function is used to give an argument a default value.
  • name - the name of the argument.
  • default - the default value of the argument.
Example:

parser:default("arg1", "argument") --# gives a default value for the argument "arg1"
parser:named("named1"):default(1) --# specifies a default value for the named argument "named1"

:func([name:string ,] fn:function)
This function attaches a "processing" function which is used to process the argument. It is passed a single argument and it's return value is used as the value for the argument.
  • name - the name of the argument.
  • fn - the "processing" function.
Example:

parser:func("arg1", function (arg)
  return "!" .. arg .. "!"
end) --# modifies the values of argument "arg1"
parser:switch("files", true):func(shell.resolve) --# shell.resolve's every argument of the switch "files"

:parse()
Parses the arguments. The parsed arguments are stored in the parser.arg table. You can access the numbered (normal) arguments by using the table as an array (eg.: parser.arg[1] for first argument) or if the argument was specified using :num() and given a name, you can access it using it's name (eg.: parser.arg.arg1 for a numbered argument named "arg1"). You can access the switche's arguments using it's name (eg.: parser.arg.switch1 for the values of "switch1"). All of the switches arguments are put into an array. If the switch doesn't accept any values and it was specified as an argument it will be set to true. You can access all named arguments using their name (eg.: parser.arg.named1 for the value of "named1"). If the named argument doesn't accept any values and it was passed as an argument it's value will be set to true.

Example:

parser:num(1, "arg1")
parser:switch("switch1", true)
parser:named("named1", true, "number"):default(1)
parser:parse()
local args = parser.args

Changelog
  • 2016-06-21(2) - the API now contains a single function which you can copy-paste into your own programs.
  • 2016-06-21(1) - fixed a bug with undefined named argument parsing.
  • 2016-06-20 - initial public release.

Licensing
You can use and edit this API as much as you want. I only ask that you don't start redistributing it as if it was your own API.
Edited on 21 June 2016 - 06:57 PM
Emma #2
Posted 21 June 2016 - 01:29 AM
Nice! I'm always too lazy to implement actual terminal arguments like this. :P/>
MKlegoman357 #3
Posted 21 June 2016 - 12:41 PM
Nice! I'm always too lazy to implement actual terminal arguments like this. :P/>

That's the reason I made this. Got tired of writing the same argument handling logic in every program.




I updated the API. There was a bug where if the API was given an undefined named argument it would crash. And yes, it will parse any undefined arguments. By default switches can have no values and named arguments can optionally have a string value. Running this program:


print(textutils.serialize(
  dofile("argser")(...):parse().args
))

…using these arguments:


1 bar -switch --named1 --named2=foo 3

…would print this onto the screen:


{
  "1",
  "bar",
  "3",
  switch = true,
  named1 = true,
  named2 = "foo",
}
CrazedProgrammer #4
Posted 21 June 2016 - 06:25 PM
Very nice! :D/>
My suggestion would be that you make some small code changes so people can copy+paste this into their program (I know this is very simple, but it's nice :P/>), so they don't have to have an external file.
MKlegoman357 #5
Posted 21 June 2016 - 08:34 PM
Very nice! :D/>
My suggestion would be that you make some small code changes so people can copy+paste this into their program (I know this is very simple, but it's nice :P/>), so they don't have to have an external file.

I was actually thinking of doing this, but was too lazy to actually do something myself. You know, literally change two lines in this API :P/>. But I guess that's what makes a good programmer - being lazy :D/>.




Anyway, I updated the code so now it only contains a single function which you can copy-paste into your own program. The function is called "argser()" by the way and it does exactly the same as if you were to load the API like this:


local argser = dofile("argser")
CrazedProgrammer #6
Posted 21 June 2016 - 10:48 PM
Nice!
Thanks for implementing my suggestion!