Skip to content

Releases: dotmake-build/command-line

DotMake.CommandLine v2.6.0

15 Jul 12:41
Compare
Choose a tag to compare
  • Fixed: With v2.5.8, auto short form aliases were still being added even when they already existed, causing the unnecessary conflict error.

  • Fixed: When calling Cli.Run<> with sub-commands but not a root command, help output did not include our custom changes.
    This happened because we were not adding our settings like HelpOption with CustomHelpAction, when the command was not a root command.
    For example, help output showed (as System.CommandLine default output):

    --settingsfile (REQUIRED)  Path to settings file

    where it should show:

    --settingsfile  Path to settings file [required]

    So now, even if it's a sub-command, its root command should be found and those settings should be added there.

    Additionaly, the fix introduced first in v2.0.0 System.CommandLine.RootCommand returned by Cli.GetConfiguration should have correct parents.
    turns out it disabled help output if you used Cli.Run<> with level-2 sub-commands or above,
    due to a subtle bug (root folder was not being populated so help option was lost).

DotMake.CommandLine v2.5.8

14 Jul 01:44
Compare
Choose a tag to compare
  • Improved: CliNamer to support a parent CliNamer. Unique names were only checked within the command builder
    so the current command was not aware of names of sibling commands. This is now fixed.
    And from now on, we will throw an explanatory exception when there is a name or alias conflict like this:

    System.Exception: CommandAlias 'c' for 'createschema' conflicts with parent CommandAlias for 'checkfornewtools'!

    This is because System.CommandLine threw a vague exception when there was a conflict during parsing:

    System.ArgumentException: An item with the same key has already been added. Key: c

    Unique names are checked for DirectiveName (surrounded with []), CommandName, CommandAlias, OptionName, OptionAlias.
    Auto short form aliases will be always derived from symbol name even when a specific name was specified,
    because if user specifies a specific name with all lowercase like checkfornewtools, we cannot split word boundaries.
    Help output will show short aliases first consistently especially for commands (order by prefix and then by length).

  • Improved: Set the values for the parent command accessors before directive, option and argument properties so that
    property get accessors can access them:

    public class DefaultSettingsCliCommand
    {
        [CliOption]
        public string SettingsFile 
        {
            get => RootCommand.SettingsFile; //<-- Avoid NullReferenceException with `RootCommand` here
            set => RootCommand.SettingsFile = value;
        }
        public RootCliCommand RootCommand { get; set; }
    }          
  • Fixed: Usage of [CliArgument].AllowedValues caused compiler error CS1061 because Argument<T>.AcceptOnlyFromAmong(...)
    was moved to extension method System.CommandLine.ArgumentValidation.AcceptOnlyFromAmong(...) in latest System.CommandLine.

  • Improved: Added empty publish/ folder to the repository. In src/nuget.config we add a custom package source so that we can consume our own built nuget package locally:

    <add key="DotMake Local" value="..\publish" />

    So as the publish/ folder was not commited to the repository, users could have a problem building the project due to NuGet Package Manager complaining about non-existing publish/ folder.

DotMake.CommandLine v2.5.6

28 Jun 19:59
Compare
Choose a tag to compare
  • Improved: Switched back to using official NuGet feed from DotNet Daily Builds feed for System.CommandLine as now 2.0.0-beta5.25306.1 is released.
    Now the DLL will no more be packed into our package and System.CommandLine will appear as NuGet dependency.

  • Fixed: Error DMCLI01: DotMake.CommandLine.SourceGeneration -> System.ArgumentException: Syntax node is not within syntax tree
    when using partial class for a command which has a nested class for a subcommand.
    Reason: SemanticModel from parent symbol can be different for a child symbol, especially for nested classes in a partial class.
    Solution: Check if SyntaxTree is same for current SemanticModel and SyntaxNode, if not, get a new SemanticModel
    via semanticModel.Compilation.GetSemanticModel(syntaxNode.SyntaxTree).

  • Fixed: The new enum CliNameAutoGenerate in v2.5.0 is a [Flags] enum and it was not translated to C# code correctly
    when multiple flags were combined:

    [CliCommand(
        ShortFormAutoGenerate = CliNameAutoGenerate.Options | CliNameAutoGenerate.Arguments
    )]
    public class RootHelpOnEmptyCliCommand
  • Improved: Added .cmd batch scripts to repository root for easier building:

    1. Build TestApp.cmd
    2. Build Nuget Package.cmd
    3.1. Build TestApp.Nuget.cmd
    3.2. Build TestApp.NugetDI.cmd
    3.3. Build TestApp.NugetAot.cmd
    4. Build Api Docs WebSite.cmd         

    Output results can be found in publish folder.

DotMake.CommandLine v2.5.0

24 Jun 16:16
Compare
Choose a tag to compare
  • Added: [CliDirective] attribute for properties to define custom directives:

    [CliCommand(Description = "A root cli command with directives")]
    public class DirectiveCliCommand
    {
        [CliDirective]
        public bool Debug { get; set; }
    
        [CliDirective]
        public string Directive2 { get; set; }
    
        [CliDirective]
        public string[] Vars { get; set; }
    
        public void Run(CliContext context)
        {
            if (context.IsEmpty())
                context.ShowHelp();
            else
            {
                Console.WriteLine($"Directive '{nameof(Debug)}' = {StringExtensions.FormatValue(Debug)}");
                Console.WriteLine($"Directive '{nameof(Directive2)}' = {StringExtensions.FormatValue(Directive2)}");
                Console.WriteLine($"Directive '{nameof(Vars)}' = {StringExtensions.FormatValue(Vars)}");
            }
        }
    }

    Currently only bool, string and string[] types are supported for [CliDirective] properties.
    Here is sample usage and output:

    src\TestApp\bin\Debug\net8.0>TestApp [debug] [directive-2:val1] [vars:val2] [vars:val3]
    Directive 'Debug' = true
    Directive 'Directive2' = "val1"
    Directive 'Vars' = ["val2", "val3"]
  • Improved: CliNameCasingConvention will now generate better CLI identifiers, i.e. it will treat unicode letter casing
    and unicode numbers in class and property names correctly. StringExtensions.ToCase() will handle strings that contain
    unicode space and punctuation characters correctly.

  • Improved: Smarter auto-generated short form aliases. In previous versions, only first letter was added as short form
    if it wasn't already used. Now letters of every word in the option name will be used to reduce conflicts.
    Also short form aliases will now also be generated for commands.

    public class ShortFormCliCommand
    {
            [CliOption(Alias = "o2")]
            public string Oauth2GrantType { get; set; } = "";
    
            [CliOption]
            public string Oauth2TokenUrl { get; set; } = "";
    
            [CliOption]
            public string Oauth2ClientId { get; set; } = "";
    
            [CliOption]
            public string Oauth2ClientSecret { get; set; } = "";
    
            [CliOption]
            public string Sha256 { get; set; } = "";
    }
    Options:
      -o2, --oauth-2-grant-type
      -o2tu, --oauth-2-token-url
      -o2ci, --oauth-2-client-id
      -o2cs, --oauth-2-client-secret
      -s256, --sha-256
  • Added: Alias property to CliCommand and CliOption attributes in addition to existing Aliases property.
    Alias property is more useful, as most of the time you will want to add a single alias which is usually a short form alias.
    When this property is set, it will override the auto-generated short form alias so when you are not happy with a specific
    auto-generated alias you can override it instead of disabling all via ShortFormAutoGenerate = false.

    When manually setting this property, if you don't specify a prefix, it will be prefixed automatically according
    to ShortFormPrefixConvention (e.g. -o or --o or /o) unless it's set to newly added CliNamePrefixConvention.None.
    This will now be also same for existing Aliases property but except, aliases without prefix will be prefixed automatically
    according to NamePrefixConvention instead.

  • Added: CliNameAutoGenerate enum and changed type of [CliCommand].ShortFormAutoGenerate from bool to this enum.
    Added new[CliCommand].NameAutoGenerate property with same enum.
    Auto-generated names can be disabled for all or specific CLI symbol types via [CliCommand].NameAutoGenerate.
    Auto-generated short form aliases can be disabled for all or specific CLI symbol types via [CliCommand].ShortFormAutoGenerate.

DotMake.CommandLine v2.4.3

17 Jun 00:01
Compare
Choose a tag to compare
  • Fixed: In previous version, using System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject caused IL2072 trimming warnings.
    We now changed handling of completions so that we never need an uninitialized instance of definition type, thus removed usage of GetUninitializedObject to fix the issue:

    • ICliAddCompletions interface is now renamed to ICliGetCompletions.
    • void AddCompletions(string propertyName, List<Func<CompletionContext, IEnumerable<CompletionItem>>> completionSources) interface method
      is now changed to IEnumerable<CompletionItem> GetCompletions(string propertyName, CompletionContext completionContext).

    So now, instead we will use Bind method to get cached definition instance and call GetCompletions interface method.
    The signature change also made implementing interface method more easy and straightforward.

DotMake.CommandLine v2.4.2

11 Jun 20:59
Compare
Choose a tag to compare
  • Updated to daily build 2.0.0-beta5.25302.104 of System.CommandLine (cannot update beyond this version for now, as Help related classes are made internal).

  • Improved: In previous versions, for setting DefaultValueFactory of options and arguments, a default instance of definition type was being created and its properties were being accessed.
    However if you used dependency injection, this caused unnecessary instantiations because every definition type in the hierarchy had to be instantiated for building the command hierarchy.
    From now on, CliCommandBuilder.Build method will not create a default instance of definition type to avoid IServiceProvider instantiations for other commands in the hierarchy.
    Instead, we will read the property initializer's SyntaxNode, fully-qualify symbols and then use that SyntaxNode directly for setting DefaultValueFactory.
    However, we still need an uninitialized instance for being able to call AddCompletions method that comes with ICliAddCompletions interface, for now.
    The uninitialized instance can not be used for property access, as the constructor is skipped, properties will come as null but it can be used for calling method access.
    Creating an uninitialized instance will not trigger dependency injection, so the problem will be avoided.

  • Fixed: Property initializers were being checked for SyntaxKind.NullKeyword where they should be checked for SyntaxKind.NullLiteralExpression instead
    to auto-determine Required = true when it's not specified explicitly.

DotMake.CommandLine v2.4.0

08 May 21:28
Compare
Choose a tag to compare
  • Updated to latest daily build 2.0.0-beta5.25257.101 of System.CommandLine.

  • Improved: The order of subcommands in the array passed to CliCommandAttribute.Children property should be respected (should not be reversed).
    So the help output and CliContext.ShowHierarchy should display the correct order of subcommands.

  • Improved: The order of inherited properties with [CliOption] or [CliArgument] attribute, should be respected.
    The definition order in the most derived class should be preserved. This is especially important for [CliArgument]
    properties because if a property is overriden in class, it should imply that the order is the overriden definition order.

DotMake.CommandLine v2.3.0

21 Apr 20:05
Compare
Choose a tag to compare
  • Added: Custom completions for options and arguments support. Normally completions were automatically added for options or arguments with Enum types.
    Now, you can add (or clear/modify) completions for any option or argument.
    In your command class, inherit ICliAddCompletions and implement AddCompletions method.
    This method will be called for every option and argument in your class.
    In the method, you should switch according to the property name
    which corresponds to the option or argument whose completions will be modified.

    using System;
    using System.Collections.Generic;
    using System.CommandLine;
    using System.CommandLine.Completions;
    using System.Linq;
    using DotMake.CommandLine;
    
    [CliCommand(Description = "A root cli command with completions for options and arguments")]
    public class AddCompletionsCliCommand : ICliAddCompletions
    {
        [CliOption(Description = "Description for DateOption")]
        public DateTime DateOption { get; set; }
    
        [CliArgument(Description = "Description for FruitArgument")]
        public string FruitArgument { get; set; } = "DefaultForFruitArgument";
    
        public void Run(CliContext context)
        {
            if (context.IsEmptyCommand())
                context.ShowHelp();
            else
                context.ShowValues();
        }
    
        public void AddCompletions(string propertyName, List<Func<CompletionContext, IEnumerable<CompletionItem>>> completionSources)
        {
            switch (propertyName)
            {
                case nameof(DateOption):
                    completionSources.Add(completionContext =>
                    {
                        var today = System.DateTime.Today;
                        var dates = new List<CompletionItem>();
                        foreach (var i in Enumerable.Range(1, 7))
                        {
                            var date = today.AddDays(i);
                            dates.Add(new CompletionItem(
                                label: date.ToShortDateString(),
                                sortText: $"{i:2}"));
                        }
                        return dates;
                    });
                    break;
    
                case nameof(FruitArgument):
                    completionSources.Add("apple", "orange", "banana");
                    break;
            }
        }
    }

    The dynamic tab completion list created by this code also appears in help output:

    DotMake Command-Line TestApp v2.3.0
    Copyright © 2023-2025 DotMake
    
    A root cli command with completions for options and arguments
    
    Usage:
      TestApp [<fruit>] [options]
    
    Arguments:
      <apple|banana|orange>  Description for FruitArgument [default: DefaultForFruitArgument]
    
    Options:
      -d, --date                                                  Description for DateOption [default: 1.01.0001 00:00:00]
      <22.04.2025|23.04.2025|24.04.2025|25.04.2025|26.04.2025|27
      .04.2025|28.04.2025>
      -?, -h, --help                                              Show help and usage information
      -v, --version                                               Show version information
  • Added: We provide new interfaces ICliRun, ICliRunWithReturn, ICliRunWithContext, ICliRunWithContextAndReturn
    and async versions ICliRunAsync, ICliRunAsyncWithReturn, ICliRunAsyncWithContext, ICliRunAsyncWithContextAndReturn
    that you can inherit in your command class.
    Normally you don't need an interface for a handler method as the source generator can detect it automatically,
    but the interfaces can be used to prevent your IDE complain about unused method in class.

    [CliCommand]
    public class RunAsyncWithReturnCliCommand : ICliRunAsyncWithReturn
    {
        public async Task<int> RunAsync()
        {
    
        }
    }
  • Fixed: ServiceProvider.Dispose() should be called in AppDomain.CurrentDomain.ProcessExit event, not at the end of the command execution.
    As Cli.Run may be called multiple time, for example for testing purpose.

  • Improved: CliReferenceDependantInput should be instantiated once per compilation change for performance optimization.
    In older versions, it was re-instantiated in CliCommandInput for non-nested commands.

DotMake.CommandLine v2.2.0

19 Apr 22:17
Compare
Choose a tag to compare
  • Updated to latest daily build 2.0.0-beta5.25218.1 of System.CommandLine.

  • Fixed: New CliCommandAttribute.Children property did not work across projects.

  • Improved: Calculating parent tree is hard (across projects) and expensive in generator so we removed
    Collect and Re-generate logic in CliIncrementalGenerator that was added in v2.1.0.
    We can already figure out whole command hierarchy at runtime.

    • for parent command accessors, we will only check if property type has CliCommand attribute.
    • for circular dependency, we will do some simple checks for types in Parent and Children attributes and class type itself.
      Full circular dependency can be detected at runtime.
  • Fixed: ServiceProvider.Dispose() should be called at the end of the command execution.

DotMake.CommandLine v2.1.0

10 Apr 15:11
Compare
Choose a tag to compare
  • Updated to latest daily build 2.0.0-beta5.25209.2 of System.CommandLine.

  • Improved: Redesigned the source generator. Separated code into Inputs and Outputs. This way collecting metadata (inputs)
    and generating source code (outputs) can be maintained more easily. For example, generating outputs for a different CLI library
    may be possible in the future. But the main reason for the redesign was to handle input cli command hierarchy in a better way.

  • Added: CliCommandAttribute.Children property. It's set to an array of types.
    This is similar to existing Parent property but in reverse. For example if you have a ConsoleAppParent and ConsoleAppChild
    projects and you have the root command in ConsoleAppParent, the external child commands in ConsoleAppChild had to reference
    ConsoleAppParent to that Parent property could refer to the root command. This compiled but did not work
    because ConsoleAppChild references ConsoleAppParent where logically it should be the opposite, i.e. ConsoleAppParent
    does not know about ConsoleAppChild so it can't find the external child commands inside it.
    For this reason, we added Children property so that ConsoleAppParent can reference ConsoleAppChild.

    /*
        Command hierarchy in below example is:
    
         TestApp
         └╴external-level-1
           └╴external-level-2
    */
    
    [CliCommand(
        Description = "A root cli command with external children",
        Children = new []
        {
            typeof(ExternalLevel1SubCliCommand)
        }
    )]
    public class RootWithExternalChildrenCliCommand
    {
        [CliOption(Description = "Description for Option1")]
        public string Option1 { get; set; } = "DefaultForOption1";
    
        [CliArgument(Description = "Description for Argument1")]
        public string Argument1 { get; set; } = "DefaultForArgument1";
    
        public void Run(CliContext context)
        {
            if (context.IsEmptyCommand())
                context.ShowHierarchy();
            else
                context.ShowValues();
        }
    }
    
    [CliCommand(
        Description = "An external level 1 sub-command",
        Children = new[]
        {
            typeof(ExternalLevel2SubCliCommand)
        }
    )]
    public class ExternalLevel1SubCliCommand
    {
        [CliOption(Description = "Description for Option1")]
        public string Option1 { get; set; } = "DefaultForOption1";
    
        [CliArgument(Description = "Description for Argument1")]
        public string Argument1 { get; set; }
    
        public void Run(CliContext context)
        {
            context.ShowValues();
        }
    }
    
    [CliCommand(Description = "An external level 2 sub-command")]
    public class ExternalLevel2SubCliCommand
    {
        [CliOption(Description = "Description for Option1")]
        public string Option1 { get; set; } = "DefaultForOption1";
    
        [CliArgument(Description = "Description for Argument1")]
        public string Argument1 { get; set; }
    
        public void Run(CliContext context)
        {
            context.ShowValues();
        }
    }
  • Added: CliContext.ShowHierarchy method which shows hierarchy for all commands.
    It will start from the root command and show a tree. Useful for testing a command.

     TestApp
     ├╴external-level-1-with-nested
     │ └╴level-2
     └╴level_1
       └╴external_level_2_with_nested
         └╴level_3