From 450d8aab115b8e835e67b16d0792fd402396d454 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 25 Nov 2024 21:01:52 -0500 Subject: [PATCH] Update INI file and add tests --- SabreTools.IO.Test/IniFileTests.cs | 67 ++++++++++++++ SabreTools.IO/IniFile.cs | 138 +++++++++++++---------------- SabreTools.IO/Writers/IniWriter.cs | 2 +- 3 files changed, 132 insertions(+), 75 deletions(-) create mode 100644 SabreTools.IO.Test/IniFileTests.cs diff --git a/SabreTools.IO.Test/IniFileTests.cs b/SabreTools.IO.Test/IniFileTests.cs new file mode 100644 index 0000000..961c057 --- /dev/null +++ b/SabreTools.IO.Test/IniFileTests.cs @@ -0,0 +1,67 @@ +using System.IO; +using System.Text; +using Xunit; + +namespace SabreTools.IO.Test +{ + public class IniFileTests + { + [Fact] + public void EndToEndTest() + { + string expected = "[section1]\nkey1=value1\nkey2=value2\n"; + + // Build the INI + var iniFile = new IniFile(); + iniFile.AddOrUpdate("section1.key1", "value1"); + iniFile["section1.key2"] = "value2"; + iniFile["section2.key3"] = "REMOVEME"; + bool removed = iniFile.Remove("section2.key3"); + + Assert.True(removed); + Assert.Equal("value1", iniFile["section1.key1"]); + Assert.Equal("value2", iniFile["section1.key2"]); + + // Write the INI + var stream = new MemoryStream(); + bool write = iniFile.Write(stream); + + // Length includes UTF-8 BOM + Assert.True(write); + Assert.Equal(38, stream.Length); + string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3); + Assert.Equal(expected, actual); + + // Parse the INI + stream.Seek(0, SeekOrigin.Begin); + var secondIni = new IniFile(stream); + Assert.Equal("value1", secondIni["section1.key1"]); + Assert.Equal("value2", secondIni["section1.key2"]); + } + + [Fact] + public void RemoveInvalidKeyTest() + { + var iniFile = new IniFile(); + bool removed = iniFile.Remove("invalid.key"); + Assert.False(removed); + } + + [Fact] + public void ReadEmptyStreamTest() + { + var stream = new MemoryStream(); + var iniFile = new IniFile(stream); + Assert.Empty(iniFile); + } + + [Fact] + public void WriteEmptyIniFileTest() + { + var iniFile = new IniFile(); + var stream = new MemoryStream(); + bool write = iniFile.Write(stream); + Assert.False(write); + } + } +} \ No newline at end of file diff --git a/SabreTools.IO/IniFile.cs b/SabreTools.IO/IniFile.cs index d5c79f7..5e12b41 100644 --- a/SabreTools.IO/IniFile.cs +++ b/SabreTools.IO/IniFile.cs @@ -44,16 +44,19 @@ public IniFile() /// public IniFile(string path) { - Parse(path); + // If we don't have a file, we can't read it + if (!File.Exists(path)) + throw new FileNotFoundException(nameof(path)); + + using var fileStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + Parse(fileStream); } /// /// Populate an INI file from stream /// public IniFile(Stream stream) - { - Parse(stream); - } + => Parse(stream); /// /// Add or update a key and value to the INI file @@ -77,74 +80,6 @@ public bool Remove(string key) return false; } - /// - /// Read an INI file based on the path - /// - public bool Parse(string path) - { - // If we don't have a file, we can't read it - if (!File.Exists(path)) - return false; - - using var fileStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - return Parse(fileStream); - } - - /// - /// Read an INI file from a stream - /// - public bool Parse(Stream? stream) - { - // If the stream is invalid or unreadable, we can't process it - if (stream == null || !stream.CanRead || stream.Position >= stream.Length - 1) - return false; - - // Keys are case-insensitive by default - try - { - // TODO: Can we use the section header in the reader? - using var reader = new IniReader(stream, Encoding.UTF8); - - string? section = string.Empty; - while (!reader.EndOfStream) - { - // If we dont have a next line - if (!reader.ReadNextLine()) - break; - - // Process the row according to type - switch (reader.RowType) - { - case IniRowType.SectionHeader: - section = reader.Section; - break; - - case IniRowType.KeyValue: - string? key = reader.KeyValuePair?.Key; - - // Section names are prepended to the key with a '.' separating - if (!string.IsNullOrEmpty(section)) - key = $"{section}.{key}"; - - // Set or overwrite keys in the returned dictionary - this[key] = reader.KeyValuePair?.Value; - break; - - default: - // No-op - break; - } - } - } - catch - { - // We don't care what the error was, just catch and return - return false; - } - - return true; - } - /// /// Write an INI file to a path /// @@ -167,8 +102,8 @@ public bool Write(Stream stream) if (_keyValuePairs.Count == 0) return false; - // If the stream is invalid or unwritable, we can't output to it - if (stream == null || !stream.CanWrite || stream.Position >= stream.Length - 1) + // If the stream is invalid, we can't output to it + if (!stream.CanWrite) return false; try @@ -220,6 +155,61 @@ public bool Write(Stream stream) return true; } + /// + /// Read an INI file from a stream + /// + private bool Parse(Stream? stream) + { + // If the stream is invalid or unreadable, we can't process it + if (stream == null || !stream.CanRead || stream.Position >= stream.Length - 1) + return false; + + // Keys are case-insensitive by default + try + { + // TODO: Can we use the section header in the reader? + using var reader = new IniReader(stream, Encoding.UTF8); + + string? section = string.Empty; + while (!reader.EndOfStream) + { + // If we dont have a next line + if (!reader.ReadNextLine()) + break; + + // Process the row according to type + switch (reader.RowType) + { + case IniRowType.SectionHeader: + section = reader.Section; + break; + + case IniRowType.KeyValue: + string? key = reader.KeyValuePair?.Key; + + // Section names are prepended to the key with a '.' separating + if (!string.IsNullOrEmpty(section)) + key = $"{section}.{key}"; + + // Set or overwrite keys in the returned dictionary + this[key] = reader.KeyValuePair?.Value; + break; + + default: + // No-op + break; + } + } + } + catch + { + // We don't care what the error was, just catch and return + return false; + } + + return true; + } + #region IDictionary Impelementations public ICollection Keys => _keyValuePairs.Keys; diff --git a/SabreTools.IO/Writers/IniWriter.cs b/SabreTools.IO/Writers/IniWriter.cs index 2ce151f..742d31c 100644 --- a/SabreTools.IO/Writers/IniWriter.cs +++ b/SabreTools.IO/Writers/IniWriter.cs @@ -24,7 +24,7 @@ public IniWriter(string filename) /// public IniWriter(Stream stream, Encoding encoding) { - sw = new StreamWriter(stream, encoding); + sw = new StreamWriter(stream, encoding, 1024, leaveOpen: true); } ///