using System.Text; namespace NpgsqlRestClient; public class Out { public void NL() => Console.WriteLine(); public void Logo() { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine(@" _ __ __ ____ __ / | / /___ ____ ________/ / / __ \___ _____/ /_ / |/ / __ \/ __ `/ ___/ __ \/ /_/ / _ \/ ___/ __/ / /| / /_/ / /_/ (__ ) /_/ / _, _/ __(__ ) /_ /_/ |_/ .___/\__, /____/\__, /_/ |_|\___/____/\__/ /_/ /____/ /_/ "); Console.ResetColor(); } public void Line(string line, ConsoleColor? color = null) { if (color is not null) { Console.ForegroundColor = color.Value; } Console.WriteLine(line); if (color is not null) { Console.ResetColor(); } } public void Write(string line, ConsoleColor? color = null) { if (color is not null) { Console.ForegroundColor = color.Value; } Console.Write(line); if (color is not null) { Console.ResetColor(); } } /// /// Write a line wrapped in a raw ANSI SGR sequence (e.g. "\x1b[38;5;197m" for a 256-color foreground), /// used when a specific color is needed that the 16-color API can't express. /// Emits plain text (no escapes) when output is redirected, so piped/CI logs stay clean. /// public void LineAnsi(string line, string ansiSgr) { if (Console.IsOutputRedirected) { Console.WriteLine(line); return; } Console.Write(ansiSgr); Console.Write(line); Console.WriteLine("\x1b[0m"); } /// /// Write a line starting with a colored "chip" (the label rendered in its own SGR — e.g. white text on /// a red background, like Serilog's ERR level chip) followed by the rest of the line, optionally in /// another SGR. Emits plain text when output is redirected. /// public void LineChip(string chip, string chipSgr, string rest, string? restSgr = null) { if (Console.IsOutputRedirected) { Console.WriteLine(string.Concat(chip, rest)); return; } Console.Write(chipSgr); Console.Write(chip); Console.Write("\x1b[0m"); if (restSgr is not null) { Console.Write(restSgr); } Console.Write(rest); Console.WriteLine("\x1b[0m"); } public void JsonHighlight(string json) { if (Console.IsOutputRedirected) { Console.WriteLine(json); return; } foreach (var line in json.Split('\n')) { var trimmed = line.AsSpan().TrimStart(); // leading whitespace if (trimmed.Length < line.Length) { Console.Write(line.AsSpan(0, line.Length - trimmed.Length)); } if (trimmed.IsEmpty) { Console.WriteLine(); continue; } // JSONC comments if (trimmed.StartsWith("//")) { Write(trimmed.ToString(), ConsoleColor.DarkGreen); Console.WriteLine(); continue; } // "key": value if (trimmed.Length > 0 && trimmed[0] == '"') { int closeQuote = IndexOfUnescapedQuote(trimmed, 1); if (closeQuote > 0) { var afterQuote = trimmed[(closeQuote + 1)..].TrimStart(); if (afterQuote.Length > 0 && afterQuote[0] == ':') { // It's a key Write("\"", ConsoleColor.DarkGray); Write(trimmed[1..closeQuote].ToString(), ConsoleColor.Cyan); Write("\"", ConsoleColor.DarkGray); Write(": ", ConsoleColor.DarkGray); var value = afterQuote[1..].TrimStart(); WriteJsonValue(value); Console.WriteLine(); continue; } else { // It's a string value on its own line (array element) WriteJsonValue(trimmed); Console.WriteLine(); continue; } } } // standalone structural or value tokens WriteJsonValue(trimmed); Console.WriteLine(); } Console.ResetColor(); } public void JsonHighlightWithMatch(string json, string match) { if (Console.IsOutputRedirected) { Console.WriteLine(json); return; } foreach (var line in json.Split('\n')) { var trimmed = line.AsSpan().TrimStart(); // leading whitespace if (trimmed.Length < line.Length) { Console.Write(line.AsSpan(0, line.Length - trimmed.Length)); } if (trimmed.IsEmpty) { Console.WriteLine(); continue; } // JSONC comments if (trimmed.StartsWith("//")) { WriteWithMatchHighlight(trimmed.ToString(), ConsoleColor.DarkGreen, match); Console.WriteLine(); continue; } // "key": value if (trimmed.Length > 0 && trimmed[0] == '"') { int closeQuote = IndexOfUnescapedQuote(trimmed, 1); if (closeQuote > 0) { var afterQuote = trimmed[(closeQuote + 1)..].TrimStart(); if (afterQuote.Length > 0 && afterQuote[0] == ':') { Write("\"", ConsoleColor.DarkGray); WriteWithMatchHighlight(trimmed[1..closeQuote].ToString(), ConsoleColor.Cyan, match); Write("\"", ConsoleColor.DarkGray); Write(": ", ConsoleColor.DarkGray); var value = afterQuote[1..].TrimStart(); WriteJsonValueWithMatch(value, match); Console.WriteLine(); continue; } else { WriteJsonValueWithMatch(trimmed, match); Console.WriteLine(); continue; } } } WriteJsonValueWithMatch(trimmed, match); Console.WriteLine(); } Console.ResetColor(); } private void WriteWithMatchHighlight(string text, ConsoleColor baseColor, string match) { int pos = 0; while (pos < text.Length) { int idx = text.IndexOf(match, pos, StringComparison.OrdinalIgnoreCase); if (idx < 0) { Write(text[pos..], baseColor); break; } if (idx > pos) { Write(text[pos..idx], baseColor); } // Invert colors for the match var prevBg = Console.BackgroundColor; var prevFg = Console.ForegroundColor; Console.BackgroundColor = baseColor; Console.ForegroundColor = ConsoleColor.Black; Console.Write(text[idx..(idx + match.Length)]); Console.BackgroundColor = prevBg; Console.ForegroundColor = prevFg; pos = idx + match.Length; } } private void WriteJsonValueWithMatch(ReadOnlySpan value, string match) { if (value.IsEmpty) return; var trimmed = value.TrimEnd(); bool trailingComma = trimmed.Length > 0 && trimmed[^1] == ','; var core = trailingComma ? trimmed[..^1] : trimmed; if (core.Length == 0) { if (trailingComma) Write(",", ConsoleColor.DarkGray); return; } char first = core[0]; if (first == '"') { Write("\"", ConsoleColor.DarkGray); WriteWithMatchHighlight(core[1..^1].ToString(), ConsoleColor.Yellow, match); Write("\"", ConsoleColor.DarkGray); } else if (first is '{' or '}' or '[' or ']') { Write(core.ToString(), ConsoleColor.DarkGray); } else if (core is "null") { Write("null", ConsoleColor.DarkGray); } else if (core is "true" or "false" || (core.Length > 0 && (char.IsDigit(first) || first == '-'))) { WriteWithMatchHighlight(core.ToString(), ConsoleColor.Green, match); } else { WriteWithMatchHighlight(core.ToString(), Console.ForegroundColor, match); } if (trailingComma) Write(",", ConsoleColor.DarkGray); } private void WriteJsonValue(ReadOnlySpan value) { if (value.IsEmpty) return; var trimmed = value.TrimEnd(); bool trailingComma = trimmed.Length > 0 && trimmed[^1] == ','; var core = trailingComma ? trimmed[..^1] : trimmed; if (core.Length == 0) { if (trailingComma) Write(",", ConsoleColor.DarkGray); return; } char first = core[0]; if (first == '"') { Write("\"", ConsoleColor.DarkGray); Write(core[1..^1].ToString(), ConsoleColor.Yellow); Write("\"", ConsoleColor.DarkGray); } else if (first is '{' or '}' or '[' or ']') { Write(core.ToString(), ConsoleColor.DarkGray); } else if (core is "null") { Write("null", ConsoleColor.DarkGray); } else if (core is "true" or "false" || (core.Length > 0 && (char.IsDigit(first) || first == '-'))) { Write(core.ToString(), ConsoleColor.Green); } else { Console.Write(core); } if (trailingComma) Write(",", ConsoleColor.DarkGray); } private static int IndexOfUnescapedQuote(ReadOnlySpan span, int start) { for (int i = start; i < span.Length; i++) { if (span[i] == '\\') { i++; // skip escaped char continue; } if (span[i] == '"') return i; } return -1; } public void Line((string str1, string str2)[] lines) { if (Console.IsOutputRedirected) { foreach (var (str1, str2) in lines) { Console.WriteLine($"{str1}\t{str2}"); } return; } var pos = lines.Select(l => l.str1.Length).Max() + 1; int consoleWidth = Console.WindowWidth; foreach (var (str1, str2) in lines) { Write(str1, ConsoleColor.Yellow); Console.CursorLeft = pos; var words = str2.Split(' '); var line = new StringBuilder(words[0]); for (int i = 1; i < words.Length; i++) { if (line.Length + words[i].Length >= consoleWidth - pos) { Line(line.ToString()); line.Clear(); Console.CursorLeft = pos - 1; } line.Append(' ' + words[i]); } if (line.Length > 0) { Line(line.ToString()); } } } }