HzNS.Cmdr.Core 1.3.4-g5eb0b81a3e

This is a prerelease version of HzNS.Cmdr.Core.
dotnet add package HzNS.Cmdr.Core --version 1.3.4-g5eb0b81a3e
NuGet\Install-Package HzNS.Cmdr.Core -Version 1.3.4-g5eb0b81a3e
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="HzNS.Cmdr.Core" Version="1.3.4-g5eb0b81a3e" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add HzNS.Cmdr.Core --version 1.3.4-g5eb0b81a3e
#r "nuget: HzNS.Cmdr.Core, 1.3.4-g5eb0b81a3e"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install HzNS.Cmdr.Core as a Cake Addin
#addin nuget:?package=HzNS.Cmdr.Core&version=1.3.4-g5eb0b81a3e&prerelease

// Install HzNS.Cmdr.Core as a Cake Tool
#tool nuget:?package=HzNS.Cmdr.Core&version=1.3.4-g5eb0b81a3e&prerelease

Cmdr.Core

CircleCI Nuget (with prereleases)

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

Supported:

  • dotNet 6 (since v1.3+)
  • dotNet Core 3.1 (since v1.1+)
  • dotNet Standard 2.1+ (since v1.1+)
  • dotNet 4.8+ [?] (NOT SURE)

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:

  • POSIX Compatible (Unix getopt(3))
  • IEEE Standard Compartiblities
  • builds multi-level command and sub-commands
  • builds short, long and alias options with kinds of data types
  • defines commands and options via fluent api style
  • full featured Options Store for hosting any application configurations
    • watchable external config file and child directory conf.d.
    • watchable option value merging event: while option value modified in external config file, it'll be loaded and merged automatically.
    • watchable option value modifying event: while option value modified (from config file, or programmatically)
    • connectable with external configuration-center

More

  • Unix getopt(3) representation but without its programmatic interface.

    • Options with short names (-h)
    • Options with long names (--help)
    • Options with aliases (--helpme, --usage, --info)
    • Options with and without arguments (bool v.s. other type)
    • Options with optional arguments and default values
    • Multiple option groups each containing a set of options
    • Supports the compat short options -aux == -a -u -x, -vvv == -v -v -v (HitCount=3)
    • Supports namespaces for (nested) option groups see also: option store and hierarchical data
  • Supports for -D+, -D- to enable/disable a bool option.

  • Supports for PassThrough by --. (Passing remaining command line arguments after -- (optional))

  • Automatic help screen generation (Generates and prints well-formatted help message)

  • Predefined commands and flags:

    • Help: -h, -?, --help, --info, --usage, --helpme, ...
    • Version & Build Info: --version/--ver/-V, --build-info/-#
      • Simulating version at runtime with —version-sim 1.9.1
      • generally, conf.AppName and conf.Version are originally.
      • --tree: list all commands and sub-commands.
      • --config <location>: specify the location of the root config file.
      • version command available.
    • Verbose & Debug: —verbose/-v, —debug/-D, —quiet/-q
  • Sortable commands and options/flags: sorted by alphabetic order or not (worker.SortByAlphabeticAscending).

  • Grouped commands and options/flags.

    Group Title may have a non-displayable prefix for sorting, separated by '.'.

    Sortable group name can be [0-9A-Za-z]+\..+ format typically, or any string tailed '.', eg:

    • 1001.c++, 1100.golang, 1200.java, …;
    • abcd.c++, b999.golang, zzzz.java, …;
  • Supports for unlimited multi-level sub-commands.

  • Overrides by environment variables.

    priority level: defaultValue -> config-file -> env-var -> command-line opts

  • Option Store - Unify option value extraction.

  • Walkable

    • Customizable Painter interface to loop each command and flag.
    • Walks on all commands with Walk(from, commandWalker, flagWalker).
  • Supports -I/usr/include -I=/usr/include -I /usr/include -I:/usr option argument specifications Automatically allows those formats (applied to long option too):

    • -I file, -Ifile, and -I=files
    • -I 'file', -I'file', and -I='files'
    • -I "file", -I"file", and -I="files"
  • Supports for PassThrough by --. (Passing remaining command line arguments after -- (optional))

  • Predefined external config file locations:

    • /etc/<appname>/<appname>.yml and conf.d sub-directory.

    • /usr/local/etc/<appname>/<appname>.yml and conf.d sub-directory.

    • $HOME/.config/<appname>/<appname>.yml and conf.d sub-directory.

    • $HOME/.<appname>/<appname>.yml and conf.d sub-directory.

    • the predefined locations are:

      predefinedLocations: []string{
      	"./ci/etc/%s/%s.yml",       // for developer
      	"/etc/%s/%s.yml",           // regular location: /etc/$APPNAME/$APPNAME.yml
      	"/usr/local/etc/%s/%s.yml", // regular macOS HomeBrew location
      	"$HOME/.config/%s/%s.yml",  // per user: $HOME/.config/$APPNAME/$APPNAME.yml
      	"$HOME/.%s/%s.yml",         // ext location per user
      	"$THIS/%s.yml",             // executable's directory
      	"%s.yml",                   // current directory
      },
      
    • Watch conf.d directory, the name is customizable (worker.).

    • RegisterExternalConfigurationsLoader(loader, ...)

  • Handlers

    • Global Handlers: RootCommand.OnPre/Post/Action(), OnSet() will be triggered before/after the concrete Command.OnPre/Post/Action()/OnSet()
    • Command Actions: Command.OnPreAction/OnAction/OnPostAction(), OnSet
    • Flag Actions: Flag.OnPreAction/OnAction/OnPostAction(), OnSet
    • Parsing Events:
      • bool OnDuplicatedCommandChar(worker, cmd, isShort, matchingString)
      • bool OnDuplicatedFlagChar(worker, cmd, flag, isShort, matchingString)
      • bool OnCommandCannotMatched(ICommand parsedCommand, string matchingArg)
      • bool OnCommandCannotMatched(ICommand parsingCommand, string fragment, bool isShort, string matchingArg)
      • bool OnSuggestingForCommand(object worker, Dictionary&lt;string, ICommand&gt; dataset, string token)
      • bool OnSuggestingForFlag(object worker, Dictionary&lt;string, IFlag&gt; dataset, string token)
      • ...
    • More...
  • Unhandled Exception cmdr handled AppDomain.CurrentDomain.UnhandledException for better display. But you can override it always:

    static int Main(string[] args) {
        AppDomain.CurrentDomain.UnhandledException+=(sender,e)=>{};
        Cmdr.NewWorker(...).Run();
    }
    
  • Smart suggestions for wrong command and flags

    based on Jaro-Winkler distance. alternate text is missing from this package README image

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

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 is compatible. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.3.4-g5eb0b81a3e 122 6/15/2022
1.1.3 693 11/11/2020
1.1.2 482 6/8/2020
1.0.146 498 6/8/2020
1.0.145-gb53776570d 333 6/8/2020
1.0.144-gbcca428ec3 341 6/8/2020
1.0.139 484 4/13/2020
1.0.138-ge0a865dfe3 323 4/13/2020
1.0.124-g0c50e314cd 309 4/12/2020
1.0.120 524 4/11/2020
1.0.117-gc74c082907 388 4/11/2020
1.0.112 539 4/9/2020
1.0.111-g0e529ad1f5 342 4/9/2020
1.0.105 503 4/8/2020
1.0.99-g568c5719a9 310 4/5/2020
1.0.97 665 4/4/2020
1.0.96 584 4/4/2020
1.0.94 598 4/4/2020
1.0.92 554 4/3/2020
1.0.91 488 4/2/2020
1.0.87 459 4/2/2020
1.0.86-gc2c000f122 315 4/2/2020
1.0.85-g0c26c61d7f 311 4/2/2020
1.0.84-g62e9e16210 306 4/2/2020
1.0.79-g197f75da87 340 4/2/2020
1.0.77-gfed15a9e33 321 4/2/2020
1.0.76-g7b8ff618d6 323 4/2/2020
1.0.48 507 4/1/2020
1.0.38 488 3/30/2020
1.0.29 586 3/30/2020

Command-line argument POSIX style parser.

           The first final version v1.1 released.
           - The test cases are passed.

           > NOTE: full symbols and sources debug info in pre-release version (v1.x.x-g#hash), if u can't step into with the normal release.

           See also:
           - project site: https://github.com/hedzr/Cmdr.Core
           - release note: https://github.com/hedzr/Cmdr.Core/blob/master/RELDOCS.md