diff --git a/src/NosCore.Shared/Helpers/ConsoleHelper.cs b/src/NosCore.Shared/Helpers/ConsoleHelper.cs new file mode 100644 index 0000000..7c0007c --- /dev/null +++ b/src/NosCore.Shared/Helpers/ConsoleHelper.cs @@ -0,0 +1,40 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using System; +using System.Runtime.InteropServices; + +namespace NosCore.Shared.Helpers +{ + public static class ConsoleHelper + { + // Console.WindowWidth reads the screen buffer through stdout, then stderr. Its third + // try, stdin, cannot answer: a console input handle has no screen buffer, so a process + // whose output is piped while stdin is still on the console has no width to read. + public static bool HasWindowSize => !Console.IsOutputRedirected || !Console.IsErrorRedirected; + + public static void SetTitle(string title) + { + if (OperatingSystem.IsWindows() && CanCarryTitle) + { + Console.Title = title; + } + } + + public static void AppendTitle(string suffix) + { + if (OperatingSystem.IsWindows() && CanCarryTitle) + { + Console.Title += suffix; + } + } + + public static bool CanCarryTitle => !Console.IsOutputRedirected && GetConsoleWindow() != IntPtr.Zero; + + [DllImport("kernel32.dll")] + private static extern IntPtr GetConsoleWindow(); + } +} diff --git a/src/NosCore.Shared/I18N/Logger.cs b/src/NosCore.Shared/I18N/Logger.cs index 080e65b..45f7f31 100644 --- a/src/NosCore.Shared/I18N/Logger.cs +++ b/src/NosCore.Shared/I18N/Logger.cs @@ -6,8 +6,8 @@ using System; using System.Globalization; -using System.IO; using Microsoft.Extensions.Configuration; +using NosCore.Shared.Helpers; using Serilog; namespace NosCore.Shared.I18N @@ -57,14 +57,9 @@ public static void PrintHeader(string text) private static int GetWindowWidth() { - try - { - return Console.WindowHeight > 0 ? Console.WindowWidth : HeadlessWindowWidth; - } - catch (IOException) - { - return HeadlessWindowWidth; - } + return ConsoleHelper.HasWindowSize && Console.WindowHeight > 0 + ? Console.WindowWidth + : HeadlessWindowWidth; } } } \ No newline at end of file diff --git a/src/NosCore.Shared/NosCore.Shared.csproj b/src/NosCore.Shared/NosCore.Shared.csproj index 42a8d25..45ac12b 100644 --- a/src/NosCore.Shared/NosCore.Shared.csproj +++ b/src/NosCore.Shared/NosCore.Shared.csproj @@ -12,7 +12,7 @@ https://github.com/NosCoreIO/NosCore.Dao.git nostale, noscore, nostale private server source, nostale emulator - 6.0.1 + 6.0.2 false NosCore's Shared Components diff --git a/test/NosCore.Shared.Tests/ConsoleHelperTests.cs b/test/NosCore.Shared.Tests/ConsoleHelperTests.cs new file mode 100644 index 0000000..0eb2641 --- /dev/null +++ b/test/NosCore.Shared.Tests/ConsoleHelperTests.cs @@ -0,0 +1,108 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using System; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NosCore.Shared.Helpers; +using NosCore.Shared.I18N; + +namespace NosCore.Shared.Tests +{ + // A test process cannot choose whether it owns a console, so each case asserts the + // half of the contract its own state can prove and stands aside for the other. + [TestClass] + public class ConsoleHelperTests + { + private const int HeadlessSeparatorWidth = 20; + + [TestMethod] + public void TitleIsLeftAloneWhenNothingCanCarryIt() + { + if (ConsoleHelper.CanCarryTitle) + { + Assert.Inconclusive("This process owns a console, so the title is expected to change."); + } + + var before = ReadTitle(); + + ConsoleHelper.SetTitle("NosCore"); + ConsoleHelper.AppendTitle(" - Port : 4000"); + + Assert.AreEqual(before, ReadTitle()); + } + + [TestMethod] + public void TitleIsSetWhenAConsoleCanCarryIt() + { + if (!ConsoleHelper.CanCarryTitle) + { + Assert.Inconclusive("This process owns no console to carry a title."); + } + + ConsoleHelper.SetTitle("NosCore"); + ConsoleHelper.AppendTitle(" - Port : 4000"); + + Assert.AreEqual("NosCore - Port : 4000", ReadTitle()); + } + + [TestMethod] + public void HeaderFallsBackToAFixedWidthWhenTheSizeIsUnreadable() + { + if (ConsoleHelper.HasWindowSize) + { + Assert.Inconclusive("This process can read a console width, so the fallback does not apply."); + } + + Assert.AreEqual(new string('=', HeadlessSeparatorWidth), PrintHeaderAndReadSeparator()); + } + + [TestMethod] + public void HeaderUsesTheConsoleWidthWhenThereIsOne() + { + if (!ConsoleHelper.HasWindowSize || Console.WindowHeight <= 0) + { + Assert.Inconclusive("This process cannot read a console width."); + } + + Assert.AreEqual(new string('=', Console.WindowWidth - 1), PrintHeaderAndReadSeparator()); + } + + private static string PrintHeaderAndReadSeparator() + { + var captured = new StringWriter(); + var original = Console.Out; + try + { + Console.SetOut(captured); + Logger.PrintHeader("NosCore"); + } + finally + { + Console.SetOut(original); + } + + return captured.ToString().Split('\n')[0].TrimEnd('\r'); + } + + private static string? ReadTitle() + { + if (!OperatingSystem.IsWindows()) + { + return null; + } + + try + { + return Console.Title; + } + catch (IOException) + { + return null; + } + } + } +}