first commit

This commit is contained in:
2026-03-02 20:53:28 +01:00
commit d27c205106
63 changed files with 4593 additions and 0 deletions

125
Cli/CliArgs.cs Normal file
View File

@@ -0,0 +1,125 @@
namespace HanaToolbox.Cli;
/// <summary>
/// Parsed result from the CLI argument array.
/// Lightweight, AOT-safe, zero external dependencies.
/// </summary>
public sealed class CliArgs
{
public string Command { get; init; } = string.Empty; // e.g. "backup", "cron"
public string SubCommand { get; init; } = string.Empty; // e.g. "setup" for "cron setup"
public List<string> Positional { get; init; } = []; // non-flag arguments
public bool Verbose { get; init; }
public string Sid { get; init; } = string.Empty;
public bool Compress { get; init; }
public int Threads { get; init; }
public bool Replace { get; init; }
/// <summary>
/// Parses raw args into a CliArgs instance.
///
/// Global options: -v | --verbose
/// --sid &lt;SID&gt;
///
/// Command-local options are also parsed here for simplicity (they are
/// silently ignored when not applicable to the active command).
///
/// Usage examples:
/// hanatoolbox backup --verbose --sid NDB
/// hanatoolbox export MYSCHEMA /hana/backup -c -t 4
/// hanatoolbox import-rename SRC DST /path --replace
/// hanatoolbox cron setup
/// </summary>
public static CliArgs Parse(string[] args)
{
var positional = new List<string>();
var verbose = false;
var sid = string.Empty;
var compress = false;
var threads = 0;
var replace = false;
var command = string.Empty;
var subCommand = string.Empty;
int i = 0;
// First token that is not a flag is the command
// second non-flag token after that may be the subcommand (for "cron setup")
while (i < args.Length)
{
var a = args[i];
switch (a)
{
case "-v" or "--verbose":
verbose = true;
i++;
break;
case "--sid" when i + 1 < args.Length:
sid = args[++i];
i++;
break;
case "-c" or "--compress":
compress = true;
i++;
break;
case "--replace":
replace = true;
i++;
break;
case "-t" or "--threads" when i + 1 < args.Length:
int.TryParse(args[++i], out threads);
i++;
break;
default:
if (!a.StartsWith('-'))
{
if (string.IsNullOrEmpty(command))
command = a;
else if (string.IsNullOrEmpty(subCommand) && positional.Count == 0)
// Peek: is this a known subcommand for the current command?
subCommand = MaybeSubCommand(command, a) ? a : AddPositional(positional, a);
else
positional.Add(a);
}
// Unknown flag — silently skip
i++;
break;
}
}
return new CliArgs
{
Command = command,
SubCommand = subCommand,
Positional = positional,
Verbose = verbose,
Sid = sid,
Compress = compress,
Threads = threads,
Replace = replace,
};
}
// ── Helpers ───────────────────────────────────────────────────────────────
private static bool MaybeSubCommand(string command, string token) =>
command == "cron" && token == "setup";
private static string AddPositional(List<string> list, string value)
{
list.Add(value);
return string.Empty;
}
/// <summary>Gets positional arg at index, or empty string if not present.</summary>
public string Pos(int index) =>
index < Positional.Count ? Positional[index] : string.Empty;
}