Since Julia now allows us to install an app/command to call it from the Shell by the @main function, I wonder how to mimic cases like some commands having subcommands in @main? Like git add, git status, git clone for git. That is, those subcommands should be gathered under a global name.
A possible solution I can think of is the check the second argument of ARGS, e.g., if it is a specific subcommand. I am not sure this is the correct way.
I was using Comonicon.jl, which allows subcommands as long as they are defined in submodules of a package.
1 Like
I am not sure if I understand what you mean with subcommands but DocOpt.jl implements the infamous docopt concept (awesome talk by Vladimir Keleshev btw. https://youtu.be/pXhcPJK5cMc) and you can define a command line interface (CLI) pretty naturally: GitHub - docopt/DocOpt.jl: command line arguments parser
One of the first slides shows this CLI
and I assume the “subcommand” you are referring to would be something like tcp or serial in that example.
You would then need to parse the arguments and call the appropriate function.
If you however mean something like dedicated functions for different “subcommands”, like e.g. what Click for Python implements, I think there is nothing comparable (yet) in the Julia ecosystem. Note that Click provides type checking and direct mapping of functions to the CLI. Might be an overkill in some cases 
The subcommand should be the first argument not the second argument, since the main script name does not appear at the beginning of ARGS.
Then you can ask the function corresponding to the subcommand to parse ARGS[2:end]. For example, ArgParse.jl can parse a user-supplied argument list.
Thanks. I mean something like docopt, but this was already implemented in Comonicon.jl. I was wondering if I can do that directly with ARGS.
No, ARGS is just a lightweight dictionary 
Of course, the package you mentioned is essentially a nice package for parsing ARGS. You could cook up your own version if you want.
Maybe this example can help you to start with:
module SimpleCalcProject
using TOML
const version = TOML.parsefile("Project.toml")["version"]
function print_help()
println(Core.stdout,
"""
SimpleCalcProject CLI
Usage:
simplecalc <command> [args...]
Commands:
add <a> <b> Add two numbers
sub <a> <b> Subtract second number from first
sum <nums...> Sum all numbers (sum)
Options:
-h, --help Show this help message
-v, --version Show version information
""")
end
function print_version()
println(Core.stdout, "SimpleCalcProject CLI v$(version)")
end
function parse_numbers(remaining)
try
tryparse.(Float64, remaining)::Vector{Float64}
catch
println(Core.stdout, "Error: invalid argument(s)")
Float64[]
end
end
function print_calculation(cmd, numbers)
if cmd == "add" && length(numbers) == 2
println(Core.stdout, "Result: $(numbers[1] + numbers[2])")
elseif cmd == "sub" && length(numbers) == 2
println(Core.stdout, "Result: $(numbers[1] - numbers[2])")
elseif cmd == "sum" && !isempty(numbers)
println(Core.stdout, "Result: $(sum(numbers))")
else
println(Core.stdout, "Invalid command or arguments. Try --help.")
end
end
function cli(args)
if isempty(args)
print_help()
return 0
end
cmd = args[1]
if cmd in ["--help", "-h"]
print_help()
return 0
elseif cmd in ["--version", "-v"]
print_version()
return 0
end
remaining = args[2:end]
numbers = parse_numbers(remaining)
length(numbers) == 0 && return 1
print_calculation(cmd, numbers)
return 0
end
function @main(ARGS)
return cli(ARGS)
end
end