Argparse.Program
A convenience wrapper that turns a Argparse.Parser.App into a ready-to-run Node program, handling the boilerplate most command-line tools want:
- parse errors (unknown command, bad flags, bad arguments) are printed to
stderr and the program exits with code
1 --help,--version, and the bare-invocation help screen are printed to stdout (exit0)- a successfully parsed command is handed to your
onCommandfunction
Your handler never touches exit codes. It returns a Task Failure {}, and
this module turns that into the process exit status for you:
- the task succeeds with
{}→ exit0 - the task fails with ExitFailure → exit
1, printing nothing extra (you've already written whatever report you wanted) - the task fails with ExitMessage → that string is printed to stderr
and the program exits
1 - the task fails with ExitValue → exits with the given code, printing nothing extra
- the task fails with ExitMessageValue → that string is printed to stderr and the program exits with the given code
So Task.succeed {} is the only success path — exit 0. Task.fail is how
you signal any non-zero exit, with the Failure constructor choosing
whether to print a message and which code to use.
This is opinionated on purpose. Programs that need a full model/update loop
should skip this module entirely: call
Argparse.Parser.run inside your own Node.defineSimpleProgram,
match on each CommandParseResult constructor by hand,
and call Node.setExitCode yourself wherever you need it. See examples/manual/
for a complete working example that exits 2 on usage errors.
If all you need is access to subsystem permissions (filesystem, terminal, child processes, …), you can use runWithContext, which lets you run your own initialization before the command handler while keeping all of the boilerplate above.
How a command handler signals failure. A handler returns Task Failure {};
Task.succeed {} exits 0, and Task.fail with one of these constructors
decides the non-zero behavior.
ExitFailure— exit1, print nothing (you've already written your report)ExitMessage msg— printmsgto stderr, then exit1ExitValue n— exitn, print nothingExitMessageValue { message, value }— printmessageto stderr, then exitvalue
Here is an example of using ExitFalure after having already printed the error
message to stderr.
check : Node.Environment -> Cmd -> Task Argparse.Program.Failure {}
check env cmd =
when cmd is
Lint { path } ->
lint path
|> Task.andThen
(\problems ->
if Array.length problems == 0 then
Stream.Log.line env.stdout "All good."
else
reportProblems env.stderr problems
|> Task.andThen (\_ -> Task.fail Argparse.Program.ExitFailure)
)
Build a Node program from a parser and a command handler.
main : Node.SimpleProgram a
main =
Argparse.Program.run
{ parser = MyArgparse.parser
, onCommand =
\env command ->
when command is
MyArgparse.Greet { name } ->
Stream.Log.line env.stdout ("Hello, " ++ name)
}
onCommand receives the Node.Environment (stdout, stderr,
args, …) and your parsed command, and returns a Task Failure {}. Succeed with
{} to exit 0; fail the task with a Failure constructor to
choose a non-zero exit and whether to print a message — the same treatment a
parse error gets.
Like run, but with a chance to initialize subsystems and acquire
permissions before any command runs. Use this when your command handler needs a
FileSystem.Permission, a Terminal configuration, child-process access, and
so on — these can only be obtained during a program's initialization phase,
which run doesn't expose.
init is given the Node.Environment and a continuation
(toProgram). Acquire whatever you need with Init.await, then call
toProgram with a context value of your choosing; that same value is later
handed to every onCommand invocation.
import FileSystem
import Init
type alias Context =
{ fs : FileSystem.Permission }
main : Node.SimpleProgram a
main =
Argparse.Program.runWithContext
{ parser = MyArgparse.parser
, init =
\_env toProgram ->
Init.await FileSystem.initialize <| \fsPermission ->
toProgram { fs = fsPermission }
, onCommand =
\env context command ->
when command is
MyArgparse.Build { path } ->
FileSystem.readFile context.fs path
|> Task.mapError FileSystem.errorToString
|> Task.andThen (\contents -> Stream.Log.line env.stdout contents)
|> Task.map (\_ -> Argparse.Program.Succeeded)
}
Parse errors, --help/--version, and the Failure→exit-code
mapping are handled exactly as in run. The context is acquired up
front.
Build a Node program from a single command, with no sub-command word —
for tools that are just flags and arguments, like mytool --loud World. This
is the Argparse.Parser.runCommand counterpart to
run, and handles the same boilerplate (parse errors → stderr + exit 1,
--help/--version → stdout, success → your handler).
main : Node.SimpleProgram a
main =
Argparse.Program.runRoot
{ name = "greet"
, version = "1.0.0"
, command = MyArgparse.greetCommand
, onCommand =
\env (MyArgparse.Greet { name, loud }) ->
Stream.Log.line env.stdout (greeting name loud)
}
name and version drive the --help usage line and --version output. The
command's own word is ignored (the help line reads name <args>), so you can
leave it empty. The Failure→exit-code mapping works as in
run.
If the command needs subsystem permissions, use runRootWithContext — this is just that with an empty context, the same way run relates to runWithContext.
Like runRoot, but with a chance to initialize subsystems and
acquire permissions before the command runs — the rootless counterpart to
runWithContext. Use it for a no-sub-command tool
(mytool --loud World) whose handler still needs a FileSystem.Permission, a
Terminal configuration, child-process access, and so on.
init is given the Node.Environment and a continuation
(toProgram). Acquire whatever you need with Init.await, then call
toProgram with a context value of your choosing; that same value is later
handed to onCommand.
import FileSystem
import Init
type alias Context =
{ fs : FileSystem.Permission }
main : Node.SimpleProgram a
main =
Argparse.Program.runRootWithContext
{ name = "wc"
, version = "1.0.0"
, command = MyArgparse.countCommand
, init =
\_env toProgram ->
Init.await FileSystem.initialize <| \fsPermission ->
toProgram { fs = fsPermission }
, onCommand =
\env context (MyArgparse.Count { path }) ->
FileSystem.readFile context.fs path
|> Task.mapError (\e -> ExitMessage (FileSystem.errorToString e))
|> Task.andThen (\contents -> Stream.Log.line env.stdout contents)
}
Parse errors, --help/--version, and the Failure→exit-code
mapping are handled exactly as in runRoot. The context is acquired
up front, so it is available even on the help/error paths; that's harmless, since
acquiring a permission performs no I/O.