Committing before help rework

This commit is contained in:
2026-09-05 13:19:55 -05:00
parent 4cce5cc45c
commit 3fa77f5707
105 changed files with 22505 additions and 684 deletions
+35
View File
@@ -472,6 +472,41 @@ Implementation notes:
### Converter Objects
Currently a custom type is wired in by reopening it and extending `CliGen::Coercable` (or `CliGen::Parsable`). That works, but it requires *owning* the type — you cannot use `Flag(SomeShardType)` without monkey-patching another shard. A converter object, modelled on `JSON::Serializable`'s `converter:`, moves the conversion logic outside the type.
```crystal
abstract class CliGen::Converter(T)
abstract def convert(raw : String) : T
abstract def to_s(value : T) : String
end
class PointConverter < CliGen::Converter(Point)
def convert(raw : String) : Point
a, b = raw.split("x", 2)
Point.new(a.to_i, b.to_i)
end
def to_s(value : Point) : String
"#{value.x}x#{value.y}"
end
end
argument(origin : Point, description: "origin", converter: PointConverter)
```
Implementation notes:
* Threading is the usual four hops: `converter:` on `argument`/`add_global_flag` → validated in `check_flag_vars``@[CliGen::Argument(converter:)]``generate.cr``Flag.new`
* The converter/flag type match is checkable at compile time — `PointConverter.ancestors` contains the *instantiated* `CliGen::Converter(Point)`, so a mismatched converter is rejected with a real message rather than failing inside array construction
* `Converter(T)#convert` has a declared return type, which `Coercable#coerce` does not — this turns a class of user error into a definition-site compile error
* Being bidirectional, a converter supplies the rendering half too, so the `T.class.has_method? :to_s` constraint in `Flag(T)#initialize` can be dropped for converted types
* A subclass instance (rather than a module) can carry state — a date format, a locale, a delimiter — and is what makes the generic parameter available for the ancestor check above
* This does **not** replace `CliGen::Parsable`, which has a different shape (`Array(Arg) -> T` plus the mark-something-processed invariant). Converter is the `String -> T` path; `Parsable` stays the multi-arg path. An explicit converter should take precedence over an implicit `Coercable`
* Open question for `Array(T)`: whether `converter:` means a `Converter(T)` applied per-element after the delimiter split, or a `Converter(Array(T))` that splits itself. Per-element composes with `delimiter:` and `format:` and is more reusable; the ancestor check can distinguish the two, so both could be supported
* Payoff beyond the feature itself: `flag.cr` has three separate compile-time chains over `T` (`process`, `coerce`, and the array-element branches). A converter short-circuits all three with a single `if conv = @converter`. It is also a route to Enum support — a macro-generated `EnumConverter(T)` calling `T.parse(raw)`, with `options` derived from `T.names`, would land enums without touching those dispatch chains
### JSON-RPC like execution
This is a LATE (post v1.0) feature. It takes the same format and instead of a CLI argument