Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
Notable changes are recorded here.

# Unreleased

`ArgParserGenerator` now supports a field of type `SomeArgs option`, where `SomeArgs` is another argument record or a union of alternative argument sets: a whole group of arguments which need not be supplied.

The group is present exactly when at least one argument beneath it was supplied — the same rule by which a union's case is selected — and its own required arguments are then enforced as usual, so supplying part of a group demands the rest of it rather than quietly treating the group as absent.
Help text introduces the group under a `Child (optional):` header rather than presenting it as an alternation: the two alternatives it is implemented with are the generator's, not the author's.

`Choice<SomeArgs, SomeArgs>` works the same way, and says that omitting the group means a particular value rather than no value: `Choice2Of2` carries the default and `Choice1Of2` what was supplied, exactly as for a defaulted leaf.
The default must come from `[<ArgumentDefaultFunction>]`, since neither a literal nor an environment variable can construct a record; and it is all-or-nothing, so supplying part of a group still demands the rest rather than filling the gaps from the default.

A group which is itself satisfiable by supplying nothing cannot be wrapped, and is rejected at generation time.
No command line could distinguish "this group was supplied, and everything in it took its default" from "this group was never mentioned", so there is a real modelling question here, and the generated parser should not answer it by silently preferring one.

Bugfix: `ArgParserGenerator` now backticks the `Default`-prefixed member name a defaulted field calls.
A field named `` `` `` ``space in name`` `` `` `` takes its default from `` `` `` ``Defaultspace in name`` `` `` ``, which was emitted bare, so the generated file did not parse.
This affected defaulted leaves (and their help text) as well as the newly-supported defaulted groups.

`ArgParserGenerator` now reports a proper error for a field of type `'a list list` or `'a option list`.

Both shapes were already unsupported, but only the `[<PositionalArgs>]` path said so: on the ordinary path they passed classification and failed much later against an assertion phrased as an internal error ("WoofWare.Myriad invariant violated"), despite being reachable from ordinary source.
Both paths now give the same message, which says why the shape has no spelling: each occurrence supplies one element, so nothing marks where one inner list ends and the next begins, and an absent element would be an occurrence which is not there.

# WoofWare.Myriad.Plugins 11.0.1

Breaking change: `ArgParserGenerator` now rejects, at generation time, several attribute placements which it previously accepted and then silently ignored.
Expand Down
88 changes: 88 additions & 0 deletions ConsumePlugin/Args.fs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,64 @@ type ParentRecordWithEscapedHelp =
Child : ChildRecord
}

/// A whole group of arguments may be omitted. Supplying none of `ChildRecord`'s arguments makes
/// the field `None`; supplying any of them makes it `Some`, and `ChildRecord`'s own required
/// arguments are then enforced as usual.
[<ArgParser true>]
type ParentRecordOptionalChild =
{
Child : ChildRecord option
AndAnother : bool
}

/// An optional group whose header carries help text, and which contains a positional sink. The
/// sink accepts zero tokens, but `Thing1` is required, so the group as a whole is not satisfiable
/// by an empty command line and can therefore be told apart from its own absence.
[<ArgParser true>]
type ParentRecordOptionalChildPos =
{
[<ArgumentHelpText "Settings for the child thing">]
Child : ChildRecordWithPositional option
}

/// A group of arguments which need not be supplied, but which stands for a value rather than for
/// nothing when it is omitted. As for a defaulted leaf, the Choice reports which happened.
[<ArgParser true>]
type ParentRecordDefaultedChild =
{
[<ArgumentDefaultFunction>]
Child : Choice<ChildRecord, ChildRecord>
AndAnother : bool
}

/// The default-function convention resolves against the record which declares the field,
/// exactly as it does for a leaf.
static member DefaultChild () : ChildRecord =
{
Thing1 = 42
Thing2 = "from the default"
}

type GrandchildRecord =
{
Deep : int
}

/// An optional group may contain one, and may be namespaced like any other structural field.
/// The inner group's absence does not make the outer group absent: `Thing1` is what decides that.
type ChildWithOptionalGrandchild =
{
Thing1 : int
Grandchild : GrandchildRecord option
}

[<ArgParser true>]
type ParentRecordNestedOptional =
{
[<ArgumentPrefix "db">]
Child : ChildWithOptionalGrandchild option
}

[<ArgParser true>]
type ChoicePositionals =
{
Expand Down Expand Up @@ -413,3 +471,33 @@ type AwkwardFieldName =
``__LINE__`` : int
``break`` : int
}

/// A defaulted field's default comes from a static member named `Default` + the field name, so an
/// awkward field name makes an awkward *member* name, which needs backticks at the call site
/// exactly as its declaration did. `Default` + `mod` is the perfectly ordinary `Defaultmod`, so
/// the names here are ones which stay awkward after the prefix is glued on.
///
/// Three separate emission sites call that member: a defaulted leaf's `parser_applyDefault`, a
/// defaulted group's instantiation, and the help text, which renders a leaf's default by calling
/// the function at generated-program runtime.
[<ArgParser true>]
type AwkwardDefaultName =
{
[<ArgumentDefaultFunction>]
``space in name`` : Choice<int, int>

[<ArgumentPrefix "grp">]
[<ArgumentDefaultFunction>]
``group name`` : Choice<ChildRecord, ChildRecord>

[<ArgumentPrefix "opt">]
``optional group`` : ChildRecord option
}

static member ``Defaultspace in name`` () = 5

static member ``Defaultgroup name`` () : ChildRecord =
{
Thing1 = 1
Thing2 = "defaulted group"
}
39 changes: 39 additions & 0 deletions ConsumePlugin/DuArgs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,45 @@ type WithModeArgs =
Mode : Mode
}

type CompressArgs =
{
Level : int
}

type EncryptArgs =
{
Recipient : string
}

/// Every case demands an argument, so no command line satisfies this union by saying nothing --
/// which is what lets an absent group be told apart from a present one.
type Transform =
| Compress of CompressArgs
| Encrypt of EncryptArgs

/// A union of alternative argument sets which need not be chosen among at all.
[<ArgParser>]
type WithOptionalTransformArgs =
{
Verbose : bool
Transform : Transform option
}

/// Not choosing among the alternatives means taking a particular one, rather than taking none.
[<ArgParser>]
type WithDefaultedTransformArgs =
{
Verbose : bool
[<ArgumentDefaultFunction>]
Transform : Choice<Transform, Transform>
}

static member DefaultTransform () =
Transform.Compress
{
Level = 6
}

type DefaultedArgs =
{
[<ArgumentDefaultFunction>]
Expand Down
Loading
Loading