What are the current (late 2024) best practices for CLI development?

Say someone is developing a package, and wants to include some command line interface. What are current best practices / ways to do this? My motivation here is twofold - I am actively working on such a project, and also would like to contribute to Modern Julia Workflows (cf. this issue).

Ideally, this guidance would be

  • Currently supported - that is something can can be used with currently released julia features (eg not relying on juliac / Pkg support)
  • Generic - that is not reliant on specific frameworks (eg Comonicon.jl) or argument parsing libraries (eg ArgParse.jl, Docopt.jl)
  • Have a clear upgrade path for when juliac / App support does arrive.

Some things to consider:

  • Code organization - CLI as a module included within package? Separate package? bin/ directory inside package?
  • Installation - How to make cli calls available to users / accessible via PATH, etc?
  • Precompilation / sysimages - how to reduce the latency for users?
  • Upgrade paths - assuming a user already has an installation, how to replace it / make sure the latest version is installed?

As a provocation to get things going, lets say we want to add a CLI for Example.jl. One way to do this (this is how I’m doing it in my current project) - we can create Example.jl/bin/src/ExampleCLI.jl that contains

module ExampleCLI

export main

using Example

function (@main)(ARGS)
    if first(ARGS) == "hello"
        println(hello(last(ARGS)))
    elseif first(ARGS) == "domath"
        num = parse(Int, last(ARGS))
        println(domath(num))
    else
        throw(ArgumentError("Command $(first(ARGS)) not supported"))
    end
    return 0
end

end

Then create bin/Project.toml with:

name = "ExampleCLI"
uuid = "9a0c4f26-9756-4254-a226-e60a319fd9cd"

[deps]
Example = "7876af07-990d-54b4-ab0e-23690620f79a"

[compat]
Example = "0.5.5"

And run

❯ julia --project=bin -e 'using Pkg; Pkg.instantiate()'

❯ julia --project=bin -e 'using ExampleCLI' domath 32
37

❯ julia --project=bin -e 'using ExampleCLI' hello world
Hello, world

So then, how would we precompile this or make a system image for it? How would we ask users to install it? How would we want to actually run it? Would you organize this differently?

Some related topics:

3 Likes

See also the follow-up: [ANN] YAArguParser and GivEmXL

1 Like