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;
+ }
+ }
+ }
+}