object_rework: working MVP with runtime CommandNode tree

- BaseCommandNode (abstract) / CommandNode(T) split for heterogeneous tree
- Flag(T) with compile-time type branching, Parsable/Coercable modules
- Arg double-process invariant enforcement
- App.generate macro builds CommandNode tree from Command subclasses
- Command#initialize generated via macro finished, populates ivars from handler
- Reserved -h/--help enforcement in Flag#check!
- MatchType::Help added, SubCommand dispatch fixed
- design.adoc: added Planned Features (markdown docs, bash completion)
- Makefile: tabs, .DEFAULT_GOAL, doc_show target

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-08-09 22:49:55 -05:00
parent f5571cde73
commit 6f31a5d27c
9 changed files with 130 additions and 29 deletions
+67
View File
@@ -74,3 +74,70 @@ The examples like above provide a "DSL-esk" way of defining:
==== How it works
Using crystal macros, you define the shape (arguments/flags, selections, work functions/subcommands, etc
== Planned Features
=== Markdown Documentation Generation
Since all command metadata is present in annotations at compile time (`@[CliGen::CommandInfo]`, `@[CliGen::SubCommand]`, `@[CliGen::Argument]`, `@[CliGen::Selection]`), the framework can walk the same structures that `generate.cr` already walks and render them into a Markdown document instead of a `CommandNode` tree.
The generation would be driven by a `macro finished` block (similar to `generate.cr`) that emits a `self.generate_docs` class method on `App`. This method walks every `Command` subclass and its annotations to produce a structured document.
Proposed output structure:
----
# <AppName>
## Commands
### mycommand
<CommandInfo description>
#### Flags
| Flag | Short | Type | Default | Description |
|------|-------|------|---------|-------------|
| --myvar VAR | -m | Int32 | 23 | ... |
#### Subcommands
- `do_thing` — <description>
- Examples: ...
----
Implementation notes:
* Driven by a `--generate-docs` global flag or a dedicated class method
* ECR templates (already pulled in) are the natural rendering mechanism
* The same annotation data powers both runtime help output and the doc generator, keeping them in sync automatically
=== Bash Autocompletion Generation
Since all command names and flag names are known at compile time, a complete bash completion script can be generated statically — no runtime `--completions` endpoint needed.
The approach is a hidden `--generate-completion bash` flag (potentially extended to `zsh`/`fish` later) that prints a ready-to-install completion script to stdout.
Proposed completion script shape:
[source,bash]
----
_myapp() {
local cur="${COMP_WORDS[COMP_CWORD]}"
local prev="${COMP_WORDS[COMP_CWORD-1]}"
case "${COMP_WORDS[1]}" in
mycommand)
COMPREPLY=($(compgen -W "--myvar -m --format -f" -- "$cur"))
;;
*)
COMPREPLY=($(compgen -W "mycommand myothercommand" -- "$cur"))
;;
esac
}
complete -F _myapp myapp
----
Implementation notes:
* Script body generated at compile time via a `macro finished` walk of `Command.subclasses`
* `@[CliGen::Selection]` options (`%w[json yaml ecr]`) can be included as valid completions for their flag
* Install path: `myapp --generate-completion bash > ~/.bash_completion.d/myapp` or printed with instructions
* Same annotation data used by the doc generator, so both stay in sync with the command definition