Releases: dotmake-build/command-line
DotMake.CommandLine v1.7.0
-
Added ShowHelp, IsEmptyCommand and ShowValues extension methods to InvocationContext. You can now manually show
help in command class Run or RunAsync method, for example if a sub-command represents a group and not an action,
you can show help. If Run or RunAsync method is missing in command class, then by default it will show help
(analyzer warning DMCLI24 is disabled). ShowValues is useful for testing a command, it shows parsed values for
current command and its arguments and options.See below example, root command does not have a handler method so it will always show help
and sub-command will show help if command is specified without any arguments or option,
and it will show (dump) values if not:[CliCommand(Description = "A root cli command")] public class HelpCliCommand { [CliOption(Description = "Description for Option1")] public string Option1 { get; set; } = "DefaultForOption1"; [CliArgument(Description = "Description for Argument1")] public string Argument1 { get; set; } = "DefaultForArgument1"; [CliCommand(Description = "A sub cli command")] public class SubCliCommand { [CliArgument(Description = "Description for Argument2")] public string Argument2 { get; set; } = "DefaultForArgument2"; public void Run(InvocationContext context) { if (context.IsEmptyCommand()) context.ShowHelp(); else context.ShowValues(); } } }
DotMake.CommandLine v1.6.9
- Added support for localizing commands, options and arguments.
You can now specify anameof
operator expression with a resource property (generated by resx) in the attribute's argument (forstring
types only)
and the source generator will smartly use the resource property accessor as the value of the argument so that it can localize at runtime.
If the property in thenameof
operator expression does not point to a resource property, then the name of that property will be used as usual.
[CliCommand(Description = nameof(TestResources.CommandDescription))]
internal class LocalizedCliCommand
{
[CliOption(Description = nameof(TestResources.OptionDescription))]
public string Option1 { get; set; } = "DefaultForOption1";
[CliArgument(Description = nameof(TestResources.ArgumentDescription))]
public string Argument1 { get; set; }
public void Run()
{
Console.WriteLine($@"Handler for '{GetType().FullName}' is run:");
Console.WriteLine($@"Value for {nameof(Option1)} property is '{Option1}'");
Console.WriteLine($@"Value for {nameof(Argument1)} property is '{Argument1}'");
Console.WriteLine();
}
}
DotMake.CommandLine v1.6.8
-
Added support for SuppressNullableWarningExpression and C# 11
required
modifier:An option/argument will be considered required when
- There is no property initializer and the property type is a reference type (e.g.
public string Arg { get; set; }
).
string
is a reference type which has a null as the default value butbool
andenum
are value
types which already have non-null default values.Nullable<T>
is a reference type, e.g.bool?
. - There is a property initializer, but it's initialized with
null
ornull!
(SuppressNullableWarningExpression)
(e.g.public string Arg { get; set; } = null!;
). - If it's forced via attribute property
Required
(e.g.[CliArgument(Required = true)]
). - If it's forced via
required
modifier (e.g.public required string Opt { get; set; }
).
Note that for being able to userequired
modifier, if your target framework is below net7.0,
you also need<LangVersion>11.0</LangVersion>
tag (minimum) in your .csproj file
(our source generator supplies the polyfills automatically as long as you set C# language version to 11).
An option/argument will be considered optional when
- There is no property initializer (e.g.
public bool Opt { get; set; }
) but the property type is a value type
which already have non-null default value. - There is a property initializer but it's not initialized with
null
ornull!
(SuppressNullableWarningExpression)
(e.g.public string Arg { get; set; } = "Default";
). - If it's forced via attribute property
Required
(e.g.[CliArgument(Required = false)]
).
- There is no property initializer and the property type is a reference type (e.g.
-
Added AllowExisting property to [CliOption] and [CliArgument] which gets or sets a value indicating whether
an argument should accept only values corresponding to an existing file or directory.
DotMake.CommandLine v1.6.6
- Added: Delegate-based model for making cli apps even easier!
Create a CLI App with DotMake.Commandline in seconds!
In Program.cs, add this simple code:And that's it! You now have a fully working command-line app.Cli.Run(([CliArgument]string argument1, bool option1) => { Console.WriteLine($@"Value for {nameof(argument1)} property is '{argument1}'"); Console.WriteLine($@"Value for {nameof(option1)} parameter is '{option1}'"); });
- Fixed: Bool flags were broken, bool options should be set as "true" even if no argument is provided.
- Fixed: NullReferenceException for option or argument with bool type.
DotMake.CommandLine v1.6.4
- Fixed: Ensured generated code does not cause CS1591 warning when project uses
<GenerateDocumentationFile>
.
Used<inheritdoc />
for builder files and added<auto-generated />
for extension files like ModuleInitializerAttribute.cs
and CliServiceExtensions.cs
DotMake.CommandLine v1.6.3
- Improved: Ensure generation (which is rendered in the generated source code) is counted separately for different projects and target frameworks. This makes source control cleaner for TestApps which use EmitCompilerGeneratedFiles.
Also use (FileName) instead of [FileName] for special classes so they are at the top in VS Analyzers node and they can be spotted quickly. - Improved: Better error handling in ArgumentConverter. If constructor or parse method of custom class/ienumerable throws exception, provide the details in error message.
DotMake.CommandLine v1.6.2
-
Added dependency injection support. When the source generator detects that your project has reference
toMicrosoft.Extensions.DependencyInjection
, it will generate extension methods for supporting dependency injection.
For example, you can now add your services with the extension methodCli.Ext.ConfigureServices
:using DotMake.CommandLine; using Microsoft.Extensions.DependencyInjection; Cli.Ext.ConfigureServices(services => { services.AddTransient<TransientClass>(); services.AddScoped<ScopedClass>(); services.AddSingleton<SingletonClass>(); }); Cli.Run<RootCliCommand>();
Then let them be injected to your command class automatically by providing a constructor with the required services:
[CliCommand(Description = "A root cli command with dependency injection")] public class RootCliCommand { private readonly TransientClass transientDisposable; private readonly ScopedClass scopedDisposable; private readonly SingletonClass singletonDisposable; public RootCliCommand( TransientClass transientDisposable, ScopedClass scopedDisposable, SingletonClass singletonDisposable ) { this.transientDisposable = transientDisposable; this.scopedDisposable = scopedDisposable; this.singletonDisposable = singletonDisposable; } [CliOption(Description = "Description for Option1")] public string Option1 { get; set; } = "DefaultForOption1"; [CliArgument(Description = "Description for Argument1")] public string Argument1 { get; set; } public void Run() { Console.WriteLine($@"Handler for '{GetType().FullName}' is run:"); Console.WriteLine($@"Value for {nameof(Option1)} property is '{Option1}'"); Console.WriteLine($@"Value for {nameof(Argument1)} property is '{Argument1}'"); Console.WriteLine(); Console.WriteLine($"Instance for {transientDisposable.Name} is available"); Console.WriteLine($"Instance for {scopedDisposable.Name} is available"); Console.WriteLine($"Instance for {singletonDisposable.Name} is available"); Console.WriteLine(); } } public sealed class TransientClass : IDisposable { public string Name => nameof(TransientClass); public void Dispose() => Console.WriteLine($"{nameof(TransientClass)}.Dispose()"); } public sealed class ScopedClass : IDisposable { public string Name => nameof(ScopedClass); public void Dispose() => Console.WriteLine($"{nameof(ScopedClass)}.Dispose()"); } public sealed class SingletonClass : IDisposable { public string Name => nameof(SingletonClass); public void Dispose() => Console.WriteLine($"{nameof(SingletonClass)}.Dispose()"); }
DotMake.CommandLine v1.6.0
- Improved command description handling in help output:
Fixed subcommand name and description on different line issue.
If there is no CliCommand.Description and it's the root command currently executing, AssemblyDescriptionAttribute will be used. - Added more docs to CliHelpBuilder.
DotMake.CommandLine v1.5.9
- Fixed: inheritance logic for CliOption and CliArgument attributes, the attibute from the most derived property should be used
and other ones should be ignored (double option or argument should not be created). - Added: Cli.GetArgs method which returns the same as the special variable
args
available inProgram.cs
(new style with top-level statements)
or as the string array passed to the program'sMain
method (old style).
For more more concise syntax when writing cli apps, madeargs
parameter optional for Cli.Run, Cli.RunAsync and Cli.Parse methods
so user does not have to specify it, i.e. it will be retrieved automatically from the process with Cli.GetArgs.
DotMake.CommandLine v1.5.8
- Added inheritance support for CliCommand classes, i.e. CliOption and CliArgument from inherited classes or interfaces will be included.
The property attribute and the property initializer from the most derived class in the hierarchy will be used (they will override the base ones).
The command handler (Run or RunAsync) will be also inherited.
This is useful when you have repeating options or arguments for your commands, i.e. you can define them once in a base class
and then share them by inheriting that base class.
[CliCommand]
public class InheritanceCliCommand : CredentialCommandBase, IDepartmentCommand
{
public string Department { get; set; } = "Accounting";
}
public abstract class CredentialCommandBase
{
[CliOption(Description = "Username of the identity performing the command")]
public string Username { get; set; } = "admin";
[CliOption(Description = "Password of the identity performing the command")]
public string Password { get; set; }
public void Run()
{
Console.WriteLine($@"I am {Username}");
}
}
public interface IDepartmentCommand
{
[CliOption(Description = "Department of the identity performing the command (interface)")]
string Department { get; set; }
}