Cysharp / ConsoleAppFramework

Zero Dependency, Zero Overhead, Zero Reflection, Zero Allocation, AOT Safe CLI Framework powered by C# Source Generator.
MIT License
1.66k stars 95 forks source link

Unable to show version without a root command #151

Open patricktcoakley opened 6 days ago

patricktcoakley commented 6 days ago

Hi, my app currently doesn't have or need a root/"" command, and ideally it would just show help on no args and --version would still display the the version. Possibly related, but one of the commands I want to pass args to launch another process, and if I use a string with -- prefix it seems to take it in as a CLI arg. For example, if I run myapp command --version, where I would normally expect --version to be treated as a string passed into the command, it instead returns the --version command from ConsoleAppFramework.

neuecc commented 6 days ago

--version and --help are treated like reserved words and are processed with top priority within the framework. So, would it be good to have an option like bool DisableShowVersion?

patricktcoakley commented 6 days ago

So I actually want to use --version and not disable it, I am just wondering if there's a way that:

1) If I don't have a root command (something that is marked Command[("")]) that --version still works properly.

2) If there's a way to tell the parser that if something is marked as [Argument] that reserved keywords like --help or --version are ignored? It's not a big deal if it's not possible, but I would love to be able to call other processes with arguments passed.

Thank you, I think this library is super neat but can be a little confusing at times.

neuecc commented 6 days ago

For the execution order, since it goes: check --help and --version -> execute, you can specify it in Arguments and execute if '--version' is not included as a command line argument. However, I think you could simply avoid using 'version' as an argument name...

patricktcoakley commented 6 days ago

I am not using version as an argument. I am trying to pass arguments to another process and one of the parts of the string contained --version, like:

            Process.Start(new ProcessStartInfo
            {
                Arguments = args,
                FileName = execPath,
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                WorkingDirectory = workingDirectory
            });

Anyways, this isn't a big deal to me if --version is magic for now. I am more confused why I can't just do, say, dotnet run -- --version and get my version. Instead, I get help:

 dotnet run -- --version
Usage: [command] [-h|--help] [--version]

Commands:
...

I would expect the version flag to work, but it only seems to work when I run it with a command, like mycommand --version works:

 dotnet run -- search --version
0.1.0+261d2481addd705c1a2d3a50b632c01803073858

The expected behavior for me would be that the first command would show the version, and the second one to say "unknown flag --version" or something.

neuecc commented 6 days ago

I've checked but --version works.

using ConsoleAppFramework;

Console.WriteLine("args length:" + args.Length);
foreach (var item in args)
{
    Console.WriteLine(item);
}
Console.WriteLine("----------");

ConsoleApp.Run(args, (int x, int y) =>
{
    Console.WriteLine($"{x}, {y}");
});

image

The logic is very simple, as you can see.

    public static void Run(string[] args, Action<int, int> command)
    {
        if (TryShowHelpOrVersion(args, 2, -1)) return;
        // snip...
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static bool TryShowHelpOrVersion(ReadOnlySpan<string> args, int requiredParameterCount, int helpId)
    {
        if (args.Length == 0)
        {
            if (requiredParameterCount == 0) return false;

            ShowHelp(helpId);
            return true;
        }

        if (args.Length == 1)
        {
            switch (args[0])
            {
                case "--version":
                    ShowVersion();
                    return true;
                case "-h":
                case "--help":
                    ShowHelp(helpId);
                    return true;
                default:
                    break;
            }
        }

        return false;
    }
patricktcoakley commented 5 days ago

I am using the builder. So, for example:

var app = Create();
app.Run(args);

return 0;

seems to generate:

internal static partial class ConsoleApp
{
    partial struct ConsoleAppBuilder
    {

        partial void AddCore(string commandName, Delegate command)
        {
            switch (commandName)
            {
                default:
                    break;
            }
        }

        partial void RunCore(string[] args)
        {
            if (args.Length == 0)
            {
                ShowHelp(-1);
                return;
            }
            switch (args[0])
            {
                default:
                    ShowHelp(-1);
                    break;
            }
        }

    }
}

The code for just running ConsoleApp.Run with an empty lambda generates:

internal static partial class ConsoleApp
{
    /// <summary>
    /// Usage: [-h|--help] [--version]<br/>
    /// <br/>
    /// </summary>
    public static void Run(string[] args, Action command)
    {
        if (TryShowHelpOrVersion(args, 0, -1)) return;

        try
        {
            for (int i = 0; i < args.Length; i++)
            {
                var name = args[i];

                switch (name)
                {
                    default:
                        ThrowArgumentNameNotFound(name);
                        break;
                }
            }

            command();
        }
        catch (Exception ex)
        {
            Environment.ExitCode = 1;
            if (ex is ValidationException or ArgumentParseFailedException)
            {
                LogError(ex.Message);
            }
            else
            {
                LogError(ex.ToString());
            }
        }
    }
}

Maybe I'm misunderstanding something? I am currently using it like:

var app = Create();
app.Add<MyCommand1>();
...
app.Run(args);
neuecc commented 5 days ago

I don't really understand what you're trying to say. Instead of showing incomplete code snippets, please provide complete, 100% copy-and-paste reproducible code with explanations.

patricktcoakley commented 5 days ago

Okay

Program.cs

using ConsoleAppFramework;
using static ConsoleAppFramework.ConsoleApp;

namespace MyApp;

public class Program
{
    public static int Main(string[] args)
    {
        var app = Create();

        app.Add<MyCommand>();
        app.Run(args);

        return 0;
    }
}

public class MyCommand
{
    public void Echo([Argument] string msg)
    {
        Console.WriteLine(msg);
    }
}

It does not show the version when I just pass in --version, only if I call a command with --version, which makes no sense:

pt@Mac ~/R/G/G/(main)> dotnet run -- echo "stuff"
stuff
---
pt@Mac ~/R/G/G/(main)> dotnet run -- --version
Usage: [command] [-h|--help] [--version]

Commands:
  echo
---
pt@Mac ~/R/G/G/(main)> dotnet run -- echo --version
0.1.0+358812e4786051e4a2e4c7896cdced174c9d7da6

I don't know what else there is to show? If you don't understand or think it's expected behavior, feel free to close it then.