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:
define_actions— collects all@[SubCommand]-annotated class methods intoACTIONS : Array(String)and@@action : String.define_header— builds theHEADERstring (banner + examples) from@[SubCommand]and@[CommandSelection]annotation metadata.define_runner(skipped if@[CommandInfo(def_runner: false)]) — generates aself.runmethod that dispatches on@@actionor a selection variable via acasestatement.define_action_setter— generatesself.action=with bounds-checking againstACTIONS.
Key macros on Command
define_argument— declares a class-level variable (@@) and an annotated setter method (@[CommandArgument]). HandlesString,Bool,Int32,Array(String|Int32), andTime. Optionalcheck:proc,logger:method, anddef_getter:flag.define_selection— likedefine_argumentbut 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:
- Creates a subparser
OptionParserwith the command'sHEADERas banner. - Wires
@[CommandSelection]-annotated methods asparser.on(name, description)that set the selection variable. - Wires
@[SubCommand]-annotated methods as subcommand strings that set@@action. - Wires
@[CommandArgument]-annotated methods asparser.on(short, long, description)flag handlers. - Calls
CliGen.define_default_flags(adds-h/--help, error handlers). - Registers the whole subparser on
parent_parserunder 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 byTimeargument 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.