CLI Project
To build better, faster and more complicated CLI, you will want to build your CLI in a Julia package and deliver it to your users. I will introduce how to create a Comonicon CLI project in this chapter.
Create a Julia Comonicon project
A Comonicon CLI project is first a Julia project, thus you should first create a Julia project. If you don't know what is a Julia project, please read about the Pkg section of creating packages. The minimal requirement of a Julia Comonicon project is the following structure
Demo
├── LICENSE
├── Manifest.toml
├── Project.toml
├── README.md
├── src
│ └── Demo.jl
└── test
└── runtests.jl
usually it's more convenient to create a Julia project via IonCLI, after you install IonCLI, you can type the following command in your terminal
ion create Demo --template=comonicon
Use @cast
to define multiple commands
In a large project, one might need to define multiple commands. This can be done via @cast
.
Comonicon.@cast
— Macro@cast <function definition>
@cast <module definition>
Denote a Julia expression is a command. If the expression is a function definition, it will be parsed as a leaf command, if the expression is a module definition or module name, it will be parsed as a node command. This macro must be used with @main
to create a multi-command CLI.
Quick Example
# in a script or module
"""
sum two numbers.
# Args
- `x`: first number
- `y`: second number
# Options
- `-p, --precision=<type>`: precision of the calculation.
# Flags
- `-f, --fastmath`: enable fastmath.
"""
@cast function sum(x, y; precision::String="float32", fastmath::Bool=false)
# implementation
return
end
"product two numbers"
@cast function prod(x, y)
return
end
@main
@cast
is similar to @main
before functions, but it won't execute anything, but only create the command and register the command to a global variable CASTED_COMMANDS
in the current module. And it will create NodeCommand
s before modules, and the sub-commands of the NodeCommand
can be created via @cast
inside the module.
After you create the commands via @cast
, you can declare an entry at the bottom of your module via @main
. A simple example looks like the following
module Demo
using Comonicon
@cast mycmd1(arg; option="Sam") = println("cmd1: arg=", arg, "option=", option)
@cast mycmd2(arg; option="Sam") = println("cmd2: arg=", arg, "option=", option)
"""
a module
"""
module Cmd3
using Comonicon
@cast mycmd4(arg) = println("cmd4: arg=", arg)
end # module
@cast Cmd3
"""
my demo Comonicon CLI project.
"""
@main
end # module
You can find all created commands via following
julia> Demo.CASTED_COMMANDS
Dict{String,Any} with 4 entries:
"mycmd2" => mycmd2 [options] <arg>
"cmd3" => cmd3 <command>
"main" => demo v0.1.0
"mycmd1" => mycmd1 [options] <arg>
and you can execute the command via Demo.command_main
created by @main
:
Setup the build.jl
Then you can create a build.jl
file in your package deps
folder to install this command to ~/.julia/bin
when your user install your package. This will only need one line:
# build.jl
using Demo; Demo.comonicon_install()
To learn about how to use it, you can type
julia --project deps/build.jl -h
which will print the following help message:
Install the CLI
You can now install the CLI by building the package either in REPL via ]build
or use IonCLI in terminal via
ion build # in Demo folder
This will install this command to ~/.julia/bin
directory by default, if you have put this directory in your PATH
then you will be able to use the command demo
directory in your terminal, e.g
demo -h
Enable System Image
Some CLI projects are quite complicated thus the startup latency is still quite huge even the package module is precompiled. In this case, one will want to use a system image to reduce the startup latency.
You can enable to the system image build by specifying [sysimg]
field in your Comonicon configuration file Comonicon.toml
(or JuliaComonicon.toml
).
name = "demo"
[install]
completion = true
quiet = false
optimize = 2
[sysimg]
You can also specify more detailed system image compilation options, e.g
[sysimg]
incremental=false
filter_stdlibs=true
You can find more references for these options in PackageCompiler#create_sysimage.
However, you may still find it being slow, you can further reduce the latency by adding an execution file to record precompilation statements.
[sysimg.precompile]
execution_file = ["deps/precompile.jl"]
or you can manually specify these precompile statements via
[sysimg.precompile]
statements_file = ["deps/statements.jl"]
you can learn more about how to create precompilation statements via SnoopCompile and create a userimg.jl
as the precompilation statements.
Enable Application Build
You can build a standalone application similar to building a system image as well, e.g
[application]
incremental=true
filter_stdlibs=false
[application.precompile]
statements_file = ["deps/statements.jl"]
Further Reference
The CLI we just used to create this project serves as the best practice for Comonicon, you can take it as a reference: Ion.jl.