class Jargon::CLI

Overview

A command-line parser driven by one or more schemas. A CLI is either flat (a single #schema) or in subcommand mode (a map of named #subcommands, each its own Schema or nested CLI). Build one with Jargon.cli/Jargon.new, then #parse arguments into a Result, #json straight to validated data, or #run to also handle --help, completion, and error exits.

Defined in:

jargon/cli.cr
jargon/config.cr
jargon/help.cr

Constructors

Instance Method Summary

Constructor Detail

def self.from_file(path : String, program_name : String = "cli") : CLI #

Create a CLI from a JSON or YAML schema file. Auto-detects multi-doc format for subcommands.


def self.from_json(json : String, program_name : String = "cli") : CLI #

Create a CLI from a JSON schema string. Auto-detects multi-doc format (relaxed JSONL) for subcommands.


def self.from_yaml(yaml : String, program_name : String = "cli") : CLI #

Create a CLI from a YAML schema string. Auto-detects multi-doc format for subcommands.


def self.new(schema : Schema, program_name : String = "cli") #

def self.new(program_name : String) #

Instance Method Detail

def bare_assignment(enabled : Bool) #

Control whether a bare key=value token — no leading dashes — may implicitly assign to a named property. On by default. When off, such a token is never read as an assignment: it falls to positional capture if a slot is open, otherwise it is an unexpected argument. Turn it off for deterministic parsing with zero implicit-assignment magic, e.g. when operands legitimately contain = (name=value pairs passed through to another tool) and you never want one silently absorbed as a property. Note that a positional slot already captures =-bearing operands literally regardless of this setting; the switch only governs the implicit-assignment fallback. It does not affect --flag=value, which is standard long-option syntax and always honored. Applies to this CLI and its directly attached Schema subcommands; a nested CLI keeps its own setting. (A colon is never an assignment separator: key:value is always an ordinary token.)


def bare_assignment? : Bool #

def bash_completion(command : String = @program_name) : String #

Generate a shell completion shim. By default it calls back into this program; pass command: to point completion at a separate (e.g. lighter) binary that answers the hidden __complete verb.


def completer(path : String, &block : Completion::Context -> Array(String)) #

Register a dynamic completer for a field, identified by its dotted path: the field name for a flat CLI, or subcommand.field (a.b.field when nested). The block receives a Completion::Context and returns the candidate strings. Raises if the path does not match a schema field, so typos and renames fail fast.


def completers : Hash(String, Proc(Completion::Context, Array(String))) #

def config_paths(*, project : Bool = false) : Array(String) #

Returns the list of config paths #load_config would search, given the same project: setting.


def default_subcommand(name : String) #

Set the subcommand used when arguments name none.


def default_subcommand : String | Nil #

def fish_completion(command : String = @program_name) : String #

Generate a shell completion shim. By default it calls back into this program; pass command: to point completion at a separate (e.g. lighter) binary that answers the hidden __complete verb.


def handle_completion(args : Array(String) = ARGV, *, io : IO = @output, exit_process : Bool = true) : Bool #

If this invocation is a completion request (the hidden __complete verb), print candidates and — unless exit_process: false — exit 0. Returns true when it handled completion, false otherwise, so it can be called early in a program (before heavy initialization) or as the whole body of a dedicated completion helper binary. The developer never types the verb itself; the generated shim supplies it.


def help(subcommand : String) : String #

Render help for a single subcommand. #subcommand is space-separated for nested commands (e.g. "remote add").


def help : String #

Render the program's help text: a command listing when subcommands are defined, an option listing for a flat CLI, or a bare usage line otherwise.


def json(args : Array(String), input : IO, *, defaults : Defaults | Nil = nil) : JSON::Any #

def json(args : Array(String) = ARGV, *, defaults : Defaults | Nil = nil) : JSON::Any #

Parse and return just the data, raising ParseError on validation errors. Use when you want the values directly and treat invalid input as fatal.


def load_config(*, merge : Bool = true, project : Bool = false) : JSON::Any | Nil #

Load config from standard locations. Supports YAML (.yaml, .yml) and JSON (.json) files.

Paths searched (in order):

  1. ./.config/{program_name}.yaml/.yml/.json (project local, flat; project: true only)
  2. ./.config/{program_name}/config.yaml/.yml/.json (project local, directory; project: true only)
  3. $XDG_CONFIG_HOME/{program_name}.yaml/.yml/.json (user global, flat)
  4. $XDG_CONFIG_HOME/{program_name}/config.yaml/.yml/.json (user global, directory)

Project-local configs are read from the current directory, which may be one someone else controls (a freshly cloned repository), and they would take precedence over the user's own config. So they are off by default; pass project: true to opt in.

With merge: true (default), merges all configs found (project wins over user). With merge: false, returns first config found. Returns nil if no config file found.


def output : IO #

def output=(output : IO) #

def parse(args : Array(String), input : IO, *, defaults : Defaults | Nil = nil) : Result #

:ditto: input supplies the stream read for the - (stdin JSON) form.


def parse(args : Array(String) = ARGV, *, defaults : Defaults | Nil = nil) : Result #

Parse arguments into a Result (parsed data plus any validation errors); never raises on invalid input. defaults seeds values not given on the command line (e.g. from a config file), below CLI args and env vars.


def program_name : String #

def run(args : Array(String), input : IO, *, defaults : Defaults | Nil = nil) : Result #

def run(args : Array(String) = ARGV, *, defaults : Defaults | Nil = nil) : Result #

Run the CLI with automatic --help, --completions, and error handling. Prints help/completions and exits 0, prints errors and exits 1, otherwise returns/yields result.


def run(args : Array(String), input : IO, *, defaults : Defaults | Nil = nil, &) : Nil #

def run(args : Array(String) = ARGV, *, defaults : Defaults | Nil = nil, &) : Nil #

def schema : Schema | Nil #

def subcommand(name : String, schema : Schema) #

def subcommand(name : String, cli : CLI) #

def subcommand(name : String, *, json : String) #

def subcommand(name : String, *, yaml : String) #

def subcommand(name : String, *, file : String) #

Load subcommand(s) from a file.

  • Single-doc file: requires name, loads as single subcommand
  • Multi-doc file without name: loads each doc as top-level subcommand
  • Multi-doc file with name: loads docs as nested subcommands under name

def subcommand(*, file : String) #

Load subcommands from a multi-document file (no parent name). Each document must have a "name" field.


def subcommand_key(key : String) #

Set the JSON key that names the subcommand when parsing full JSON from stdin (the - form). Defaults to "subcommand".


def subcommand_key : String #

def subcommands : Hash(String, Schema | CLI) #

def validate(data : Hash(String, JSON::Any), subcommand : String | Nil = nil) : Array(String) #

Validate data against a schema, returning any errors. If no schema is provided, uses the CLI's root schema. For subcommand validation, pass the subcommand name (space-separated for nested).


def validate(result : Result) : Array(String) #

:ditto: Validate a previously parsed Result against its subcommand.


def zsh_completion(command : String = @program_name) : String #

Generate a shell completion shim. By default it calls back into this program; pass command: to point completion at a separate (e.g. lighter) binary that answers the hidden __complete verb.