Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

6. Versioned Commands and Explicit Assignments

A generated command represents an invocation with explicit values and a target version. Its correctness depends on the collected syntax, descriptor conventions and assignment semantics.

The compiler can check property names and CLR types. Availability guards and serialization handle information that depends on the selected version and actual property assignments.

Merging sampled versions

VersionDiffer sorts input versions by semantic precedence and collects command paths across those snapshots. For multiple snapshots, first and last observed appearances establish command bounds. The upper bound is the next sampled version after the last appearance when the command is absent from the newest snapshot.

Options are merged by long name. The latest occurrence supplies their descriptive and type information. Positional arguments come from the latest observed command node. A single-snapshot input generates no inferred Since/Until bounds.

Consider this illustrative observation table:

Snapshot Command observed? Option observed?
1.0.0 Yes No
1.2.0 Yes Yes
1.4.0 No Not observable on this command

A bound inferred at 1.2.0 means “first observed in the supplied data.” It does not establish the upstream commit that introduced the option, or behavior in an unsampled 1.1 release.

The unified model stores bounds, rather than a complete set of disjoint availability intervals. Disappearance and reintroduction therefore need special attention. Help snapshots also do not establish that argument types and semantics stayed constant between releases.

TargetVersion is carried through construction

Generated clients use the binding's detected version when constructing builders and commands. Manually created commands can receive it in their constructor or initializer.

A direct object demonstrates the generated shape. This complete offline example requests Git porcelain v2 and prints arguments; it executes no Git process.

using FrenchExDev.Net.BinaryWrapper.Lib;
using FrenchExDev.Net.Git;

var command = new GitStatusCommand(SemanticVersion.Parse("2.53.0"))
{
    Porcelain = "2",
    Branch = true,
};
Console.WriteLine(string.Join(" | ", command.ToArguments()));

Git's descriptor uses an equals separator so the optional porcelain value is attached: --porcelain=2. A flag value does not become an independent accidental path argument.

A manually created command and a builder are different construction paths. Builders implement generated compatibility validation. Supplying TargetVersion on a command should not be described as a universal revalidation of all construction guards by the executor.

Explicit assignments

The generator records whether each option or positional property was assigned. Builders similarly track whether With* was called, then transfer that information.

This matters for false, 0, an empty string and defaults. An unassigned option leaves the tool's default in control. An explicitly assigned default value expresses an override when the descriptor's serialization supports it.

Unset boolean          -> no emitted option
Explicit true          -> descriptor-specific true form
Explicit false         -> descriptor-specific false form, when supported

Presence-only booleans cannot express the same syntax as equals-form booleans. Docker and Compose enable explicit boolean serialization such as --flag=false. Do not extrapolate that convention to every tool.

The same distinction appears in environment records, where absence, suppression and an empty value can have three different meanings.

Packer's bare machine-readable flag

Packer's global machine-readable switch is an exception to its usual boolean equals convention. Its descriptor names machine-readable in BareTrueFlags.

This complete offline example prints the serialized options:

using FrenchExDev.Net.BinaryWrapper.Lib;
using FrenchExDev.Net.Packer;

var version = SemanticVersion.Parse("1.16.0");
var enabled = new PackerBuildCommand(version) { MachineReadable = true };
var disabled = new PackerBuildCommand(version) { MachineReadable = false };
Console.WriteLine(string.Join(" | ", enabled.ToArguments()));
Console.WriteLine(string.Join(" | ", disabled.ToArguments()));

True produces the bare -machine-readable token. Explicit false produces -machine-readable=false. The output strategy recognizes the exact bare token for automatic machine-readable parsing. A superficially similar token need not enable the same upstream mode.

Tokens and command paths

CommandPath contains path segments such as container and run. ToArguments() supplies serialized options and positional arguments. The executor concatenates those arrays, not a shell command string.

A path with spaces must remain one element. Shell quoting characters belong to a shell grammar and should not be added to an argument element merely because it contains spaces. The process runner uses the argument-list boundary.

Consumers can still expose holes in help-derived positional syntax. Packer provides PackerInvocation to append known positional inputs without discarding the generated command's version and environment. GitLab CLI has an explicit adapter for its api command. These are focused adaptations, rather than evidence that every scraped argument surface is complete.

Naming and hierarchy

CLI names can normalize to identical C# identifiers. The naming resolver gives generated command types deterministic distinct names while keeping the actual binary tokens unchanged.

By default generation selects leaf commands. Tools such as WinGet enable IncludeParentCommands because roots and parents have meaningful direct invocations. Parent generation changes the API surface; it does not imply that every parent help node is an operational command for every tool.

Validation boundaries

Test a command at three levels: its assigned properties, the complete argument array and the final ProcessSpec delivered to the transport. Version-boundary tests then verify the builder path on sampled availability edges.

Output tests are separate. A perfectly serialized command can produce a format outside the reviewed parser profile, and a matching profile can still receive malformed data. Output versioning explains how that second compatibility decision is made.

Source snapshot

Source inspection: 2 October 2026. These implementation paths are relative to Net/FrenchExDev in the local ecosystem checkout. The v2 changes are largely uncommitted; these paths identify inspected code, without claiming that public branch links contain it.

  • BinaryWrapper/src/FrenchExDev.Net.BinaryWrapper.SourceGenerator/VersionDiffer.cs
  • BinaryWrapper/src/FrenchExDev.Net.BinaryWrapper.SourceGenerator/CommandClassEmitter.cs
  • BinaryWrapper/src/FrenchExDev.Net.BinaryWrapper.SourceGenerator/BuilderClassEmitter.cs
  • BinaryWrapper/src/FrenchExDev.Net.BinaryWrapper.SourceGenerator/NamingHelper.cs
  • Git/src/FrenchExDev.Net.Git/GitDescriptor.cs
  • Packer/src/FrenchExDev.Net.Packer/PackerDescriptor.cs

Previous · Series index · Next

⬇ Download