Comonicon

repository·main·Indexed 43 days ago

https://github.com/comonicon/comonicon.jl

A Julia package for creating command line interfaces (CLIs) with minimal boilerplate. It uses macros like `@main` and `@cast` to automatically generate CLI arguments, options, flags, and help messages by parsing function signatures and docstrings.

Tokens
5.4K
Snippets
20
Records
29
Agent score
55%

What's inside Comonicon

  1. Use the dash separator `--` to disambiguate CLI arguments

    main

    The dash separator -- is used to signal the end of command options for the primary CLI program. This is useful when your command executes another script or program that also accepts command-line arguments, preventing the primary program from trying to parse the sub-program's arguments.

    Example: If you have a command run that executes a Julia script:

    run --flag -- script.jl a b c

    In this case, --flag is an argument for run, and a b c are arguments passed to script.jl.

  2. Customize command behavior using Comonicon.AST

    main
    While the high-level @main and @cast macros provide a simplified interface for defining commands, you can achieve full customization by creating command objects directly using the interfaces provided in the Comonicon.AST module. This allows you to bypass the restrictions of the frontend macros and define complex command structures manually.
  3. How @main and @cast work together

    main

    Comonicon provides two primary macros for building interfaces:

    1. @main: Used for simple, single-command CLIs. It turns a function into a command line entry point.

    2. @cast: Used to create more complicated command line interfaces, such as those with multiple subcommands. You can use @cast to annotate a function or a module to build out these complex structures.

    Best Practice: For optimal performance, it is recommended to use @main within a Julia project module so that the CLI entry point is compiled by the Julia compiler.

  4. Choosing between a CLI script and a Comonicon project

    main

    Decide which approach to use based on your project scale:

    CLI Script

    Best for simple and small cases. You write a single .jl file and run it via the Julia interpreter.

    Comonicon Project

    Recommended for larger projects or serious usage. Creating a dedicated Comonicon project provides:

    • Much faster startup times.
    • Automatic CLI installation.
    • Easier distribution via Julia packages.
    • Ability to build distributable system images or standalone applications using PackageCompiler.jl in CI.
  5. How @main and @cast work in Comonicon

    main

    Comonicon uses the @main and @cast macros to define the CLI interface of a Julia module. When you apply the @main macro to a module, Comonicon automatically generates several specialized functions and a registry to facilitate CLI execution, installation, and standalone application building:

    1. command_main: The primary entry point for your CLI command.
    2. comonicon_install: Used for building and installing the command.
    3. comonicon_install_path: Used for building and installing the command path.
    4. julia_main: Used for building standalone applications.

    Additionally, a registry constant CASTED_COMMANDS is added to the module. You can access the original Comonicon Abstract Syntax Tree (AST) for your command by indexing this constant with your command's name.

  6. How @main works

    main

    The @main macro automates the boilerplate required for a CLI by performing the following steps:

    1. Parses your function expression to create a command line object.
    2. Uses that object to create an entry point following Comonicon conventions.
    3. Generates a Julia script to execute the command.
    4. Caches the generated script to avoid recompilation overhead on subsequent runs.
  7. Define multiple commands using `@cast`

    main

    In large projects, use the @cast macro to define multiple commands. Unlike @main, @cast does not execute the command immediately; instead, it registers the command into a global variable CASTED_COMMANDS within the current module.

    Key behaviors:

    • NodeCommands: @cast can be used before modules to create NodeCommands. Sub-commands of these nodes can then be defined using @cast inside those modules.
    • Registration: All commands created via @cast are stored in ModuleName.CASTED_COMMANDS as a Dict{String, Any}.
    • Entry Point: After defining sub-commands with @cast, use @main at the bottom of your module to declare the primary entry point for the CLI.
    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
  8. How Comonicon maps Julia functions to CLI commands

    main

    When using @cast or @main on function expressions, Comonicon automatically converts your Julia code into a CLI structure based on the following rules:

    • Arguments: Function arguments are parsed as command arguments. If type annotations are provided, values will be automatically converted. Optional arguments are supported.
    • Flags and Options: Function keyword arguments are parsed as command flags or options.
      • Keyword arguments must have a default value.
      • Keyword arguments of type Bool with a false default are treated as flags (which also support short flags).
      • Values are automatically converted if type annotations are present.
    • Naming Convention: To ensure shell compatibility, underscores _ in variable names are automatically replaced with dashes -. For example, a keyword argument dash_dash becomes the --dash-dash option in the terminal. You should use dashes in your docstring to match this behavior.
    # Example of how keyword arguments map to options/flags
    @main function my_command(arg1::Int; name::String="default", verbose::Bool=false)
        # arg1 -> positional argument
        # name -> --name <value> option
        # verbose -> --verbose flag
    end
  9. Run Comonicon tools as scripts with cache control

    main

    If you are running your CLI tool as a script rather than a compiled binary, you can manage the performance of the compilation process using cache control functions. Use Comonicon.disable_cache() to prevent caching or Comonicon.enable_cache() to enable it.

    using Comonicon
    
    Comonicon.enable_cache()
    # ... your CLI logic ...
  10. Setup `build.jl` for CLI installation

    main

    To allow users to install your CLI to ~/.julia/bin automatically when they install your package, create a build.jl file in your package's deps folder. This file should call comonicon_install() from your package module.

    # deps/build.jl
    using Demo; Demo.comonicon_install()

    You can inspect the installation help by running:

    julia --project deps/build.jl -h
    # build.jl
    using Demo; Demo.comonicon_install()
  11. Build and install CLI tools to ~/.julia/bin

    main

    To distribute your CLI as a standalone package installed to ~/.julia/bin, use the Comonicon.install function. You can tune the build process using the following parameters:

    • compile=:min: Use this if you want a faster build process and do not prioritize the execution speed of the resulting tool.
    • sysimg=true: Use this if you want to optimize for both fast startup time and high execution speed (this typically involves more intensive compilation).
    # Example: Installing with optimized startup and execution speed
    Comonicon.install(compile=:min, sysimg=true)
  12. Make a Comonicon script executable in the shell

    main

    To run your Comonicon script directly from the terminal without typing julia every time, follow these steps:

    1. Create a file without a .jl extension (e.g., mycmd).
    2. Add a shebang line at the very top pointing to your Julia executable: #!<path to your julia executable>
    3. Include your Comonicon code in the file.
    4. Grant execution permissions using chmod:
      chmod +x mycmd
    5. (Optional) To run the command from anywhere, move the file to .julia/bin and ensure .julia/bin is in your PATH:
      export PATH="$HOME/.julia/bin:$PATH"
    #!<path to your julia executable>
    using Comonicon
    
    """
    my first Comonicon CLI.
    """
    @main function mycmd(arg; option="Sam", flag::Bool=false)
        @show arg
        @show option
        @show flag
    end