- introduce `sdt` subcommands for run, debug, setup, env, favorite, and explain - add project/workspace discovery plus config bootstrap and migration helpers - expand tests for CLI parsing, project role detection, and headless flows
95 lines
3.0 KiB
C#
95 lines
3.0 KiB
C#
namespace Sdt.Cli;
|
|
|
|
public static class CliParser
|
|
{
|
|
private static readonly HashSet<string> SubcommandRoots = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"workspace",
|
|
"bridge",
|
|
"setup",
|
|
"doctor",
|
|
"env",
|
|
"favorite",
|
|
"config",
|
|
};
|
|
|
|
public static CliInvocation Parse(IReadOnlyList<string> args)
|
|
{
|
|
if (args.Count == 0)
|
|
return new CliInvocation("interactive", null, [], new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase));
|
|
|
|
if (args.Any(a => string.Equals(a, "--plain", StringComparison.OrdinalIgnoreCase)))
|
|
ApplyPlainOutputMode();
|
|
|
|
if (args.Count == 1 &&
|
|
(string.Equals(args[0], "--help", StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(args[0], "-h", StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(args[0], "help", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
return new CliInvocation("help", null, [], new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase));
|
|
}
|
|
|
|
if (args.Count == 1 &&
|
|
(string.Equals(args[0], "init", StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(args[0], "--init", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
return new CliInvocation("init", null, [], new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase));
|
|
}
|
|
|
|
var command = args[0];
|
|
string? subcommand = null;
|
|
var optionStart = args.Count;
|
|
|
|
if (SubcommandRoots.Contains(command) &&
|
|
args.Count > 1 &&
|
|
!args[1].StartsWith("-", StringComparison.Ordinal))
|
|
{
|
|
subcommand = args[1];
|
|
optionStart = 2;
|
|
}
|
|
else
|
|
{
|
|
optionStart = 1;
|
|
}
|
|
|
|
var tail = args.Skip(optionStart).ToArray();
|
|
var options = ParseOptions(tail, out var positional);
|
|
|
|
return new CliInvocation(command, subcommand, positional, options);
|
|
}
|
|
|
|
private static Dictionary<string, string?> ParseOptions(string[] args, out List<string> positional)
|
|
{
|
|
var options = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
|
positional = [];
|
|
for (var i = 0; i < args.Length; i++)
|
|
{
|
|
var token = args[i];
|
|
if (!token.StartsWith("-", StringComparison.Ordinal))
|
|
{
|
|
positional.Add(token);
|
|
continue;
|
|
}
|
|
|
|
if (i + 1 < args.Length && !args[i + 1].StartsWith("-", StringComparison.Ordinal))
|
|
{
|
|
options[token] = args[i + 1];
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
options[token] = null;
|
|
}
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
private static void ApplyPlainOutputMode()
|
|
{
|
|
Environment.SetEnvironmentVariable("NO_COLOR", "1");
|
|
Environment.SetEnvironmentVariable("SDT_NO_COLOR", "1");
|
|
Environment.SetEnvironmentVariable("SDT_NO_UNICODE", "1");
|
|
}
|
|
}
|