Comonicon

gith averminaluk ayh juldas mausan urdan

Build Status

Roger's magic book for command line interfaces.

Quick Start

The simplest and most common way to use Comonicon is to use @cast and @main. Let's use a simple example to show how, the following example creates a command using @cast.

using Comonicon
@main function mycmd(arg; option="Sam", flag::Bool=false)
    @show arg
    @show option
    @show flag
end

if you write this into a script file myscript.jl and execute it using

julia myscript.jl -h

You will see the following in your terminal.

  main

No documentation found.
mycmd is a Function.
# 1 method for generic function "mycmd":
[1] mycmd(arg; option, flag) in Main at /Users/roger/.julia/dev/Comonicon/example/myscript.jl:2

Usage

  main [options...] [flags...] <arg>

Args

  <arg>

Flags

  -f,--flag

  -h, --help          print this help message

  -V, --version       print version information

Options

  --option <::Any>

Now let me explain what @main does here. In short it does the following things:

  • parse your expression and create a command line object
  • use this command line object to create an entry
  • generate a Julia script to actually execute the command
  • cache the generated Julia script into a file so it won't need to recompile your code again

Convention

leaf command: leaf commands are the commands at the last of the CLI that takes arguments, options and flags, e.g the show command below

node command: node commands are the commands at the middle or first of the CLI that contains sub-commands, e.g the remote command below

git remote show origin

arguments: arguments are command line arguments that required at the leaf command

flags: flags are command line options that has no arguments, e.g --flag or -f (short flag).

options: options are command line options that has arguments, e.g --name Sam or -n Sam, also --name=Sam or -nSam.

When used on function expressions, @cast and @main have the same convention on how they convert your expressions to commands, these are

  • function arguments are parsed as command arguments:
    • value will be converted automatically if arguments has type annotation
    • optional arguments are allowed
  • function keyword arguments are parsed as command flags or options:
    • keyword arguments must have default value
    • keyword arguments of type Bool can only have false as default value, which will be treated as flags that allow short flags.
    • value will be converted automatically if keyword arguments has type annotation
  • function doc string can use section names: Arguments, Options and Flags to annotate your CLI:
    • short options or short flags can be declared via -f, flag or -o, --option <name> (see example below)

An example of function docstring

"""
my command line interface.

# Arguments

- `arg`: an argument

# Options

- `-o, --option`: an option that has short option.

# Flags

- `-f, --flag`: a flag that has short flag.
"""
@main function mycmd(arg; option="Sam", flag::Bool=false)
    @show arg
    @show option
    @show flag
end

This will give a help message looks like below after execute this in myscript.jl via julia myscript.jl


  main

my command line interface.

Usage

  main [options...] [flags...] <arg>

Args

  <arg>                  an argument

Flags

  -f,--flag              a flag that has short flag.

  -h, --help             print this help message

  -V, --version          print version information

Options

  -o, --option <name>

Create a CLI project

However, to build better and faster CLI, you will want to build your CLI in a Julia package and deliver it to your users. This can be done via @cast.

@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 NodeCommands 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 Dummy

using Comonicon

@cast mycmd1(arg; option="Sam") = println("cmd1: arg=", arg, "option=", option)
@cast mycmd2(arg; option="Sam") = println("cmd2: arg=", arg, "option=", option)

module Cmd3

using Comonicon

@cast mycmd4(arg) = println("cmd4: arg=", arg)

end # module

@main name="dummy" version=v"0.1.0"

end # module

You can find all created commands via following

julia> Dummy.CASTED_COMMANDS
Dict{String,Any} with 3 entries:
  "mycmd2" => mycmd2 [options...] <arg>
  "main"   => dummy v0.1.0
  "mycmd1" => mycmd1 [options...] <arg>

and you can execute the command via Dummy.command_main created by @main:

julia> Dummy.command_main(["-h"])

  dummy v0.1.0



Usage

  dummy <command>

Commands

  mycmd2 [options...] <arg>    No documentation found. Main.Dummy.mycmd2 is a
                               # 1 method for generic function "mycmd2": [1]
                               option) in Main.Dummy at REPL[2]:6

  mycmd1 [options...] <arg>    No documentation found. Main.Dummy.mycmd1 is a
                               # 1 method for generic function "mycmd1": [1]
                               option) in Main.Dummy at REPL[2]:5

Flags

  -h, --help                   print this help message

  -V, --version                print version information


0

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 two line:

# build.jl
using Comonicon, Dummy
Comonicon.install(Dummy, "dummy")

You can turn on the keyword sysimg to true to compile a system image for the CLI.

# build.jl
using Comonicon, Dummy
Comonicon.install(Dummy, "dummy"; sysimg=true)

There is an example project IonCLI.jl you can take as a reference.