diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8035c6..b75be17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,9 +1,9 @@ name: Release env: - v: '5.1.2' + v: '5.1.3' av: '5.0.0' - pv: '5.1.2' + pv: '5.1.3' on: push: diff --git a/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs b/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs index 7af2131..339a76c 100644 --- a/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs +++ b/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs @@ -6,17 +6,15 @@ namespace Config.Net.Tests.Stores.Formats public class IniKeyValueTest { [Theory] - [InlineData("key=value", "key", "value")] - [InlineData("key=value;123", "key", "value")] - [InlineData("key==value", "key", "=value")] - [InlineData("key=value;value;value", "key", "value;value")] - [InlineData("key=value=value;value", "key", "value=value")] - public void FromLine_ParsingInlineComments(string input, string expectedKey, string expectedValue) + [InlineData("key=value", "key", "value", null)] + [InlineData("key=value;123", "key", "value", "123")] + [InlineData("key==value", "key", "=value", null)] + [InlineData("key=value;value;value", "key", "value;value", "value")] + [InlineData("key=value=value;value", "key", "value=value", "value")] + [InlineData("key=value;rest;", "key", "value;rest", "")] + public void FromLine_ParsingInlineComments(string input, string expectedKey, string expectedValue, string expectedComment) { - IniKeyValue kv = IniKeyValue.FromLine(input, true); - - Assert.Equal(expectedKey, kv.Key); - Assert.Equal(expectedValue, kv.Value); + FromLine_DoTest(true, false, input, expectedKey, expectedValue, expectedComment); } [Theory] @@ -27,11 +25,32 @@ public void FromLine_ParsingInlineComments(string input, string expectedKey, str [InlineData("key=value=value;value", "key", "value=value;value")] public void FromLine_IgnoringInlineComments(string input, string expectedKey, string expectedValue) { - IniKeyValue kv = IniKeyValue.FromLine(input, false); + FromLine_DoTest(false, false, input, expectedKey, expectedValue, null); + } + + [Theory] + [InlineData(true, @"k1\r\nk2=v1\r\nv2;c1\r\nc2", "k1\r\nk2", "v1\r\nv2", "c1\r\nc2")] + [InlineData(false, @"k1\r\nk2=v1\r\nv2;c1\r\nc2", @"k1\r\nk2", @"v1\r\nv2", @"c1\r\nc2")] + public void FromLine_UnescapeNewLines(bool unescapeNewLines, string input, string expectedKey, string expectedValue, string expectedComment) + { + FromLine_DoTest(true, unescapeNewLines, input, expectedKey, expectedValue, expectedComment); + } + + private static void FromLine_DoTest(bool parseInlineComments, bool unescapeNewLines, string input, string expectedKey, string expectedValue, string expectedComment) + { + IniKeyValue kv = IniKeyValue.FromLine(input, parseInlineComments, unescapeNewLines); Assert.Equal(expectedKey, kv.Key); Assert.Equal(expectedValue, kv.Value); - } + if (expectedComment == null) + { + Assert.Null(kv.Comment); + } + else + { + Assert.Equal(expectedComment, kv.Comment.Value); + } + } } } diff --git a/src/Config.Net.Tests/Stores/Formats/StructuredIniFileTest.cs b/src/Config.Net.Tests/Stores/Formats/StructuredIniFileTest.cs new file mode 100644 index 0000000..d96e32c --- /dev/null +++ b/src/Config.Net.Tests/Stores/Formats/StructuredIniFileTest.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; +using System.Text; +using Config.Net.Stores.Formats.Ini; +using Xunit; + +namespace Config.Net.Tests.Stores.Formats +{ + public class StructuredIniFileTest : AbstractTestFixture + { + [Fact] + public void WriteInlineCommentWithNewLine() + { + string fullPath = Path.Combine(TestDir.FullName, Guid.NewGuid() + ".ini"); + string content = @"key=value ;c1\r\nc2 +"; + + StructuredIniFile file; + using (Stream input = new MemoryStream(Encoding.UTF8.GetBytes(content))) + { + file = StructuredIniFile.ReadFrom(input, true, true); + } + + using (Stream output = File.OpenWrite(fullPath)) + { + file.WriteTo(output); + } + + string resultText = File.ReadAllText(fullPath); + Assert.Equal(content, resultText); + } + } +} diff --git a/src/Config.Net.Tests/Stores/IniFileConfigStoreTest.cs b/src/Config.Net.Tests/Stores/IniFileConfigStoreTest.cs index a25ce86..fd822d8 100644 --- a/src/Config.Net.Tests/Stores/IniFileConfigStoreTest.cs +++ b/src/Config.Net.Tests/Stores/IniFileConfigStoreTest.cs @@ -85,5 +85,23 @@ public void Write_NewFileWithNewValues_WritesCorrectText() key0=s2value0 ", resultText, false, true); } + + [Theory] + [InlineData("key", "l1\r\nl2", @"key=l1\r\nl2")] + [InlineData("key", "l1\rl2", @"key=l1\rl2")] + [InlineData("key", "l1\nl2", @"key=l1\nl2")] + [InlineData("k1\r\nk2", "value", @"k1\r\nk2=value")] + [InlineData("k1\rk2", "value", @"k1\rk2=value")] + [InlineData("k1\nk2", "value", @"k1\nk2=value")] + public void Write_NewLine(string key, string value, string expectedOutput) + { + string fullPath = Path.Combine(TestDir.FullName, Guid.NewGuid() + ".ini"); + var ini = new IniFileConfigStore(fullPath, true, true); + + ini.Write(key, value); + + string resultText = File.ReadAllText(fullPath); + Assert.Equal(expectedOutput + "\r\n", resultText); + } } } \ No newline at end of file diff --git a/src/Config.Net/Stores/Formats/Ini/IniComment.cs b/src/Config.Net/Stores/Formats/Ini/IniComment.cs index 9793ff4..0e83091 100644 --- a/src/Config.Net/Stores/Formats/Ini/IniComment.cs +++ b/src/Config.Net/Stores/Formats/Ini/IniComment.cs @@ -11,6 +11,11 @@ public IniComment(string value) public string Value { get; set; } + public string EscapedValue + { + get { return Value.Replace("\r", @"\r").Replace("\n", @"\n"); } + } + public override string ToString() => Value; } } diff --git a/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs b/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs index 9480726..c290d7c 100644 --- a/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs +++ b/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs @@ -18,9 +18,19 @@ public IniKeyValue(string key, string value, string? comment) public string Value { get; set; } + public string EscapedKey + { + get { return Key.Replace("\r", @"\r").Replace("\n", @"\n"); } + } + + public string EscapedValue + { + get { return Value.Replace("\r", @"\r").Replace("\n", @"\n"); } + } + public IniComment? Comment { get; } - public static IniKeyValue? FromLine(string line, bool parseInlineComments) + public static IniKeyValue? FromLine(string line, bool parseInlineComments, bool unescapeNewLines = false) { int idx = line.IndexOf(KeyValueSeparator, StringComparison.CurrentCulture); if(idx == -1) return null; @@ -39,9 +49,21 @@ public IniKeyValue(string key, string value, string? comment) } } + if(unescapeNewLines) + { + key = UnescapeString(key); + value = UnescapeString(value); + comment = (comment != null) ? UnescapeString(comment) : null; + } + return new IniKeyValue(key, value, comment); } + private static string UnescapeString(string key) + { + return key.Replace(@"\r", "\r").Replace(@"\n", "\n"); + } + public override string ToString() { return $"{Value}"; diff --git a/src/Config.Net/Stores/Formats/Ini/IniSection.cs b/src/Config.Net/Stores/Formats/Ini/IniSection.cs index e2cb1ab..d5074ff 100644 --- a/src/Config.Net/Stores/Formats/Ini/IniSection.cs +++ b/src/Config.Net/Stores/Formats/Ini/IniSection.cs @@ -93,12 +93,12 @@ public void WriteTo(StreamWriter writer) IniKeyValue? ikv = entity as IniKeyValue; if(ikv != null) { - writer.Write($"{ikv.Key}{IniKeyValue.KeyValueSeparator}{ikv.Value}"); + writer.Write($"{ikv.EscapedKey}{IniKeyValue.KeyValueSeparator}{ikv.EscapedValue}"); if(ikv.Comment != null) { writer.Write(" "); writer.Write(IniComment.CommentSeparator); - writer.Write(ikv.Comment.Value); + writer.Write(ikv.Comment.EscapedValue); } writer.WriteLine(); continue; diff --git a/src/Config.Net/Stores/Formats/Ini/StructuredIniFile.cs b/src/Config.Net/Stores/Formats/Ini/StructuredIniFile.cs index 3bc8a80..3b72f61 100644 --- a/src/Config.Net/Stores/Formats/Ini/StructuredIniFile.cs +++ b/src/Config.Net/Stores/Formats/Ini/StructuredIniFile.cs @@ -60,7 +60,7 @@ public string? this[string key] } } - public static StructuredIniFile ReadFrom(Stream inputStream, bool parseInlineComments) + public static StructuredIniFile ReadFrom(Stream inputStream, bool parseInlineComments, bool unescapeNewLines = false) { if(inputStream == null) throw new ArgumentNullException(nameof(inputStream)); @@ -90,7 +90,7 @@ public static StructuredIniFile ReadFrom(Stream inputStream, bool parseInlineCom } else { - IniKeyValue? ikv = IniKeyValue.FromLine(line, parseInlineComments); + IniKeyValue? ikv = IniKeyValue.FromLine(line, parseInlineComments, unescapeNewLines); if(ikv == null) continue; section.Add(ikv); diff --git a/src/Config.Net/Stores/IniFileConfigStore.cs b/src/Config.Net/Stores/IniFileConfigStore.cs index f77e8fc..8d16976 100644 --- a/src/Config.Net/Stores/IniFileConfigStore.cs +++ b/src/Config.Net/Stores/IniFileConfigStore.cs @@ -19,7 +19,7 @@ class IniFileConfigStore : IConfigStore /// r /// File does not have to exist, however it will be created as soon as you /// try to write to it - public IniFileConfigStore(string name, bool isFilePath, bool parseInlineComments) + public IniFileConfigStore(string name, bool isFilePath, bool parseInlineComments, bool unescapeNewLines = false) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -34,13 +34,13 @@ public IniFileConfigStore(string name, bool isFilePath, bool parseInlineComments Directory.CreateDirectory(parentDirPath); } - _iniFile = ReadIniFile(_fullName, parseInlineComments); + _iniFile = ReadIniFile(_fullName, parseInlineComments, unescapeNewLines); CanWrite = true; } else { - _iniFile = ReadIniContent(name, parseInlineComments); + _iniFile = ReadIniContent(name, parseInlineComments, unescapeNewLines); CanWrite = false; } @@ -78,14 +78,14 @@ public void Write(string key, string? value) WriteIniFile(); } - private static StructuredIniFile ReadIniFile(string fullName, bool parseInlineComments) + private static StructuredIniFile ReadIniFile(string fullName, bool parseInlineComments, bool unescapeNewLines = false) { FileInfo iniFile = new FileInfo(fullName); if(iniFile.Exists) { using(FileStream stream = iniFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { - return StructuredIniFile.ReadFrom(stream, parseInlineComments); + return StructuredIniFile.ReadFrom(stream, parseInlineComments, unescapeNewLines); } } else @@ -94,11 +94,11 @@ private static StructuredIniFile ReadIniFile(string fullName, bool parseInlineCo } } - private static StructuredIniFile ReadIniContent(string content, bool parseInlineComments) + private static StructuredIniFile ReadIniContent(string content, bool parseInlineComments, bool unescapeNewLines = false) { using (Stream input = new MemoryStream(Encoding.UTF8.GetBytes(content))) { - return StructuredIniFile.ReadFrom(input, parseInlineComments); + return StructuredIniFile.ReadFrom(input, parseInlineComments, unescapeNewLines); } }