Committing before help rework
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user