diff options
-rw-r--r-- | src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs | 136 | ||||
-rw-r--r-- | src/SMAPI.Internal/ConsoleWriting/LogLevel.cs | 27 | ||||
-rw-r--r-- | src/SMAPI.Internal/ConsoleWriting/MonitorColorScheme.cs (renamed from src/SMAPI/Framework/Models/MonitorColorScheme.cs) | 2 | ||||
-rw-r--r-- | src/SMAPI.Internal/SMAPI.Internal.projitems | 3 | ||||
-rw-r--r-- | src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs | 27 | ||||
-rw-r--r-- | src/SMAPI/Framework/Models/SConfig.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/Monitor.cs | 111 | ||||
-rw-r--r-- | src/SMAPI/LogLevel.cs | 16 | ||||
-rw-r--r-- | src/SMAPI/StardewModdingAPI.csproj | 1 |
9 files changed, 196 insertions, 129 deletions
diff --git a/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs b/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs new file mode 100644 index 00000000..5f8fe271 --- /dev/null +++ b/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; + +namespace StardewModdingAPI.Internal.ConsoleWriting +{ + /// <summary>Provides a wrapper for writing color-coded text to the console.</summary> + internal class ColorfulConsoleWriter + { + /********* + ** Properties + *********/ + /// <summary>The console text color for each log level.</summary> + private readonly IDictionary<ConsoleLogLevel, ConsoleColor> Colors; + + /// <summary>Whether the current console supports color formatting.</summary> + private readonly bool SupportsColor; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="platform">The target platform.</param> + /// <param name="colorScheme">The console color scheme to use.</param> + public ColorfulConsoleWriter(Platform platform, MonitorColorScheme colorScheme) + { + this.SupportsColor = this.TestColorSupport(); + this.Colors = this.GetConsoleColorScheme(platform, colorScheme); + } + + /// <summary>Write a message line to the log.</summary> + /// <param name="message">The message to log.</param> + /// <param name="level">The log level.</param> + public void WriteLine(string message, ConsoleLogLevel level) + { + if (this.SupportsColor) + { + if (level == ConsoleLogLevel.Critical) + { + Console.BackgroundColor = ConsoleColor.Red; + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(message); + Console.ResetColor(); + } + else + { + Console.ForegroundColor = this.Colors[level]; + Console.WriteLine(message); + Console.ResetColor(); + } + } + else + Console.WriteLine(message); + } + + + /********* + ** Private methods + *********/ + /// <summary>Test whether the current console supports color formatting.</summary> + private bool TestColorSupport() + { + try + { + Console.ForegroundColor = Console.ForegroundColor; + return true; + } + catch (Exception) + { + return false; // Mono bug + } + } + + /// <summary>Get the color scheme to use for the current console.</summary> + /// <param name="platform">The target platform.</param> + /// <param name="colorScheme">The console color scheme to use.</param> + private IDictionary<ConsoleLogLevel, ConsoleColor> GetConsoleColorScheme(Platform platform, MonitorColorScheme colorScheme) + { + // auto detect color scheme + if (colorScheme == MonitorColorScheme.AutoDetect) + { + colorScheme = platform == Platform.Mac + ? MonitorColorScheme.LightBackground // MacOS doesn't provide console background color info, but it's usually white. + : ColorfulConsoleWriter.IsDark(Console.BackgroundColor) ? MonitorColorScheme.DarkBackground : MonitorColorScheme.LightBackground; + } + + // get colors for scheme + switch (colorScheme) + { + case MonitorColorScheme.DarkBackground: + return new Dictionary<ConsoleLogLevel, ConsoleColor> + { + [ConsoleLogLevel.Trace] = ConsoleColor.DarkGray, + [ConsoleLogLevel.Debug] = ConsoleColor.DarkGray, + [ConsoleLogLevel.Info] = ConsoleColor.White, + [ConsoleLogLevel.Warn] = ConsoleColor.Yellow, + [ConsoleLogLevel.Error] = ConsoleColor.Red, + [ConsoleLogLevel.Alert] = ConsoleColor.Magenta + }; + + case MonitorColorScheme.LightBackground: + return new Dictionary<ConsoleLogLevel, ConsoleColor> + { + [ConsoleLogLevel.Trace] = ConsoleColor.DarkGray, + [ConsoleLogLevel.Debug] = ConsoleColor.DarkGray, + [ConsoleLogLevel.Info] = ConsoleColor.Black, + [ConsoleLogLevel.Warn] = ConsoleColor.DarkYellow, + [ConsoleLogLevel.Error] = ConsoleColor.Red, + [ConsoleLogLevel.Alert] = ConsoleColor.DarkMagenta + }; + + default: + throw new NotSupportedException($"Unknown color scheme '{colorScheme}'."); + } + } + + /// <summary>Get whether a console color should be considered dark, which is subjectively defined as 'white looks better than black on this text'.</summary> + /// <param name="color">The color to check.</param> + private static bool IsDark(ConsoleColor color) + { + switch (color) + { + case ConsoleColor.Black: + case ConsoleColor.Blue: + case ConsoleColor.DarkBlue: + case ConsoleColor.DarkMagenta: // Powershell + case ConsoleColor.DarkRed: + case ConsoleColor.Red: + return true; + + default: + return false; + } + } + } +} diff --git a/src/SMAPI.Internal/ConsoleWriting/LogLevel.cs b/src/SMAPI.Internal/ConsoleWriting/LogLevel.cs new file mode 100644 index 00000000..85e69f51 --- /dev/null +++ b/src/SMAPI.Internal/ConsoleWriting/LogLevel.cs @@ -0,0 +1,27 @@ +namespace StardewModdingAPI.Internal.ConsoleWriting +{ + /// <summary>The log severity levels.</summary> + internal enum ConsoleLogLevel + { + /// <summary>Tracing info intended for developers.</summary> + Trace, + + /// <summary>Troubleshooting info that may be relevant to the player.</summary> + Debug, + + /// <summary>Info relevant to the player. This should be used judiciously.</summary> + Info, + + /// <summary>An issue the player should be aware of. This should be used rarely.</summary> + Warn, + + /// <summary>A message indicating something went wrong.</summary> + Error, + + /// <summary>Important information to highlight for the player when player action is needed (e.g. new version available). This should be used rarely to avoid alert fatigue.</summary> + Alert, + + /// <summary>A critical issue that generally signals an immediate end to the application.</summary> + Critical + } +} diff --git a/src/SMAPI/Framework/Models/MonitorColorScheme.cs b/src/SMAPI.Internal/ConsoleWriting/MonitorColorScheme.cs index d8289d08..bccb56d7 100644 --- a/src/SMAPI/Framework/Models/MonitorColorScheme.cs +++ b/src/SMAPI.Internal/ConsoleWriting/MonitorColorScheme.cs @@ -1,4 +1,4 @@ -namespace StardewModdingAPI.Framework.Models +namespace StardewModdingAPI.Internal.ConsoleWriting { /// <summary>A monitor color scheme to use.</summary> internal enum MonitorColorScheme diff --git a/src/SMAPI.Internal/SMAPI.Internal.projitems b/src/SMAPI.Internal/SMAPI.Internal.projitems index 764cb12e..dadae4b0 100644 --- a/src/SMAPI.Internal/SMAPI.Internal.projitems +++ b/src/SMAPI.Internal/SMAPI.Internal.projitems @@ -9,9 +9,12 @@ <Import_RootNamespace>SMAPI.Internal</Import_RootNamespace> </PropertyGroup> <ItemGroup> + <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ColorfulConsoleWriter.cs" /> <Compile Include="$(MSBuildThisFileDirectory)EnvironmentUtility.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\LogLevel.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Models\ModInfoModel.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Models\ModSeachModel.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Platform.cs" /> <Compile Include="$(MSBuildThisFileDirectory)SemanticVersionImpl.cs" /> </ItemGroup> diff --git a/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs b/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs index b8f2c34e..c04bcd1a 100644 --- a/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs +++ b/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs @@ -15,9 +15,6 @@ namespace StardewModdingAPI.Framework.Logging /********* ** Accessors *********/ - /// <summary>Whether the current console supports color formatting.</summary> - public bool SupportsColor { get; } - /// <summary>The event raised when a message is written to the console directly.</summary> public event Action<string> OnMessageIntercepted; @@ -32,9 +29,6 @@ namespace StardewModdingAPI.Framework.Logging this.Output = new InterceptingTextWriter(Console.Out); this.Output.OnMessageIntercepted += line => this.OnMessageIntercepted?.Invoke(line); Console.SetOut(this.Output); - - // test color support - this.SupportsColor = this.TestColorSupport(); } /// <summary>Get an exclusive lock and write to the console output without interception.</summary> @@ -61,26 +55,5 @@ namespace StardewModdingAPI.Framework.Logging Console.SetOut(this.Output.Out); this.Output.Dispose(); } - - - /********* - ** private methods - *********/ - /// <summary>Test whether the current console supports color formatting.</summary> - private bool TestColorSupport() - { - try - { - this.ExclusiveWriteWithoutInterception(() => - { - Console.ForegroundColor = Console.ForegroundColor; - }); - return true; - } - catch (Exception) - { - return false; // Mono bug - } - } } } diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index b732921f..e201e966 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -1,3 +1,5 @@ +using StardewModdingAPI.Internal.ConsoleWriting; + namespace StardewModdingAPI.Framework.Models { /// <summary>The SMAPI configuration settings.</summary> diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index 8df2e59b..2812a9cc 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -1,10 +1,8 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using StardewModdingAPI.Framework.Logging; -using StardewModdingAPI.Framework.Models; -using StardewModdingAPI.Internal; +using StardewModdingAPI.Internal.ConsoleWriting; namespace StardewModdingAPI.Framework { @@ -17,8 +15,11 @@ namespace StardewModdingAPI.Framework /// <summary>The name of the module which logs messages using this instance.</summary> private readonly string Source; + /// <summary>Handles writing color-coded text to the console.</summary> + private readonly ColorfulConsoleWriter ConsoleWriter; + /// <summary>Manages access to the console output.</summary> - private readonly ConsoleInterceptionManager ConsoleManager; + private readonly ConsoleInterceptionManager ConsoleInterceptor; /// <summary>The log file to which to write messages.</summary> private readonly LogFileManager LogFile; @@ -26,9 +27,6 @@ namespace StardewModdingAPI.Framework /// <summary>The maximum length of the <see cref="LogLevel"/> values.</summary> private static readonly int MaxLevelLength = (from level in Enum.GetValues(typeof(LogLevel)).Cast<LogLevel>() select level.ToString().Length).Max(); - /// <summary>The console text color for each log level.</summary> - private readonly IDictionary<LogLevel, ConsoleColor> Colors; - /// <summary>Propagates notification that SMAPI should exit.</summary> private readonly CancellationTokenSource ExitTokenSource; @@ -54,21 +52,21 @@ namespace StardewModdingAPI.Framework *********/ /// <summary>Construct an instance.</summary> /// <param name="source">The name of the module which logs messages using this instance.</param> - /// <param name="consoleManager">Manages access to the console output.</param> + /// <param name="consoleInterceptor">Intercepts access to the console output.</param> /// <param name="logFile">The log file to which to write messages.</param> /// <param name="exitTokenSource">Propagates notification that SMAPI should exit.</param> /// <param name="colorScheme">The console color scheme to use.</param> - public Monitor(string source, ConsoleInterceptionManager consoleManager, LogFileManager logFile, CancellationTokenSource exitTokenSource, MonitorColorScheme colorScheme) + public Monitor(string source, ConsoleInterceptionManager consoleInterceptor, LogFileManager logFile, CancellationTokenSource exitTokenSource, MonitorColorScheme colorScheme) { // validate if (string.IsNullOrWhiteSpace(source)) throw new ArgumentException("The log source cannot be empty."); // initialise - this.Colors = Monitor.GetConsoleColorScheme(colorScheme); this.Source = source; this.LogFile = logFile ?? throw new ArgumentNullException(nameof(logFile), "The log file manager cannot be null."); - this.ConsoleManager = consoleManager; + this.ConsoleWriter = new ColorfulConsoleWriter(Constants.Platform, colorScheme); + this.ConsoleInterceptor = consoleInterceptor; this.ExitTokenSource = exitTokenSource; } @@ -77,7 +75,7 @@ namespace StardewModdingAPI.Framework /// <param name="level">The log severity level.</param> public void Log(string message, LogLevel level = LogLevel.Debug) { - this.LogImpl(this.Source, message, level, this.Colors[level]); + this.LogImpl(this.Source, message, (ConsoleLogLevel)level); } /// <summary>Immediately exit the game without saving. This should only be invoked when an irrecoverable fatal error happens that risks save corruption or game-breaking bugs.</summary> @@ -92,7 +90,7 @@ namespace StardewModdingAPI.Framework internal void Newline() { if (this.WriteToConsole) - this.ConsoleManager.ExclusiveWriteWithoutInterception(Console.WriteLine); + this.ConsoleInterceptor.ExclusiveWriteWithoutInterception(Console.WriteLine); this.LogFile.WriteLine(""); } @@ -101,7 +99,7 @@ namespace StardewModdingAPI.Framework internal void LogUserInput(string input) { // user input already appears in the console, so just need to write to file - string prefix = this.GenerateMessagePrefix(this.Source, LogLevel.Info); + string prefix = this.GenerateMessagePrefix(this.Source, (ConsoleLogLevel)LogLevel.Info); this.LogFile.WriteLine($"{prefix} $>{input}"); } @@ -113,16 +111,14 @@ namespace StardewModdingAPI.Framework /// <param name="message">The message to log.</param> private void LogFatal(string message) { - this.LogImpl(this.Source, message, LogLevel.Error, ConsoleColor.White, background: ConsoleColor.Red); + this.LogImpl(this.Source, message, ConsoleLogLevel.Critical); } /// <summary>Write a message line to the log.</summary> /// <param name="source">The name of the mod logging the message.</param> /// <param name="message">The message to log.</param> /// <param name="level">The log level.</param> - /// <param name="color">The console foreground color.</param> - /// <param name="background">The console background color (or <c>null</c> to leave it as-is).</param> - private void LogImpl(string source, string message, LogLevel level, ConsoleColor color, ConsoleColor? background = null) + private void LogImpl(string source, string message, ConsoleLogLevel level) { // generate message string prefix = this.GenerateMessagePrefix(source, level); @@ -130,20 +126,11 @@ namespace StardewModdingAPI.Framework string consoleMessage = this.ShowFullStampInConsole ? fullMessage : $"[{source}] {message}"; // write to console - if (this.WriteToConsole && (this.ShowTraceInConsole || level != LogLevel.Trace)) + if (this.WriteToConsole && (this.ShowTraceInConsole || level != ConsoleLogLevel.Trace)) { - this.ConsoleManager.ExclusiveWriteWithoutInterception(() => + this.ConsoleInterceptor.ExclusiveWriteWithoutInterception(() => { - if (this.ConsoleManager.SupportsColor) - { - if (background.HasValue) - Console.BackgroundColor = background.Value; - Console.ForegroundColor = color; - Console.WriteLine(consoleMessage); - Console.ResetColor(); - } - else - Console.WriteLine(consoleMessage); + this.ConsoleWriter.WriteLine(consoleMessage, level); }); } @@ -154,72 +141,10 @@ namespace StardewModdingAPI.Framework /// <summary>Generate a message prefix for the current time.</summary> /// <param name="source">The name of the mod logging the message.</param> /// <param name="level">The log level.</param> - private string GenerateMessagePrefix(string source, LogLevel level) + private string GenerateMessagePrefix(string source, ConsoleLogLevel level) { string levelStr = level.ToString().ToUpper().PadRight(Monitor.MaxLevelLength); return $"[{DateTime.Now:HH:mm:ss} {levelStr} {source}]"; } - - /// <summary>Get the color scheme to use for the current console.</summary> - /// <param name="colorScheme">The console color scheme to use.</param> - private static IDictionary<LogLevel, ConsoleColor> GetConsoleColorScheme(MonitorColorScheme colorScheme) - { - // auto detect color scheme - if (colorScheme == MonitorColorScheme.AutoDetect) - { - if (Constants.Platform == Platform.Mac) - colorScheme = MonitorColorScheme.LightBackground; // MacOS doesn't provide console background color info, but it's usually white. - else - colorScheme = Monitor.IsDark(Console.BackgroundColor) ? MonitorColorScheme.DarkBackground : MonitorColorScheme.LightBackground; - } - - // get colors for scheme - switch (colorScheme) - { - case MonitorColorScheme.DarkBackground: - return new Dictionary<LogLevel, ConsoleColor> - { - [LogLevel.Trace] = ConsoleColor.DarkGray, - [LogLevel.Debug] = ConsoleColor.DarkGray, - [LogLevel.Info] = ConsoleColor.White, - [LogLevel.Warn] = ConsoleColor.Yellow, - [LogLevel.Error] = ConsoleColor.Red, - [LogLevel.Alert] = ConsoleColor.Magenta - }; - - case MonitorColorScheme.LightBackground: - return new Dictionary<LogLevel, ConsoleColor> - { - [LogLevel.Trace] = ConsoleColor.DarkGray, - [LogLevel.Debug] = ConsoleColor.DarkGray, - [LogLevel.Info] = ConsoleColor.Black, - [LogLevel.Warn] = ConsoleColor.DarkYellow, - [LogLevel.Error] = ConsoleColor.Red, - [LogLevel.Alert] = ConsoleColor.DarkMagenta - }; - - default: - throw new NotSupportedException($"Unknown color scheme '{colorScheme}'."); - } - } - - /// <summary>Get whether a console color should be considered dark, which is subjectively defined as 'white looks better than black on this text'.</summary> - /// <param name="color">The color to check.</param> - private static bool IsDark(ConsoleColor color) - { - switch (color) - { - case ConsoleColor.Black: - case ConsoleColor.Blue: - case ConsoleColor.DarkBlue: - case ConsoleColor.DarkMagenta: // Powershell - case ConsoleColor.DarkRed: - case ConsoleColor.Red: - return true; - - default: - return false; - } - } } } diff --git a/src/SMAPI/LogLevel.cs b/src/SMAPI/LogLevel.cs index 89647876..7987f82a 100644 --- a/src/SMAPI/LogLevel.cs +++ b/src/SMAPI/LogLevel.cs @@ -1,24 +1,26 @@ +using StardewModdingAPI.Internal.ConsoleWriting; + namespace StardewModdingAPI { /// <summary>The log severity levels.</summary> public enum LogLevel { /// <summary>Tracing info intended for developers.</summary> - Trace, + Trace = ConsoleLogLevel.Trace, /// <summary>Troubleshooting info that may be relevant to the player.</summary> - Debug, + Debug = ConsoleLogLevel.Debug, /// <summary>Info relevant to the player. This should be used judiciously.</summary> - Info, + Info = ConsoleLogLevel.Info, /// <summary>An issue the player should be aware of. This should be used rarely.</summary> - Warn, + Warn = ConsoleLogLevel.Warn, /// <summary>A message indicating something went wrong.</summary> - Error, + Error = ConsoleLogLevel.Error, /// <summary>Important information to highlight for the player when player action is needed (e.g. new version available). This should be used rarely to avoid alert fatigue.</summary> - Alert + Alert = ConsoleLogLevel.Alert } -}
\ No newline at end of file +} diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 320b97e7..01d5aaf1 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -125,7 +125,6 @@ <Compile Include="Framework\SContentManager.cs" /> <Compile Include="Framework\Exceptions\SAssemblyLoadFailedException.cs" /> <Compile Include="Framework\ModLoading\AssemblyLoadStatus.cs" /> - <Compile Include="Framework\Models\MonitorColorScheme.cs" /> <Compile Include="Framework\Reflection\InterfaceProxyBuilder.cs" /> <Compile Include="Framework\Reflection\InterfaceProxyFactory.cs" /> <Compile Include="Framework\RewriteFacades\SpriteBatchMethods.cs" /> |