Closed poizan42 closed 8 months ago
How about this?
[CliCommand(Parent = typeof(RootCommand))]
internal class FooCommand
{
[CliArgument()]
public string? FooBar { get; set; }
public async Task RunAsync(CliContext context)
{
var parent = context.ParseResult.Bind<RootCommand>();
await Console.Out.WriteLineAsync($"Frob = {parent.Frob}, FooBar = {FooBar}");
}
}
Seems to work and is less hacky I guess. Still it's creating a new instance of RootCommand rather than giving me the one it already constructed.
using System.CommandLine;
using DotMake.CommandLine;
namespace DotMakeCommandLineTest;
[CliCommand]
internal class RootCommand
{
private static int LastId = -1;
public RootCommand()
{
id = Interlocked.Increment(ref LastId);
Console.WriteLine($"RootCommand {id} constructed");
}
private int id;
public int GetId() => id;
[CliOption(Aliases = ["-f"], Recursive = true)]
public bool Frob { get; set; }
}
[CliCommand(Parent = typeof(RootCommand))]
internal class FooCommand
{
[CliArgument()]
public string? FooBar { get; set; }
public async Task RunAsync(CliContext context)
{
var parent = context.ParseResult.Bind<RootCommand>();
var frob = parent.Frob;
await Console.Out.WriteLineAsync($"Frob = {frob}, FooBar = {FooBar}, RootCommand.id = {parent.GetId()}");
}
}
public class Program
{
public static async Task Main()
{
await Cli.RunAsync<RootCommand>("--frob foo BAZ");
await Cli.RunAsync<RootCommand>("foo --frob BAZ2");
await Cli.RunAsync<RootCommand>("foo BAZ3 --frob");
await Cli.RunAsync<RootCommand>("foo BAZ4");
await Cli.RunAsync<RootCommand>("-f foo BAZ");
await Cli.RunAsync<RootCommand>("foo -f BAZ2");
await Cli.RunAsync<RootCommand>("foo BAZ3 -f");
await Cli.RunAsync<RootCommand>("foo BAZ4");
}
}
Output:
RootCommand 0 constructed
RootCommand 1 constructed
Frob = True, FooBar = BAZ, RootCommand.id = 1
RootCommand 2 constructed
RootCommand 3 constructed
Frob = True, FooBar = BAZ2, RootCommand.id = 3
RootCommand 4 constructed
RootCommand 5 constructed
Frob = True, FooBar = BAZ3, RootCommand.id = 5
RootCommand 6 constructed
RootCommand 7 constructed
Frob = False, FooBar = BAZ4, RootCommand.id = 7
RootCommand 8 constructed
RootCommand 9 constructed
Frob = True, FooBar = BAZ, RootCommand.id = 9
RootCommand 10 constructed
RootCommand 11 constructed
Frob = True, FooBar = BAZ2, RootCommand.id = 11
RootCommand 12 constructed
RootCommand 13 constructed
Frob = True, FooBar = BAZ3, RootCommand.id = 13
RootCommand 14 constructed
RootCommand 15 constructed
Frob = False, FooBar = BAZ4, RootCommand.id = 15
New instance of RootCommand is due to creating an instance for default values, it's not due to parsing. We may find a better way in future but object creation is cheap any way.
Actually a neater way exists, you put your common option in a base class (you don't use a recursive option) and you use inheritance:
using System.CommandLine;
using DotMake.CommandLine;
namespace DotMakeCommandLineTest;
internal abstract class CommandBase
{
[CliOption(Aliases = ["-f"])]
public bool Frob { get; set; }
}
[CliCommand]
internal class RootCommand : CommandBase
{
}
[CliCommand(Parent = typeof(RootCommand))]
internal class FooCommand : CommandBase
{
[CliArgument()]
public string? FooBar { get; set; }
public async Task RunAsync(CliContext context)
{
await Console.Out.WriteLineAsync($"Frob = {Frob}, FooBar = {FooBar}");
}
}
This does not quite work. Frob becomes a common option but not a global option. There are now two distinct versions of the frob option instead, one on RootCommand and one on FooCommand.
using System.CommandLine;
using DotMake.CommandLine;
namespace DotMakeCommandLineTest;
internal abstract class CommandBase
{
[CliOption(Aliases = ["-f"])]
public bool Frob { get; set; }
}
[CliCommand]
internal class RootCommand : CommandBase
{
}
[CliCommand(Parent = typeof(RootCommand))]
internal class FooCommand : RootCommand
{
[CliArgument()]
public string? FooBar { get; set; }
public async Task RunAsync(CliContext context)
{
await Console.Out.WriteLineAsync($"Frob = {Frob}, FooBar = {FooBar}");
}
}
public class Program
{
public static async Task Main()
{
await Cli.RunAsync<RootCommand>("--frob foo BAZ"); // Fail
await Cli.RunAsync<RootCommand>("foo --frob BAZ2");
await Cli.RunAsync<RootCommand>("foo BAZ3 --frob");
await Cli.RunAsync<RootCommand>("foo BAZ4");
await Cli.RunAsync<RootCommand>("-f foo BAZ"); // Fail
await Cli.RunAsync<RootCommand>("foo -f BAZ2");
await Cli.RunAsync<RootCommand>("foo BAZ3 -f");
await Cli.RunAsync<RootCommand>("foo BAZ4");
}
}
Output:
Frob = False, FooBar = BAZ
Frob = True, FooBar = BAZ2
Frob = True, FooBar = BAZ3
Frob = False, FooBar = BAZ4
Frob = False, FooBar = BAZ
Frob = True, FooBar = BAZ2
Frob = True, FooBar = BAZ3
Frob = False, FooBar = BAZ4
I have a global CliOption (i.e. Recursive = true) that I want to get the value of from a sub-command. However I can't seem to find a way to get access to the root command instance from the sub-command. Trying to add it as an argument to Run/RunAsync causes the method to not be recognized. Right now the best I can do is a hack like this