Home

Awesome

Cmdr.Core

CircleCI Nuget (with prereleases)

Useful POSIX command line arguments parser for dotNet. Hierarchical configurations Store for app.

Supported:

NOTED .NET 5 has been ignored.

NuGet

PM> Install-Package HzNS.Cmdr.Core -Version 1.0.29
# Or CLI
$ dotnet add package HzNS.Cmdr.Core --version 1.0.29

Please replace 1.0.29 with the newest version (stable or pre-release), see the nuget badge icon.

Features

Cmdr.Core has rich features:

More

Option Store - Hierarchical Configurations Store

Standard primitive types and non-primitive types.

Get(), GetAs<T>()

Set<T>(), SetWithoutPrefix<T>()

Delete()

HasKeys(), HasKeysWithoutPrefix()

var exists = Cmdr.Instance.Store.HasKeys("tags.mode.s1.s2");
var exists = Cmdr.Instance.Store.HasKeys(new string[] { "tags", "mode", "s1", "s2" });
var exists = Cmdr.Instance.Store.HasKeysWithoutPrefix(new string[] { "app", "tags", "mode", "s1", "s2" });
Console.WriteLine(Cmdr.Instance.Store.Prefix);

FindBy()

var (slot, valueKey) = Cmdr.Instance.Store.FindBy("tags.mode.s1.s2");
if (slot != null){
  if (string.IsNullOrWhiteSpace(valueKey)) {
    // a child slot node matched
  } else {
    // a value entry matched, inside a slot node
  }
}

Walk()

GetAsMap()

return a SlotEntries map so that you can yaml it:

  // NOTE: Cmdr.Instance.Store == worker.OptionsStore
  var map = worker.OptionsStore.GetAsMap("tags.mode");
  // worker.log.Information("tag.mode => {OptionsMap}", map);
  {
      var serializer = new SerializerBuilder().Build();
      var yaml = serializer.Serialize(map);
      Console.WriteLine(yaml);
  }

CMDR EnvVars

CMDR_DUMP

enable Store entries dumping at the end of help screen.

CMDR_DUMP_NO_STORE, CMDR_DUMP_NO_HIT

To prevent the store dump, or hit options dump.

CMDR_DEBUG

= Worker.EnableCmdrLogDebug

allows the display output in defaultOnSet.

CMDR_TRACE

= Worker.EnableCmdrLogTrace

allows the worker logDebug().

CMDR_VERBOSE

allows more logging output.

Getting Start

Fluent API

Basically, the Main program looks like:

static int Main(string[] args) => 
  Cmdr.NewWorker(RootCommand.New(
    new AppInfo(),  // your app information, desc, ...
    buildRootCmd(), // to attach the sub-commands and options to the RootCommand
    workerOpts,     // to customize the Cmdr Worker
  ))
  .Run(args, postRun);

Your first app with Cmdr.Core could be:

<details> <summary> Expand to source codes </summary>
namespace Simple
{
    class Program
    {
        static int Main(string[] args) => Cmdr.NewWorker(

                #region RootCmd Definitions

                RootCommand.New(
                    new AppInfo
                    {
                        AppName = "tag-tool",
                        Author = "hedzr",
                        Copyright = "Copyright © Hedzr Studio, 2020. All Rights Reserved.",
                    },
                    (root) =>
                    {
                        root.Description = "description here";
                        root.DescriptionLong = "long description here";
                        root.Examples = "examples here";

                        // for "dz"
                        _a = 0;

                        root.AddCommand(new Command
                            {
                                Long = "dz", Short = "dz", Description = "test divide by zero",
                                Action = (worker, opt, remainArgs) => { Console.WriteLine($"{B / _a}"); },
                            })
                            .AddCommand(new Command {Short = "t", Long = "tags", Description = "tags operations"}
                                .AddCommand(new TagsAddCmd())
                                .AddCommand(new TagsRemoveCmd())
                                // .AddCommand(new TagsAddCmd { }) // for dup-test
                                .AddCommand(new TagsListCmd())
                                .AddCommand(new TagsModifyCmd())
                                .AddCommand(new TagsModeCmd())
                                .AddCommand(new TagsToggleCmd())
                                .AddFlag(new Flag<string>
                                {
                                    DefaultValue = "consul.ops.local",
                                    Long = "addr", Short = "a", Aliases = new[] {"address", "host"},
                                    Description = "Consul IP/Host and/or Port: HOST[:PORT] (No leading 'http(s)://')",
                                    PlaceHolder = "HOST[:PORT]",
                                    Group = "Consul",
                                })
                                .AddFlag(new Flag<string>
                                {
                                    DefaultValue = "",
                                    Long = "cacert", Short = "", Aliases = new string[] {"ca-cert"},
                                    Description = "Consul Client CA cert)",
                                    PlaceHolder = "FILE",
                                    Group = "Consul",
                                })
                                .AddFlag(new Flag<string>
                                {
                                    DefaultValue = "",
                                    Long = "cert", Short = "", Aliases = new string[] { },
                                    Description = "Consul Client Cert)",
                                    PlaceHolder = "FILE",
                                    Group = "Consul",
                                })
                                .AddFlag(new Flag<bool>
                                {
                                    DefaultValue = false,
                                    Long = "insecure", Short = "k", Aliases = new string[] { },
                                    Description = "Ignore TLS host verification",
                                    Group = "Consul",
                                })
                            );

                        root.OnSet = (worker, flag, oldValue, newValue) =>
                        {
                            if (worker.OptionStore.GetAs<bool>("quiet")) return;
                            if (Cmdr.Instance.Store.GetAs<bool>("verbose") &&
                                flag.Root?.FindFlag("verbose")?.HitCount > 1)
                                Console.WriteLine($"--> [{Cmdr.Instance.Store.GetAs<bool>("quiet")}][root.onSet] {flag} set: {oldValue?.ToStringEx()} -> {newValue?.ToStringEx()}");
                        };
                    }
                ), // <- RootCmd Definitions

                #endregion

                #region Options for Worker

                (w) =>
                {
                    //
                    // w.UseSerilog((configuration) => configuration.WriteTo.Console().CreateLogger())
                    //

                    // w.EnableCmdrGreedyLongFlag = true;
                    // w.EnableDuplicatedCharThrows = true;
                    // w.EnableEmptyLongFieldThrows = true;

                    w.RegisterExternalConfigurationsLoader(ExternalConfigLoader);
                    
                    w.OnDuplicatedCommandChar = (worker, command, isShort, matchingArg) => false;
                    w.OnDuplicatedFlagChar = (worker, command, flag, isShort, matchingArg) => false;
                    w.OnCommandCannotMatched = (parsedCommand, matchingArg) => false;
                    w.OnFlagCannotMatched = (parsingCommand, fragment, isShort, matchingArg) => false;
                    w.OnSuggestingForCommand = (worker, dataset, token) => false;
                    w.OnSuggestingForFlag = (worker, dataset, token) => false;
                }

                #endregion

            )
            .Run(args, () =>
            {
                // Postrun here
                
                // Wait for the user to quit the program.

                // Console.WriteLine($"         AssemblyVersion: {VersionUtil.AssemblyVersion}");
                // Console.WriteLine($"             FileVersion: {VersionUtil.FileVersion}");
                // Console.WriteLine($"    InformationalVersion: {VersionUtil.InformationalVersion}");
                // Console.WriteLine($"AssemblyProductAttribute: {VersionUtil.AssemblyProductAttribute}");
                // Console.WriteLine($"      FileProductVersion: {VersionUtil.FileVersionInfo.ProductVersion}");
                // Console.WriteLine();

                // Console.WriteLine("Press 'q' to quit the sample.");
                // while (Console.Read() != 'q')
                // {
                //     //
                // }

                return 0;
            });

        private static void ExternalConfigLoader(IBaseWorker w, IRootCommand root)
        {
            // throw new NotImplementedException();
        }

        
        private static int _a = 9;
        private const int B = 10;
    }
}
</details>

Declarative API

Since v1.0.139, we added the declarative API for compatibility with some others command-line argument parser libraries.

A sample at: SimpleAttrs.

The codes might be:

<details> <summary> Expand to source codes </summary>
class Program
{
    static int Main(string[] args) => Cmdr.Compile<SampleAttrApp>(args);
}

[CmdrAppInfo(appName: "SimpleAttrs", author: "hedzr", copyright: "copyright")]
public class SampleAttrApp
{
    [CmdrOption(longName: "count", shortName: "c", "cnt")]
    [CmdrDescriptions(description: "a counter", descriptionLong: "", examples: "")]
    [CmdrRange(min: 0, max: 10)]
    [CmdrRequired]
    public int Count { get; }

    [CmdrCommand(longName: "tags", shortName: "t")]
    [CmdrGroup(@group: "")]
    [CmdrDescriptions(description: "tags operations")]
    public class TagsCmd
    {
        [CmdrCommand(longName: "mode", shortName: "m")]
        [CmdrDescriptions(description: "set tags' mode", descriptionLong: "", examples: "")]
        public class ModeCmd
        {
            [CmdrAction]
            public void Execute(IBaseWorker w, IBaseOpt cmd, IEnumerable<string> remainArgs)
            {
                Console.WriteLine($"Hit: {cmd}, Remains: {remainArgs}. Count: {Cmdr.Instance.Store.GetAs<int>(key: "count")}");
             }

            [CmdrOption(longName: "count2", shortName: "c2", "cnt2")]
            [CmdrDescriptions(description: "a counter", descriptionLong: "", examples: "", placeHolder: "COUNT")]
            public int Count { get; }

            [CmdrOption(longName: "ok", shortName: "ok")]
            [CmdrDescriptions(description: "boolean option", descriptionLong: "", examples: "")]
            [CmdrHidden]
            public bool OK { get; }
            
            [CmdrOption(longName: "addr", shortName: "a", "address")]
            [CmdrDescriptions(description: "string option", descriptionLong: "", examples: "", placeHolder: "HOST[:PORT]")]
            public string Address { get; }
        }
    }
}
</details>

Logger

The external logger has been removed from Cmdr.Core.

But you can always enable one or customize yours. In the HzNS.Cmdr.Logger.Serilog package/project, we've given an implements and it's simple to use:

  1. Add HzNS.Cmdr.Logger.Serilog at first:
dotnet add package HzNS.Cmdr.Logger.Serilog --version 1.0.6
  1. Modify the program entry:
    Cmdr.NewWorker(RootCommand.New(new AppInfo {AppName = "mdxTool", AppVersion = "1.0.0"}, (root) =>
            {
                root.AddCommand(new Command {Short = "t", Long = "tags", Description = "tags operations"});
            }), // <- RootCmd
            // Options ->
            (w) =>
            {
                w.SetLogger(HzNS.Cmdr.Logger.Serilog.SerilogBuilder.Build((logger) =>
                {
                    // These following flags will be loaded from envvars such as '$CMDR_TRACE', ...
                    // logger.EnableCmdrLogInfo = false;
                    // logger.EnableCmdrLogTrace = false;
                }));

                // w.EnableDuplicatedCharThrows = true;
            })
        .Run(args);

ACKNOWNLEDGES

Colorify

I have to copy some codes from Colorify for the dotnetcore devenv.

There's some reason. But I will be pleasure to re-integrate the original or put an issue later (soon).

Thanks to JODL

JODL (JetBrains OpenSource Development License) is good:

rider jetbrains

LICENSE

MIT