generalise console color logic for reuse (#495)
This commit is contained in:
parent
5a2755bfcc
commit
02c02a55ee
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace StardewModdingAPI.Framework.Models
|
||||
namespace StardewModdingAPI.Internal.ConsoleWriting
|
||||
{
|
||||
/// <summary>A monitor color scheme to use.</summary>
|
||||
internal enum MonitorColorScheme
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using StardewModdingAPI.Internal.ConsoleWriting;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Models
|
||||
{
|
||||
/// <summary>The SMAPI configuration settings.</summary>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in New Issue