SDT/Cli/CliParser.cs
stan44 d5a74be368 Add guided CLI workflows and config commands
- 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
2026-03-29 22:22:48 -05:00

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");
}
}