fix core dump on export option

This commit is contained in:
2026-05-20 11:26:44 +02:00
parent 4874bf096c
commit 76064fa822
3 changed files with 59 additions and 60 deletions
Binary file not shown.
+28 -43
View File
@@ -10,9 +10,16 @@ namespace HanaTui.Tui;
/// </summary> /// </summary>
public static class KeySelectionScreen public static class KeySelectionScreen
{ {
// Sentinel values for the non-key choices private sealed class KeyChoice
private const string ManualEntry = "__MANUAL__"; {
private const string ExitChoice = "__EXIT__"; public string Display { get; init; } = "";
public string? KeyName { get; init; } // null = exit, "" = manual entry
public static readonly KeyChoice Manual = new() { Display = "[ Enter key name manually ]", KeyName = "" };
public static readonly KeyChoice Exit = new() { Display = "[ Exit ]", KeyName = null };
public override string ToString() => Display;
}
public static string? Run() public static string? Run()
{ {
@@ -30,14 +37,11 @@ public static class KeySelectionScreen
return null; return null;
} }
// Show client path for reference
var clientDir = HdbClientLocator.ClientDirectory; var clientDir = HdbClientLocator.ClientDirectory;
if (clientDir is not null) if (clientDir is not null)
AnsiConsole.MarkupLine($"[dim]HDB client: {Markup.Escape(clientDir)}[/]\n"); AnsiConsole.MarkupLine($"[dim]HDB client: {Markup.Escape(clientDir)}[/]\n");
// Load keys with a spinner
List<HdbUserstoreKey> keys = []; List<HdbUserstoreKey> keys = [];
AnsiConsole.Status() AnsiConsole.Status()
.Spinner(Spinner.Known.Dots) .Spinner(Spinner.Known.Dots)
.SpinnerStyle(Style.Parse("blue")) .SpinnerStyle(Style.Parse("blue"))
@@ -52,52 +56,34 @@ public static class KeySelectionScreen
AnsiConsole.MarkupLine("You can still enter a key name manually.\n"); AnsiConsole.MarkupLine("You can still enter a key name manually.\n");
} }
// Build a plain-string list where the value IS the key name (or sentinel). // Build typed choices. Display is plain text — never parsed as markup.
// We use SelectionPrompt<string> with a display converter so Spectre renders var choices = keys
// the label as markup but we always get back the plain key name. .Select(k => new KeyChoice { Display = BuildKeyDisplay(k), KeyName = k.Name })
var choiceNames = new List<string>(); .ToList();
var displayMap = new Dictionary<string, string>(); // name -> display label choices.Add(KeyChoice.Manual);
choices.Add(KeyChoice.Exit);
foreach (var k in keys) var prompt = new SelectionPrompt<KeyChoice>()
{
choiceNames.Add(k.Name);
displayMap[k.Name] = BuildKeyDisplay(k);
}
choiceNames.Add(ManualEntry);
displayMap[ManualEntry] = "[ Enter key name manually ]";
choiceNames.Add(ExitChoice);
displayMap[ExitChoice] = "[ Exit ]";
var prompt = new SelectionPrompt<string>()
.Title("[bold]Select HDBUSERSTORE key:[/]") .Title("[bold]Select HDBUSERSTORE key:[/]")
.PageSize(15) .PageSize(15)
.HighlightStyle(Style.Parse("bold dodgerblue1")) .HighlightStyle(Style.Parse("bold dodgerblue1"))
.UseConverter(name => displayMap.TryGetValue(name, out var label) .UseConverter(c => Markup.Escape(c.Display))
? Markup.Escape(label) .AddChoices(choices);
: Markup.Escape(name))
.AddChoices(choiceNames);
var selected = AnsiConsole.Prompt(prompt); var selected = AnsiConsole.Prompt(prompt);
if (selected == ExitChoice) if (selected.KeyName is null)
return null; return null; // Exit
if (selected == ManualEntry) if (selected.KeyName == "")
{ {
var manual = AnsiConsole.Ask<string>("[bold]Enter HDBUSERSTORE key name:[/]").Trim(); var manual = AnsiConsole.Ask<string>("[bold]Enter HDBUSERSTORE key name:[/]").Trim();
return string.IsNullOrWhiteSpace(manual) ? null : manual; return string.IsNullOrWhiteSpace(manual) ? null : manual;
} }
// selected is already the plain key name return selected.KeyName;
return selected;
} }
/// <summary>
/// Builds a plain-text display label (no markup) for a key.
/// Spectre's UseConverter renders the returned string as plain text.
/// </summary>
private static string BuildKeyDisplay(HdbUserstoreKey k) private static string BuildKeyDisplay(HdbUserstoreKey k)
{ {
var sb = new SysText.StringBuilder(k.Name); var sb = new SysText.StringBuilder(k.Name);
@@ -111,12 +97,11 @@ public static class KeySelectionScreen
sb.Append(':'); sb.Append(':');
sb.Append(k.Port); sb.Append(k.Port);
} }
} if (!string.IsNullOrEmpty(k.Tenant))
{
if (!string.IsNullOrEmpty(k.Tenant)) sb.Append('@');
{ sb.Append(k.Tenant);
sb.Append('@'); }
sb.Append(k.Tenant);
} }
if (!string.IsNullOrEmpty(k.User)) if (!string.IsNullOrEmpty(k.User))
+31 -17
View File
@@ -14,6 +14,21 @@ public static class OperationForms
// Shared helpers // Shared helpers
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/// <summary>
/// A choice item that keeps the real value separate from the display label.
/// The display label is pre-escaped plain text — never interpreted as markup.
/// </summary>
private sealed class SchemaChoice
{
public string Display { get; init; } = "";
public string? Value { get; init; } // null = cancel, "" = manual entry
public static readonly SchemaChoice Manual = new() { Display = "[ Enter manually ]", Value = "" };
public static readonly SchemaChoice Cancel = new() { Display = "[ Cancel ]", Value = null };
public override string ToString() => Display;
}
/// <summary> /// <summary>
/// Fetches the schema list with a spinner, then shows an arrow-key picker. /// Fetches the schema list with a spinner, then shows an arrow-key picker.
/// Returns the selected schema, or null if cancelled. /// Returns the selected schema, or null if cancelled.
@@ -38,36 +53,35 @@ public static class OperationForms
if (error is not null) if (error is not null)
AnsiConsole.MarkupLine($"[yellow][[WARN]] Could not fetch schemas: {Markup.Escape(error)}[/]"); AnsiConsole.MarkupLine($"[yellow][[WARN]] Could not fetch schemas: {Markup.Escape(error)}[/]");
// Use sentinel strings as values so no markup leaks into choice labels // Build typed choices. Display is plain text; ToString() is what SelectionPrompt shows.
const string manualSentinel = "__MANUAL__"; var choices = schemas
const string cancelSentinel = "__CANCEL__"; .Select(s => new SchemaChoice { Display = s, Value = s })
.ToList();
choices.Add(SchemaChoice.Manual);
choices.Add(SchemaChoice.Cancel);
var choiceValues = new List<string>(schemas) { manualSentinel, cancelSentinel }; // UseConverter returns Display which is already plain text.
// We also set the prompt to NOT interpret converter output as markup by
var prompt = new SelectionPrompt<string>() // escaping it — belt-and-suspenders.
var prompt = new SelectionPrompt<SchemaChoice>()
.Title($"[bold]{Markup.Escape(title)}[/]") .Title($"[bold]{Markup.Escape(title)}[/]")
.PageSize(15) .PageSize(15)
.HighlightStyle(Style.Parse("bold dodgerblue1")) .HighlightStyle(Style.Parse("bold dodgerblue1"))
.UseConverter(v => v switch .UseConverter(c => Markup.Escape(c.Display))
{ .AddChoices(choices);
manualSentinel => "[ Enter manually ]",
cancelSentinel => "[ Cancel ]",
_ => Markup.Escape(v),
})
.AddChoices(choiceValues);
var selected = AnsiConsole.Prompt(prompt); var selected = AnsiConsole.Prompt(prompt);
if (selected == cancelSentinel) if (selected.Value is null)
return null; return null; // Cancel
if (selected == manualSentinel) if (selected.Value == "")
{ {
var manual = AnsiConsole.Ask<string>("Enter schema name:").Trim(); var manual = AnsiConsole.Ask<string>("Enter schema name:").Trim();
return string.IsNullOrWhiteSpace(manual) ? null : manual; return string.IsNullOrWhiteSpace(manual) ? null : manual;
} }
return selected.Trim(); return selected.Value;
} }
private static int PickThreads(string label = "Number of threads") private static int PickThreads(string label = "Number of threads")