Scal.Interpreting.Commands
1.0.5
dotnet add package Scal.Interpreting.Commands --version 1.0.5
NuGet\Install-Package Scal.Interpreting.Commands -Version 1.0.5
<PackageReference Include="Scal.Interpreting.Commands" Version="1.0.5" />
<PackageVersion Include="Scal.Interpreting.Commands" Version="1.0.5" />
<PackageReference Include="Scal.Interpreting.Commands" />
paket add Scal.Interpreting.Commands --version 1.0.5
#r "nuget: Scal.Interpreting.Commands, 1.0.5"
#:package Scal.Interpreting.Commands@1.0.5
#addin nuget:?package=Scal.Interpreting.Commands&version=1.0.5
#tool nuget:?package=Scal.Interpreting.Commands&version=1.0.5
Scal.Interpreting.Commands
A lightweight, deterministic command-line interpreter for DotNet with attribute-based validation and type conversion.
Unlike reflection-heavy or attribute-mandatory CLI frameworks, Scal.Interpreting.Commands prioritizes deterministic resolution and minimal dependencies.
Motivation
Philosophy
- simple deterministic grammar: verb (noun) (arguments)
- case-insensitive
- dash-tolerant but dash-agnostic
- accept abbreviations but detect collisions
- bulletproof and predictable behavior
Technical features
- verb/noun definition by attributes or Pascal-case naming convention
- strongly-typed command instantiation
- validation via DataAnnotations
- TypeConverter support
- contextual help generation
- dependency-free
- DI-agnostic construction
- .Net 8.0 LTS compatible (console or ASP.NET)
- lightweight (425 lines in total including comments, 4 classes and 2 extensions)
Syntax
Usage example
dotnet add package Scal.Interpreting.Commands
Example of a program accepting as List Image Name=abc command
[DataContract(Name = "CliArgs")]
[Description("Cli arguments interpreter example")]
public abstract class Program
{
private static async Task<int> Main(string[] args)
{
var interpretation = new CommandLineInterpreter().Interpret<Program>(args);
if (interpretation.Command is null) {
interpretation.Feedback(Console.WriteLine);
return 1;
}
interpretation.Feedback(Console.WriteLine, showHelp: false);
return await interpretation.Command.ExecuteAsync();
}
public abstract Task<int> ExecuteAsync();
[Description("List the images")]
[DataContract(Namespace = "List", Name = "Image")]
public class ListImage : Program
{
[Description("The image name pattern")]
[Required]
[MinLength(1)]
public string Name { get; set; } = string.Empty;
[Description("The image type Id")]
[Range(1, 9)]
public int TypeId { get; set; } = 1;
public override Task<int> ExecuteAsync()
{
Console.WriteLine("Simulate {0} {1}", nameof(ListImage), this.Name);
return Task.FromResult(0);
}
}
}
Mention that:
- the Program itself is an abstract with just an entrypoint and the ExecuteAsync contract
- commands are classes deriving from Program containg the methods you desire (ExecuteAsync in the example)
- it is derived in a ListImage class that is instantiated
- I choose to output the feedback without help in case of success which shows the program title
Executing it with List Image Name=abc or L I N=abc gives:
CliArgs Cli arguments interpreter example
Simulate ListImage abc
Help
Executing the program without parameter will output this with the accepted abbreviations in parentheses:
CliArgs Cli arguments interpreter example
*** : Usage: verb (noun) (parameters)
List Image List the images (L I)
TypeId The image type Id (T)
Name The image name pattern (N)
Validation using DataAnnotations attributes or your custom attributes
Executing it with:
CliArgs.exe List Image Name= Type=10
or
CliArgs.exe L I N= T=10
gives:
CliArgs Cli arguments interpreter example
*** TypeId: The field TypeId must be between 1 and 9.
*** Name: The Name field is required.
List Image List the images (L I)
TypeId The image type Id (T)
Name The image name pattern (N)
New ListImport command without attribute
When adding a new command List Import to the same program:
public class ListImportWithoutParameter : Program
{
public override Task<int> ExecuteAsync()
{
Console.WriteLine("Simulate {0}", nameof(ListImportWithoutParameter));
return Task.FromResult(0);
}
}
Mention that:
- The class does not require any attribute and VerbNoun is extracted using the first two words of the class name Pascal-casing
- Acronyms such as XML or HTTP will be split into individual letters unless explicitly configured using attributes, e.g. a ListXMLFile will be interpreted as List X by convention. In such a case, use a DataContract attribute to clarify your intent.
- The abbreviations now become l ima for List Image and l imp for List Import as those are the minimum required to prevent ambiguity.
⚠️ Please note that abbreviations should never be used is scripts or documentation for many reasons (clarity, newbie-friendly), including the fact that they may change by adding new commands.
Verb-only command
You may define a verb-only command by creating a one-word class like Cleanup, or by specifying a Namespace in a DataContract without Name:
public class Cleanup : Program
{
public override Task<int> ExecuteAsync()
{
Console.WriteLine("Simulate {0}", nameof(Cleanup));
return Task.FromResult(0);
}
}
Type converter and custom validation
Custom type converter and custom validation may be used:
public class SomeCommand : Program
{
[TypeConverter(typeof(YourTypeConverter))]
[YourCustomValidation]
[Required]
public YourType Reference { get; set; }
public override Task<int> ExecuteAsync()
{
Console.WriteLine("Simulate {0} {1}", nameof(SomeCommand), this.Reference);
return Task.FromResult(0);
}
}
Factory constructor
You may provide a factory delegate to integrate with your preferred DI framework:
var interpreter = new CommandLineInterpreter(
factory: type => MyContainer.Resolve(type));
If no factory delegate is provided, Activator is used:
... = Activator.CreateInstance(type);
Examples
To view examples, see the tests models: by convention, by annotation and with type converter.
Thanks
Thanks to Dan (aka ChatGPT) for they advices and making doubts disappear.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- No dependencies.
GitHub repositories
This package is not used by any popular GitHub repositories.
# Change Log
<a name="1.0.5"></a>
## [1.0.5](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/releases/tag/v1.0.5) (2026-02-23)
### Bug Fixes
* Image form NuGet ([efbb846](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/efbb84603476ce4cfc44f2078dbe29ef7e3dc07f))
<a name="1.0.4"></a>
## [1.0.4](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/releases/tag/v1.0.4) (2026-02-23)
<a name="1.0.3"></a>
## [1.0.3](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/releases/tag/v1.0.3) (2026-02-23)
<a name="1.0.2"></a>
## [1.0.2](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/releases/tag/v1.0.2) (2026-02-23)
<a name="1.0.1"></a>
## [1.0.1](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/releases/tag/v1.0.1) (2026-02-23)
### Documentation
* Adjust ReadMe ([38b90a4](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/38b90a4d2e98991d35f955d40936e85af805510d))
* Align tags with GitHub ([a3828ef](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/a3828ef3a1de6fd6bbbd4f3e9a84127acc904c1c))
* ChangeLog in PackageReleaseNotes ([d36de6e](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/d36de6e1cbf15b5aee114ce40ee26e00721d2cb0))
* Description and tags refinement ([6ffbee4](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/6ffbee44b628d38fccfea2024888ed2fce3c5b00))
* Documentation changes including ebnf, move ChangeLog to root ([3738fe0](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/3738fe08057987649d5f3bf4ff5056854c33dd08))
* ReadMe corrections ([49876ba](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/49876ba8024c774b40db85cc11b04a00ebc37eac))
* Tags ([bb75e38](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/bb75e38ab66e5d803f4d328f8d31225b87442169))
* Use ChangeLog content as PackageReleaseNotes ([9fd3f1c](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/9fd3f1cb326471b79a3768bd7466c19128a36774))
* **ChangeLog:** Remove pre-v1 changes ([003ecbf](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/003ecbfd67334deec566bc2394d44962ddba4025))
* **Versionize:** Default configuration ([6a12c3b](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/6a12c3b3ecf87a2b11d75928eaf8a4385f42fe46))
### Small Changes
* Use Versionize 2.5.0 ([e650607](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/e650607b31dbe4cf3fdf4aa159d43003131a29df))
<a name="1.0.0"></a>
## [1.0.0](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/releases/tag/v1.0.0) (2026-02-18)
### Breaking Changes
* Bump v1 ([88d1af8](https://www.github.com/Scal-Human/Scal.Interpreting.Commands/commit/88d1af83ba1397645bfbf3322513fe66790c68b9))