Add nullable context to SabreTools.IO

This commit is contained in:
Matt Nadareski
2023-08-10 15:02:40 -04:00
parent fb81fd0243
commit 7bb0ba245d
22 changed files with 356 additions and 174 deletions

View File

@@ -59,6 +59,9 @@ namespace SabreTools.IO
file.Read(bom, 0, 4);
file.Dispose();
// Disable warning about UTF7 usage
#pragma warning disable SYSLIB0001
// Analyze the BOM
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
@@ -66,6 +69,8 @@ namespace SabreTools.IO
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32;
return Encoding.Default;
#pragma warning restore SYSLIB0001
}
catch
{
@@ -78,14 +83,14 @@ namespace SabreTools.IO
/// </summary>
/// <param name="path">Path to get extension from</param>
/// <returns>Extension, if possible</returns>
public static string GetNormalizedExtension(this string path)
public static string? GetNormalizedExtension(this string? path)
{
// Check null or empty first
if (string.IsNullOrWhiteSpace(path))
return null;
// Get the extension from the path, if possible
string ext = Path.GetExtension(path)?.ToLowerInvariant();
string? ext = Path.GetExtension(path)?.ToLowerInvariant();
// Check if the extension is null or empty
if (string.IsNullOrWhiteSpace(ext))
@@ -96,13 +101,13 @@ namespace SabreTools.IO
return ext;
}
/// <summary>
/// Get all empty folders within a root folder
/// </summary>
/// <param name="root">Root directory to parse</param>
/// <returns>IEumerable containing all directories that are empty, an empty enumerable if the root is empty, null otherwise</returns>
public static List<string> ListEmpty(this string root)
public static List<string>? ListEmpty(this string? root)
{
// Check null or empty first
if (string.IsNullOrEmpty(root))

View File

@@ -11,14 +11,14 @@ namespace SabreTools.IO
/// <summary>
/// Current full path represented
/// </summary>
public string CurrentPath { get; private set; }
public string CurrentPath { get; init; }
/// <summary>
/// Possible parent path represented (may be null or empty)
/// </summary>
public string ParentPath { get; private set; }
public string? ParentPath { get; init; }
public ParentablePath(string currentPath, string parentPath = null)
public ParentablePath(string currentPath, string? parentPath = null)
{
CurrentPath = currentPath;
ParentPath = parentPath;
@@ -29,7 +29,7 @@ namespace SabreTools.IO
/// </summary>
/// <param name="sanitize">True if path separators should be converted to '-', false otherwise</param>
/// <returns>Subpath for the file</returns>
public string GetNormalizedFileName(bool sanitize)
public string? GetNormalizedFileName(bool sanitize)
{
// If the current path is empty, we can't do anything
if (string.IsNullOrWhiteSpace(CurrentPath))
@@ -55,7 +55,7 @@ namespace SabreTools.IO
/// <param name="outDir">Output directory to use</param>
/// <param name="inplace">True if the output file should go to the same input folder, false otherwise</param>
/// <returns>Complete output path</returns>
public string GetOutputPath(string outDir, bool inplace)
public string? GetOutputPath(string outDir, bool inplace)
{
// If the current path is empty, we can't do anything
if (string.IsNullOrWhiteSpace(CurrentPath))
@@ -73,21 +73,22 @@ namespace SabreTools.IO
return Path.GetDirectoryName(CurrentPath);
// If the current and parent paths are the same, just use the output directory
if (!splitpath || CurrentPath.Length == ParentPath.Length)
if (!splitpath || CurrentPath.Length == (ParentPath?.Length ?? 0))
return outDir;
// By default, the working parent directory is the parent path
string workingParent = ParentPath;
string workingParent = ParentPath ?? string.Empty;
// TODO: Should this be the default? Always create a subfolder if a folder is found?
// If we are processing a path that is coming from a directory and we are outputting to the current directory, we want to get the subfolder to write to
if (outDir == Environment.CurrentDirectory)
workingParent = Path.GetDirectoryName(ParentPath);
workingParent = Path.GetDirectoryName(ParentPath ?? string.Empty) ?? string.Empty;
// Determine the correct subfolder based on the working parent directory
int extraLength = workingParent.EndsWith(':')
|| workingParent.EndsWith(Path.DirectorySeparatorChar)
|| workingParent.EndsWith(Path.AltDirectorySeparatorChar) ? 0 : 1;
return Path.GetDirectoryName(Path.Combine(outDir, CurrentPath.Remove(0, workingParent.Length + extraLength)));
}
}

View File

@@ -23,12 +23,12 @@ namespace SabreTools.IO.Readers
/// <summary>
/// Internal stream reader for inputting
/// </summary>
private readonly StreamReader sr;
private readonly StreamReader? sr;
/// <summary>
/// Contents of the current line, unprocessed
/// </summary>
public string CurrentLine { get; private set; } = string.Empty;
public string? CurrentLine { get; private set; } = string.Empty;
/// <summary>
/// Get the current line number
@@ -49,12 +49,12 @@ namespace SabreTools.IO.Readers
/// <summary>
/// Contents of the currently read line as an internal item
/// </summary>
public Dictionary<string, string> Internal { get; private set; } = new Dictionary<string, string>();
public Dictionary<string, string>? Internal { get; private set; } = new Dictionary<string, string>();
/// <summary>
/// Current internal item name
/// </summary>
public string InternalName { get; private set; } = null;
public string? InternalName { get; private set; }
/// <summary>
/// Get if we should be making DosCenter exceptions
@@ -85,7 +85,7 @@ namespace SabreTools.IO.Readers
/// <summary>
/// Current top-level being read
/// </summary>
public string TopLevel { get; private set; } = string.Empty;
public string? TopLevel { get; private set; } = string.Empty;
/// <summary>
/// Constructor for opening a write from a file
@@ -108,10 +108,13 @@ namespace SabreTools.IO.Readers
/// </summary>
public bool ReadNextLine()
{
if (!(sr.BaseStream?.CanRead ?? false) || sr.EndOfStream)
if (sr?.BaseStream == null)
return false;
CurrentLine = sr.ReadLine().Trim();
if (!sr.BaseStream.CanRead || sr.EndOfStream)
return false;
CurrentLine = sr.ReadLine()?.Trim();
LineNumber++;
ProcessLine();
return true;
@@ -122,6 +125,9 @@ namespace SabreTools.IO.Readers
/// </summary>
private void ProcessLine()
{
if (CurrentLine == null)
return;
// Standalone (special case for DC dats)
if (CurrentLine.StartsWith("Name:"))
{
@@ -303,7 +309,7 @@ namespace SabreTools.IO.Readers
/// </summary>
public void Dispose()
{
sr.Dispose();
sr?.Dispose();
}
}
}

View File

@@ -11,7 +11,7 @@ namespace SabreTools.IO.Readers
/// <summary>
/// Internal stream reader for inputting
/// </summary>
private readonly StreamReader sr;
private readonly StreamReader? sr;
/// <summary>
/// Get if at end of stream
@@ -32,7 +32,7 @@ namespace SabreTools.IO.Readers
/// <summary>
/// Contents of the current line, unprocessed
/// </summary>
public string CurrentLine { get; private set; } = string.Empty;
public string? CurrentLine { get; private set; } = string.Empty;
/// <summary>
/// Get the current line number
@@ -47,7 +47,7 @@ namespace SabreTools.IO.Readers
/// <summary>
/// Current section being read
/// </summary>
public string Section { get; private set; } = string.Empty;
public string? Section { get; private set; } = string.Empty;
/// <summary>
/// Validate that rows are in key=value format
@@ -75,10 +75,13 @@ namespace SabreTools.IO.Readers
/// </summary>
public bool ReadNextLine()
{
if (!(sr.BaseStream?.CanRead ?? false) || sr.EndOfStream)
if (sr?.BaseStream == null)
return false;
CurrentLine = sr.ReadLine().Trim();
if (!sr.BaseStream.CanRead || sr.EndOfStream)
return false;
CurrentLine = sr.ReadLine()?.Trim();
LineNumber++;
ProcessLine();
return true;
@@ -89,6 +92,9 @@ namespace SabreTools.IO.Readers
/// </summary>
private void ProcessLine()
{
if (CurrentLine == null)
return;
// Comment
if (CurrentLine.StartsWith(";"))
{
@@ -142,7 +148,7 @@ namespace SabreTools.IO.Readers
/// </summary>
public void Dispose()
{
sr.Dispose();
sr?.Dispose();
}
}
}

View File

@@ -12,7 +12,7 @@ namespace SabreTools.IO.Readers
/// <summary>
/// Internal stream reader for inputting
/// </summary>
private readonly StreamReader sr;
private readonly StreamReader? sr;
/// <summary>
/// Internal value to say how many fields should be written
@@ -33,7 +33,7 @@ namespace SabreTools.IO.Readers
/// <summary>
/// Contents of the current line, unprocessed
/// </summary>
public string CurrentLine { get; private set; } = string.Empty;
public string? CurrentLine { get; private set; } = string.Empty;
/// <summary>
/// Get the current line number
@@ -48,12 +48,12 @@ namespace SabreTools.IO.Readers
/// <summary>
/// Header row values
/// </summary>
public List<string> HeaderValues { get; set; } = null;
public List<string>? HeaderValues { get; set; } = null;
/// <summary>
/// Get the current line values
/// </summary>
public List<string> Line { get; private set; } = null;
public List<string>? Line { get; private set; } = null;
/// <summary>
/// Assume that values are wrapped in quotes
@@ -105,13 +105,19 @@ namespace SabreTools.IO.Readers
/// </summary>
public bool ReadNextLine()
{
if (!(sr.BaseStream?.CanRead ?? false) || sr.EndOfStream)
if (sr?.BaseStream == null)
return false;
string fullLine = sr.ReadLine();
if (!sr.BaseStream.CanRead || sr.EndOfStream)
return false;
string? fullLine = sr.ReadLine();
CurrentLine = fullLine;
LineNumber++;
if (fullLine == null)
return false;
// If we have quotes, we need to split specially
if (Quotes)
{
@@ -155,17 +161,21 @@ namespace SabreTools.IO.Readers
/// <summary>
/// Get the value for the current line for the current key
/// </summary>
public string GetValue(string key)
public string? GetValue(string key)
{
// No header means no key-based indexing
if (!Header)
throw new ArgumentException("No header expected so no keys can be used");
// If we don't have the key, return null;
// If we don't have the key, return null
if (HeaderValues == null)
throw new ArgumentException($"Current line doesn't have key {key}");
if (!HeaderValues.Contains(key))
return null;
int index = HeaderValues.IndexOf(key);
if (Line == null)
throw new ArgumentException($"Current line doesn't have index {index}");
if (Line.Count < index)
throw new ArgumentException($"Current line doesn't have index {index}");
@@ -177,6 +187,8 @@ namespace SabreTools.IO.Readers
/// </summary>
public string GetValue(int index)
{
if (Line == null)
throw new ArgumentException($"Current line doesn't have index {index}");
if (Line.Count < index)
throw new ArgumentException($"Current line doesn't have index {index}");
@@ -188,7 +200,7 @@ namespace SabreTools.IO.Readers
/// </summary>
public void Dispose()
{
sr.Dispose();
sr?.Dispose();
}
}
}

View File

@@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>

View File

@@ -44,11 +44,8 @@ namespace SabreTools.IO.Writers
/// <summary>
/// Tag information for the stack
/// </summary>
private struct TagInfo
private record struct TagInfo(string? Name, bool Mixed)
{
public string Name;
public bool Mixed;
public void Init()
{
Name = null;
@@ -173,7 +170,7 @@ namespace SabreTools.IO.Writers
/// <summary>
/// Write a complete element with content
/// </summary>
public void WriteElementString(string name, string value)
public void WriteElementString(string name, string? value)
{
WriteStartElement(name);
WriteString(value);
@@ -186,7 +183,7 @@ namespace SabreTools.IO.Writers
/// <param name="name">Name of the element</param>
/// <param name="value">Value to write in the element</param>
/// <param name="throwOnError">Indicates if an error should be thrown on a missing required value</param>
public void WriteRequiredElementString(string name, string value, bool throwOnError = false)
public void WriteRequiredElementString(string name, string? value, bool throwOnError = false)
{
// Throw an exception if we are configured to
if (value == null && throwOnError)
@@ -200,7 +197,7 @@ namespace SabreTools.IO.Writers
/// </summary>
/// <param name="name">Name of the element</param>
/// <param name="value">Value to write in the element</param>
public void WriteOptionalElementString(string name, string value)
public void WriteOptionalElementString(string name, string? value)
{
if (!string.IsNullOrEmpty(value))
WriteElementString(name, value);
@@ -253,7 +250,7 @@ namespace SabreTools.IO.Writers
/// <param name="name">Name of the attribute</param>
/// <param name="value">Value to write in the attribute</param>
/// <param name="quoteOverride">Non-null to overwrite the writer setting, null otherwise</param>
public void WriteAttributeString(string name, string value, bool? quoteOverride = null)
public void WriteAttributeString(string name, string? value, bool? quoteOverride = null)
{
WriteStartAttribute(name, quoteOverride);
WriteString(value);
@@ -267,7 +264,7 @@ namespace SabreTools.IO.Writers
/// <param name="value">Value to write in the attribute</param>
/// <param name="quoteOverride">Non-null to overwrite the writer setting, null otherwise</param>
/// <param name="throwOnError">Indicates if an error should be thrown on a missing required value</param>
public void WriteRequiredAttributeString(string name, string value, bool? quoteOverride = null, bool throwOnError = false)
public void WriteRequiredAttributeString(string name, string? value, bool? quoteOverride = null, bool throwOnError = false)
{
// Throw an exception if we are configured to
if (value == null && throwOnError)
@@ -282,7 +279,7 @@ namespace SabreTools.IO.Writers
/// <param name="name">Name of the attribute</param>
/// <param name="value">Value to write in the attribute</param>
/// <param name="quoteOverride">Non-null to overwrite the writer setting, null otherwise</param>
public void WriteOptionalAttributeString(string name, string value, bool? quoteOverride = null)
public void WriteOptionalAttributeString(string name, string? value, bool? quoteOverride = null)
{
if (!string.IsNullOrEmpty(value))
WriteAttributeString(name, value, quoteOverride);
@@ -294,7 +291,7 @@ namespace SabreTools.IO.Writers
/// <param name="name">Name of the attribute</param>
/// <param name="value">Value to write in the attribute</param>
/// <param name="quoteOverride">Non-null to overwrite the writer setting, null otherwise</param>
public void WriteStandalone(string name, string value, bool? quoteOverride = null)
public void WriteStandalone(string name, string? value, bool? quoteOverride = null)
{
try
{
@@ -306,7 +303,7 @@ namespace SabreTools.IO.Writers
|| (quoteOverride == true))
{
name = name.Replace("\"", "''");
value = value.Replace("\"", "''");
value = value?.Replace("\"", "''");
}
AutoComplete(Token.Standalone);
@@ -338,7 +335,7 @@ namespace SabreTools.IO.Writers
/// <param name="value">Value to write in the attribute</param>
/// <param name="quoteOverride">Non-null to overwrite the writer setting, null otherwise</param>
/// <param name="throwOnError">Indicates if an error should be thrown on a missing required value</param>
public void WriteRequiredStandalone(string name, string value, bool? quoteOverride = null, bool throwOnError = false)
public void WriteRequiredStandalone(string name, string? value, bool? quoteOverride = null, bool throwOnError = false)
{
// Throw an exception if we are configured to
if (value == null && throwOnError)
@@ -353,7 +350,7 @@ namespace SabreTools.IO.Writers
/// <param name="name">Name of the attribute</param>
/// <param name="value">Value to write in the attribute</param>
/// <param name="quoteOverride">Non-null to overwrite the writer setting, null otherwise</param>
public void WriteOptionalStandalone(string name, string value, bool? quoteOverride = null)
public void WriteOptionalStandalone(string name, string? value, bool? quoteOverride = null)
{
if (!string.IsNullOrEmpty(value))
WriteStandalone(name, value, quoteOverride);
@@ -362,7 +359,7 @@ namespace SabreTools.IO.Writers
/// <summary>
/// Write a string content value
/// </summary>
public void WriteString(string value)
public void WriteString(string? value)
{
try
{

View File

@@ -9,7 +9,7 @@ namespace SabreTools.IO.Writers
/// <summary>
/// Internal stream writer for outputting
/// </summary>
private readonly StreamWriter sw;
private readonly StreamWriter? sw;
/// <summary>
/// Constructor for writing to a file
@@ -30,8 +30,11 @@ namespace SabreTools.IO.Writers
/// <summary>
/// Write a section tag
/// </summary>
public void WriteSection(string value)
public void WriteSection(string? value)
{
if (sw?.BaseStream == null)
return;
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Section tag cannot be null or empty", nameof(value));
@@ -41,8 +44,11 @@ namespace SabreTools.IO.Writers
/// <summary>
/// Write a key value pair
/// </summary>
public void WriteKeyValuePair(string key, string value)
public void WriteKeyValuePair(string key, string? value)
{
if (sw?.BaseStream == null)
return;
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Key cannot be null or empty", nameof(key));
@@ -53,8 +59,11 @@ namespace SabreTools.IO.Writers
/// <summary>
/// Write a comment
/// </summary>
public void WriteComment(string value)
public void WriteComment(string? value)
{
if (sw?.BaseStream == null)
return;
value ??= string.Empty;
sw.WriteLine($";{value}");
}
@@ -62,8 +71,11 @@ namespace SabreTools.IO.Writers
/// <summary>
/// Write a generic string
/// </summary>
public void WriteString(string value)
public void WriteString(string? value)
{
if (sw?.BaseStream == null)
return;
value ??= string.Empty;
sw.Write(value);
}
@@ -73,6 +85,9 @@ namespace SabreTools.IO.Writers
/// </summary>
public void WriteLine()
{
if (sw?.BaseStream == null)
return;
sw.WriteLine();
}
@@ -81,7 +96,7 @@ namespace SabreTools.IO.Writers
/// </summary>
public void Flush()
{
sw.Flush();
sw?.Flush();
}
/// <summary>
@@ -89,7 +104,7 @@ namespace SabreTools.IO.Writers
/// </summary>
public void Dispose()
{
sw.Dispose();
sw?.Dispose();
}
}
}

View File

@@ -60,7 +60,7 @@ namespace SabreTools.IO.Writers
/// <summary>
/// Write a header row
/// </summary>
public void WriteHeader(string[] headers)
public void WriteHeader(string?[] headers)
{
// If we haven't written anything out, we can write headers
if (!header && !firstRow)
@@ -72,7 +72,7 @@ namespace SabreTools.IO.Writers
/// <summary>
/// Write a value row
/// </summary>
public void WriteValues(object[] values, bool newline = true)
public void WriteValues(object?[] values, bool newline = true)
{
// If the writer can't be used, we error
if (sw == null || !sw.BaseStream.CanWrite)