Files
cligen/CLAUDE.md
T
2026-08-09 15:20:06 -05:00

4.5 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

# Run all specs
crystal spec

# Run a single spec file
crystal spec spec/command_spec.cr

# Type-check without running
crystal build src/cligen.cr --no-codegen

# Install dependencies
shards install

What This Is

cligen is a Crystal shard (library) — not a standalone application. It wraps Crystal's built-in OptionParser with an annotation- and macro-driven system that auto-generates CLI parsers from class definitions. Users of the shard subclass CliGen::Command and annotate methods; the library generates the OptionParser wiring at compile time via Crystal macros.

Architecture

Entry point: src/cligen.cr

Defines the CliGen module. The macro finished hook calls define_root_parser, which scans all CliGen::Command subclasses at compile time and registers each as a subcommand on the root OptionParser. CliGen.parse runs the root parser.

ADDITIONAL_DEFAULT_FLAGS / add_default_flag let users inject extra flags into every parser (root and subcommand).

Command definition: src/cligen/command.cr

CliGen::Command is the base class. When subclassed, macro inherited installs a macro finished block that triggers four code-generation macros in order:

  1. define_actions — collects all @[SubCommand]-annotated class methods into ACTIONS : Array(String) and @@action : String.
  2. define_header — builds the HEADER string (banner + examples) from @[SubCommand] and @[CommandSelection] annotation metadata.
  3. define_runner (skipped if @[CommandInfo(def_runner: false)]) — generates a self.run method that dispatches on @@action or a selection variable via a case statement.
  4. define_action_setter — generates self.action= with bounds-checking against ACTIONS.

Key macros on Command

  • define_argument — declares a class-level variable (@@) and an annotated setter method (@[CommandArgument]). Handles String, Bool, Int32, Array(String|Int32), and Time. Optional check: proc, logger: method, and def_getter: flag.
  • define_selection — like define_argument but validates against a fixed list of values; annotated with @[CommandArgument] and participates in selection-based dispatch.

Parser generation: src/cligen/command/parser.cr

CliGen::Parser is extended by Command. It provides define_parser, which generates self.make_parser(parent_parser). That method:

  1. Creates a subparser OptionParser with the command's HEADER as banner.
  2. Wires @[CommandSelection]-annotated methods as parser.on(name, description) that set the selection variable.
  3. Wires @[SubCommand]-annotated methods as subcommand strings that set @@action.
  4. Wires @[CommandArgument]-annotated methods as parser.on(short, long, description) flag handlers.
  5. Calls CliGen.define_default_flags (adds -h/--help, error handlers).
  6. Registers the whole subparser on parent_parser under the command's lowercase class name.

Annotations

Annotation Applied to Purpose
@[CommandInfo(description:, def_runner:)] Command subclass Required; provides the description shown in root help; def_runner: false skips auto-generating run
@[SubCommand(description:, examples:)] class method on Command Marks a method as a dispatachable subcommand
@[CommandArgument(short:, long:, description:, type:)] class method on Command Generated automatically by define_argument/define_selection; drives parser wiring
@[CommandSelection(selector:, description:, examples:)] class method on Command Alternative to SubCommand; dispatches via a named selector variable instead of @@action
@[CommandPreRun] class method on Command Methods run unconditionally before dispatch inside self.run
@[DefaultFlag] (reserved) Defined but not currently used in generation

Supporting files

  • src/cligen/format.cr — date/datetime format strings used by Time argument parsing.
  • src/cligen/regex.cr — regexes for validating date/datetime input strings.

Spec structure

spec/command_spec.cr uses Crystal's macro system heavily: most it blocks are generated at compile time by inspecting CommandSubclass via @type introspection. The macro finished wrapper around the entire describe block is required because make_parser and run don't exist until all macro finished hooks have fired.