summaryrefslogtreecommitdiff
path: root/src/SMAPI.Web/Framework/LogParsing
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI.Web/Framework/LogParsing')
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs25
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/LogParseException.cs3
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/LogParser.cs82
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/Models/LogMessage.cs38
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs74
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs20
6 files changed, 162 insertions, 80 deletions
diff --git a/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs b/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs
index 992876ef..a1384b8f 100644
--- a/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Text;
using StardewModdingAPI.Web.Framework.LogParsing.Models;
@@ -11,7 +12,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
** Fields
*********/
/// <summary>The local time when the next log was posted.</summary>
- public string Time { get; set; }
+ public string? Time { get; set; }
/// <summary>The log level for the next log message.</summary>
public LogLevel Level { get; set; }
@@ -20,16 +21,17 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
public int ScreenId { get; set; }
/// <summary>The mod name for the next log message.</summary>
- public string Mod { get; set; }
+ public string? Mod { get; set; }
/// <summary>The text for the next log message.</summary>
- private readonly StringBuilder Text = new StringBuilder();
+ private readonly StringBuilder Text = new();
/*********
** Accessors
*********/
/// <summary>Whether the next log message has been started.</summary>
+ [MemberNotNullWhen(true, nameof(LogMessageBuilder.Time), nameof(LogMessageBuilder.Mod))]
public bool Started { get; private set; }
@@ -70,19 +72,18 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
}
/// <summary>Get a log message for the accumulated values.</summary>
- public LogMessage Build()
+ public LogMessage? Build()
{
if (!this.Started)
return null;
- return new LogMessage
- {
- Time = this.Time,
- Level = this.Level,
- ScreenId = this.ScreenId,
- Mod = this.Mod,
- Text = this.Text.ToString()
- };
+ return new LogMessage(
+ time: this.Time,
+ level: this.Level,
+ screenId: this.ScreenId,
+ mod: this.Mod,
+ text: this.Text.ToString()
+ );
}
/// <summary>Reset to start a new log message.</summary>
diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParseException.cs b/src/SMAPI.Web/Framework/LogParsing/LogParseException.cs
index 5d4c8c08..3f815e3e 100644
--- a/src/SMAPI.Web/Framework/LogParsing/LogParseException.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/LogParseException.cs
@@ -10,6 +10,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
*********/
/// <summary>Construct an instance.</summary>
/// <param name="message">The user-friendly error message.</param>
- public LogParseException(string message) : base(message) { }
+ public LogParseException(string message)
+ : base(message) { }
}
}
diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
index 887d0105..55272b23 100644
--- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
@@ -14,38 +14,38 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
** Fields
*********/
/// <summary>A regex pattern matching the start of a SMAPI message.</summary>
- private readonly Regex MessageHeaderPattern = new Regex(@"^\[(?<time>\d\d[:\.]\d\d[:\.]\d\d) (?<level>[a-z]+)(?: +screen_(?<screen>\d+))? +(?<modName>[^\]]+)\] ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex MessageHeaderPattern = new(@"^\[(?<time>\d\d[:\.]\d\d[:\.]\d\d) (?<level>[a-z]+)(?: +screen_(?<screen>\d+))? +(?<modName>[^\]]+)\] ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching SMAPI's initial platform info message.</summary>
- private readonly Regex InfoLinePattern = new Regex(@"^SMAPI (?<apiVersion>.+) with Stardew Valley (?<gameVersion>.+) on (?<os>.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex InfoLinePattern = new(@"^SMAPI (?<apiVersion>.+) with Stardew Valley (?<gameVersion>.+) on (?<os>.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching SMAPI's mod folder path line.</summary>
- private readonly Regex ModPathPattern = new Regex(@"^Mods go here: (?<path>.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex ModPathPattern = new(@"^Mods go here: (?<path>.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching SMAPI's log timestamp line.</summary>
- private readonly Regex LogStartedAtPattern = new Regex(@"^Log started at (?<timestamp>.+) UTC", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex LogStartedAtPattern = new(@"^Log started at (?<timestamp>.+) UTC", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching the start of SMAPI's mod list.</summary>
- private readonly Regex ModListStartPattern = new Regex(@"^Loaded \d+ mods:$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex ModListStartPattern = new(@"^Loaded \d+ mods:$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching an entry in SMAPI's mod list.</summary>
/// <remarks>The author name and description are optional.</remarks>
- private readonly Regex ModListEntryPattern = new Regex(@"^ (?<name>.+?) (?<version>[^\s]+)(?: by (?<author>[^\|]+))?(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex ModListEntryPattern = new(@"^ (?<name>.+?) (?<version>[^\s]+)(?: by (?<author>[^\|]+))?(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching the start of SMAPI's content pack list.</summary>
- private readonly Regex ContentPackListStartPattern = new Regex(@"^Loaded \d+ content packs:$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex ContentPackListStartPattern = new(@"^Loaded \d+ content packs:$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching an entry in SMAPI's content pack list.</summary>
- private readonly Regex ContentPackListEntryPattern = new Regex(@"^ (?<name>.+?) (?<version>[^\s]+)(?: by (?<author>[^\|]+))? \| for (?<for>[^\|]+)(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex ContentPackListEntryPattern = new(@"^ (?<name>.+?) (?<version>[^\s]+)(?: by (?<author>[^\|]+))? \| for (?<for>[^\|]+)(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching the start of SMAPI's mod update list.</summary>
- private readonly Regex ModUpdateListStartPattern = new Regex(@"^You can update \d+ mods?:$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex ModUpdateListStartPattern = new(@"^You can update \d+ mods?:$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching an entry in SMAPI's mod update list.</summary>
- private readonly Regex ModUpdateListEntryPattern = new Regex(@"^ (?<name>.+) (?<version>[^\s]+): (?<link>.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex ModUpdateListEntryPattern = new(@"^ (?<name>.+) (?<version>[^\s]+): (?<link>.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching SMAPI's update line.</summary>
- private readonly Regex SmapiUpdatePattern = new Regex(@"^You can update SMAPI to (?<version>[^\s]+): (?<link>.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex SmapiUpdatePattern = new(@"^You can update SMAPI to (?<version>[^\s]+): (?<link>.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/*********
@@ -53,7 +53,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
*********/
/// <summary>Parse SMAPI log text.</summary>
/// <param name="logText">The SMAPI log text.</param>
- public ParsedLog Parse(string logText)
+ public ParsedLog Parse(string? logText)
{
try
{
@@ -69,7 +69,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
}
// init log
- ParsedLog log = new ParsedLog
+ ParsedLog log = new()
{
IsValid = true,
RawText = logText,
@@ -77,8 +77,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
};
// parse log messages
- LogModInfo smapiMod = new LogModInfo { Name = "SMAPI", Author = "Pathoschild", Description = "", Loaded = true };
- LogModInfo gameMod = new LogModInfo { Name = "game", Author = "", Description = "", Loaded = true };
+ LogModInfo smapiMod = new(name: "SMAPI", author: "Pathoschild", version: "", description: "", loaded: true);
+ LogModInfo gameMod = new(name: "game", author: "", version: "", description: "", loaded: true);
IDictionary<string, List<LogModInfo>> mods = new Dictionary<string, List<LogModInfo>>();
bool inModList = false;
bool inContentPackList = false;
@@ -101,7 +101,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
default:
if (mods.TryGetValue(message.Mod, out var entries))
{
- foreach (var entry in entries)
+ foreach (LogModInfo entry in entries)
entry.Errors++;
}
break;
@@ -131,9 +131,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
string author = match.Groups["author"].Value;
string description = match.Groups["description"].Value;
- if (!mods.TryGetValue(name, out List<LogModInfo> entries))
+ if (!mods.TryGetValue(name, out List<LogModInfo>? entries))
mods[name] = entries = new List<LogModInfo>();
- entries.Add(new LogModInfo { Name = name, Author = author, Version = version, Description = description, Loaded = true });
+ entries.Add(new LogModInfo(name: name, author: author, version: version, description: description, loaded: true));
message.Section = LogSection.ModsList;
}
@@ -154,9 +154,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
string description = match.Groups["description"].Value;
string forMod = match.Groups["for"].Value;
- if (!mods.TryGetValue(name, out List<LogModInfo> entries))
+ if (!mods.TryGetValue(name, out List<LogModInfo>? entries))
mods[name] = entries = new List<LogModInfo>();
- entries.Add(new LogModInfo { Name = name, Author = author, Version = version, Description = description, ContentPackFor = forMod, Loaded = true });
+ entries.Add(new LogModInfo(name: name, author: author, version: version, description: description, contentPackFor: forMod, loaded: true));
message.Section = LogSection.ContentPackList;
}
@@ -177,23 +177,19 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
if (mods.TryGetValue(name, out var entries))
{
- foreach (var entry in entries)
- {
- entry.UpdateLink = link;
- entry.UpdateVersion = version;
- }
+ foreach (LogModInfo entry in entries)
+ entry.SetUpdate(version, link);
}
message.Section = LogSection.ModUpdateList;
}
-
else if (message.Level == LogLevel.Alert && this.SmapiUpdatePattern.IsMatch(message.Text))
{
Match match = this.SmapiUpdatePattern.Match(message.Text);
string version = match.Groups["version"].Value;
string link = match.Groups["link"].Value;
- smapiMod.UpdateVersion = version;
- smapiMod.UpdateLink = link;
+
+ smapiMod.SetUpdate(version, link);
}
// platform info line
@@ -203,7 +199,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
log.ApiVersion = match.Groups["apiVersion"].Value;
log.GameVersion = match.Groups["gameVersion"].Value;
log.OperatingSystem = match.Groups["os"].Value;
- smapiMod.Version = log.ApiVersion;
+ smapiMod.OverrideVersion(log.ApiVersion);
}
// mod path line
@@ -211,9 +207,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
{
Match match = this.ModPathPattern.Match(message.Text);
log.ModPath = match.Groups["path"].Value;
- int lastDelimiterPos = log.ModPath.LastIndexOfAny(new char[] { '/', '\\' });
+ int lastDelimiterPos = log.ModPath.LastIndexOfAny(new[] { '/', '\\' });
log.GamePath = lastDelimiterPos >= 0
- ? log.ModPath.Substring(0, lastDelimiterPos)
+ ? log.ModPath[..lastDelimiterPos]
: log.ModPath;
}
@@ -227,7 +223,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
}
// finalize log
- gameMod.Version = log.GameVersion;
+ if (log.GameVersion != null)
+ gameMod.OverrideVersion(log.GameVersion);
log.Mods = new[] { gameMod, smapiMod }.Concat(mods.Values.SelectMany(p => p).OrderBy(p => p.Name)).ToArray();
return log;
}
@@ -259,7 +256,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
/// <param name="messages">The messages to filter.</param>
private IEnumerable<LogMessage> CollapseRepeats(IEnumerable<LogMessage> messages)
{
- LogMessage next = null;
+ LogMessage? next = null;
+
foreach (LogMessage message in messages)
{
// new message
@@ -280,7 +278,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
yield return next;
next = message;
}
- yield return next;
+
+ if (next != null)
+ yield return next;
}
/// <summary>Split a SMAPI log into individual log messages.</summary>
@@ -288,12 +288,12 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
/// <exception cref="LogParseException">The log text can't be parsed successfully.</exception>
private IEnumerable<LogMessage> GetMessages(string logText)
{
- LogMessageBuilder builder = new LogMessageBuilder();
- using StringReader reader = new StringReader(logText);
+ LogMessageBuilder builder = new();
+ using StringReader reader = new(logText);
while (true)
{
// read line
- string line = reader.ReadLine();
+ string? line = reader.ReadLine();
if (line == null)
break;
@@ -306,17 +306,17 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
{
if (builder.Started)
{
- yield return builder.Build();
+ yield return builder.Build()!;
builder.Clear();
}
- var screenGroup = header.Groups["screen"];
+ Group screenGroup = header.Groups["screen"];
builder.Start(
time: header.Groups["time"].Value,
level: Enum.Parse<LogLevel>(header.Groups["level"].Value, ignoreCase: true),
screenId: screenGroup.Success ? int.Parse(screenGroup.Value) : 0, // main player is always screen ID 0
mod: header.Groups["modName"].Value,
- text: line.Substring(header.Length)
+ text: line[header.Length..]
);
}
else
@@ -330,7 +330,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
// end last message
if (builder.Started)
- yield return builder.Build();
+ yield return builder.Build()!;
}
}
}
diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/LogMessage.cs b/src/SMAPI.Web/Framework/LogParsing/Models/LogMessage.cs
index 1e08be78..7a5f32e0 100644
--- a/src/SMAPI.Web/Framework/LogParsing/Models/LogMessage.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/Models/LogMessage.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics.CodeAnalysis;
+
namespace StardewModdingAPI.Web.Framework.LogParsing.Models
{
/// <summary>A parsed log message.</summary>
@@ -7,19 +9,19 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
** Accessors
*********/
/// <summary>The local time when the log was posted.</summary>
- public string Time { get; set; }
+ public string Time { get; }
/// <summary>The log level.</summary>
- public LogLevel Level { get; set; }
+ public LogLevel Level { get; }
/// <summary>The screen ID in split-screen mode.</summary>
- public int ScreenId { get; set; }
+ public int ScreenId { get; }
/// <summary>The mod name.</summary>
- public string Mod { get; set; }
+ public string Mod { get; }
/// <summary>The log text.</summary>
- public string Text { get; set; }
+ public string Text { get; }
/// <summary>The number of times this message was repeated consecutively.</summary>
public int Repeated { get; set; }
@@ -28,6 +30,32 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
public LogSection? Section { get; set; }
/// <summary>Whether this message is the first one of its section.</summary>
+ [MemberNotNullWhen(true, nameof(LogMessage.Section))]
public bool IsStartOfSection { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance/</summary>
+ /// <param name="time">The local time when the log was posted.</param>
+ /// <param name="level">The log level.</param>
+ /// <param name="screenId">The screen ID in split-screen mode.</param>
+ /// <param name="mod">The mod name.</param>
+ /// <param name="text">The log text.</param>
+ /// <param name="repeated">The number of times this message was repeated consecutively.</param>
+ /// <param name="section">The section that this log message belongs to.</param>
+ /// <param name="isStartOfSection">Whether this message is the first one of its section.</param>
+ public LogMessage(string time, LogLevel level, int screenId, string mod, string text, int repeated = 0, LogSection? section = null, bool isStartOfSection = false)
+ {
+ this.Time = time;
+ this.Level = level;
+ this.ScreenId = screenId;
+ this.Mod = mod;
+ this.Text = text;
+ this.Repeated = repeated;
+ this.Section = section;
+ this.IsStartOfSection = isStartOfSection;
+ }
}
}
diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
index 067e4df4..a6b9165c 100644
--- a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics.CodeAnalysis;
+
namespace StardewModdingAPI.Web.Framework.LogParsing.Models
{
/// <summary>Metadata about a mod or content pack in the log.</summary>
@@ -7,33 +9,81 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
** Accessors
*********/
/// <summary>The mod name.</summary>
- public string Name { get; set; }
+ public string Name { get; }
/// <summary>The mod author.</summary>
- public string Author { get; set; }
-
- /// <summary>The update version.</summary>
- public string UpdateVersion { get; set; }
-
- /// <summary>The update link.</summary>
- public string UpdateLink { get; set; }
+ public string Author { get; }
/// <summary>The mod version.</summary>
- public string Version { get; set; }
+ public string Version { get; private set; }
/// <summary>The mod description.</summary>
- public string Description { get; set; }
+ public string Description { get; }
+
+ /// <summary>The update version.</summary>
+ public string? UpdateVersion { get; private set; }
+
+ /// <summary>The update link.</summary>
+ public string? UpdateLink { get; private set; }
/// <summary>The name of the mod for which this is a content pack (if applicable).</summary>
- public string ContentPackFor { get; set; }
+ public string? ContentPackFor { get; }
/// <summary>The number of errors logged by this mod.</summary>
public int Errors { get; set; }
/// <summary>Whether the mod was loaded into the game.</summary>
- public bool Loaded { get; set; }
+ public bool Loaded { get; }
/// <summary>Whether the mod has an update available.</summary>
+ [MemberNotNullWhen(true, nameof(LogModInfo.UpdateVersion), nameof(LogModInfo.UpdateLink))]
public bool HasUpdate => this.UpdateVersion != null && this.Version != this.UpdateVersion;
+
+ /// <summary>Whether the mod is a content pack for another mod.</summary>
+ [MemberNotNullWhen(true, nameof(LogModInfo.ContentPackFor))]
+ public bool IsContentPack => !string.IsNullOrWhiteSpace(this.ContentPackFor);
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="name">The mod name.</param>
+ /// <param name="author">The mod author.</param>
+ /// <param name="version">The mod version.</param>
+ /// <param name="description">The mod description.</param>
+ /// <param name="updateVersion">The update version.</param>
+ /// <param name="updateLink">The update link.</param>
+ /// <param name="contentPackFor">The name of the mod for which this is a content pack (if applicable).</param>
+ /// <param name="errors">The number of errors logged by this mod.</param>
+ /// <param name="loaded">Whether the mod was loaded into the game.</param>
+ public LogModInfo(string name, string author, string version, string description, string? updateVersion = null, string? updateLink = null, string? contentPackFor = null, int errors = 0, bool loaded = true)
+ {
+ this.Name = name;
+ this.Author = author;
+ this.Version = version;
+ this.Description = description;
+ this.UpdateVersion = updateVersion;
+ this.UpdateLink = updateLink;
+ this.ContentPackFor = contentPackFor;
+ this.Errors = errors;
+ this.Loaded = loaded;
+ }
+
+ /// <summary>Add an update alert for this mod.</summary>
+ /// <param name="updateVersion">The update version.</param>
+ /// <param name="updateLink">The update link.</param>
+ public void SetUpdate(string updateVersion, string updateLink)
+ {
+ this.UpdateVersion = updateVersion;
+ this.UpdateLink = updateLink;
+ }
+
+ /// <summary>Override the version number, for cases like SMAPI itself where the version is only known later during parsing.</summary>
+ /// <param name="version">The new mod version.</param>
+ public void OverrideVersion(string version)
+ {
+ this.Version = version;
+ }
}
}
diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs
index 87b20eb0..6951e434 100644
--- a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
namespace StardewModdingAPI.Web.Framework.LogParsing.Models
{
@@ -12,39 +13,40 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
** Metadata
****/
/// <summary>Whether the log file was successfully parsed.</summary>
+ [MemberNotNullWhen(true, nameof(ParsedLog.RawText))]
public bool IsValid { get; set; }
/// <summary>An error message indicating why the log file is invalid.</summary>
- public string Error { get; set; }
+ public string? Error { get; set; }
/// <summary>The raw log text.</summary>
- public string RawText { get; set; }
+ public string? RawText { get; set; }
/****
** Log data
****/
/// <summary>The SMAPI version.</summary>
- public string ApiVersion { get; set; }
+ public string? ApiVersion { get; set; }
/// <summary>The game version.</summary>
- public string GameVersion { get; set; }
+ public string? GameVersion { get; set; }
/// <summary>The player's operating system.</summary>
- public string OperatingSystem { get; set; }
+ public string? OperatingSystem { get; set; }
/// <summary>The game install path.</summary>
- public string GamePath { get; set; }
+ public string? GamePath { get; set; }
/// <summary>The mod folder path.</summary>
- public string ModPath { get; set; }
+ public string? ModPath { get; set; }
/// <summary>The ISO 8601 timestamp when the log was started.</summary>
public DateTimeOffset Timestamp { get; set; }
/// <summary>Metadata about installed mods and content packs.</summary>
- public LogModInfo[] Mods { get; set; } = new LogModInfo[0];
+ public LogModInfo[] Mods { get; set; } = Array.Empty<LogModInfo>();
/// <summary>The log messages.</summary>
- public LogMessage[] Messages { get; set; }
+ public LogMessage[] Messages { get; set; } = Array.Empty<LogMessage>();
}
}