diff --git a/Corlib.Extensions/System/IO/FileInfo.cs b/Corlib.Extensions/System/IO/FileInfo.cs index b21ad297..e0a0343c 100644 --- a/Corlib.Extensions/System/IO/FileInfo.cs +++ b/Corlib.Extensions/System/IO/FileInfo.cs @@ -1704,7 +1704,23 @@ private static void _KeepFirstLines(FileInfo @this, int count, Encoding encoding /// /// This example keeps the last 5 lines of "example.txt" and removes all other preceding lines. /// - public static void KeepLastLines(this FileInfo @this, int count) => _KeepLastLines(@this, count, null, LineBreakMode.AutoDetect); + public static void KeepLastLines(this FileInfo @this, int count) => _KeepLastLines(@this, count, null, LineBreakMode.AutoDetect, 0); + + /// + /// Keeps only the specified number of last lines in the file, discarding the rest. + /// + /// The object representing the file. + /// The number of lines from the end of the file to keep. + /// The number of lines to keep at the start of the file. + /// + /// + /// FileInfo fileInfo = new FileInfo("example.txt"); + /// fileInfo.KeepLastLines(5, 1); + /// Console.WriteLine("Last 5 lines and the first kept, others removed."); + /// + /// This example keeps the last 5 lines and the first of "example.txt" and removes all other preceding lines. + /// + public static void KeepLastLines(this FileInfo @this, int count, int offsetInLines) => _KeepLastLines(@this, count, null, LineBreakMode.AutoDetect, offsetInLines); /// /// Keeps only the specified number of last lines in the file, discarding the rest, using the provided encoding. @@ -1723,7 +1739,28 @@ private static void _KeepFirstLines(FileInfo @this, int count, Encoding encoding public static void KeepLastLines(this FileInfo @this, int count, Encoding encoding) { Against.ArgumentIsNull(encoding); - _KeepLastLines(@this, count, encoding, LineBreakMode.AutoDetect); + _KeepLastLines(@this, count, encoding, LineBreakMode.AutoDetect, 0); + } + + /// + /// Keeps only the specified number of last lines in the file, discarding the rest, using the provided encoding. + /// + /// The object representing the file. + /// The number of lines from the end of the file to keep. + /// The number of lines to keep at the start of the file. + /// The encoding to use for interpreting the file's content. + /// + /// + /// FileInfo fileInfo = new FileInfo("example.txt"); + /// fileInfo.KeepLastLines(5, 1, Encoding.UTF8); + /// Console.WriteLine("Last 5 lines and the first kept using UTF-8 encoding, others removed."); + /// + /// This example keeps the last 5 lines and the first of "example.txt" using UTF-8 encoding and removes all other preceding lines. + /// + public static void KeepLastLines(this FileInfo @this, int count, int offsetInLines, Encoding encoding) { + Against.ArgumentIsNull(encoding); + + _KeepLastLines(@this, count, encoding, LineBreakMode.AutoDetect, offsetInLines); } /// @@ -1740,7 +1777,24 @@ public static void KeepLastLines(this FileInfo @this, int count, Encoding encodi /// /// This example keeps the last 5 lines of "example.txt" based on CrLf line breaks and removes all other preceding lines. /// - public static void KeepLastLines(this FileInfo @this, int count, LineBreakMode newLine) => _KeepLastLines(@this, count, null, newLine); + public static void KeepLastLines(this FileInfo @this, int count, LineBreakMode newLine) => _KeepLastLines(@this, count, null, newLine, 0); + + /// + /// Keeps only the specified number of last lines in the file, discarding the rest, based on the specified line break mode. + /// + /// The object representing the file. + /// The number of lines from the end of the file to keep. + /// The number of lines to keep at the start of the file. + /// The line break mode to determine the line endings in the file. + /// + /// + /// FileInfo fileInfo = new FileInfo("example.txt"); + /// fileInfo.KeepLastLines(5, 1, LineBreakMode.CrLf); + /// Console.WriteLine("Last 5 lines and the first kept using CrLf line breaks, others removed."); + /// + /// This example keeps the last 5 lines and the first of "example.txt" based on CrLf line breaks and removes all other preceding lines. + /// + public static void KeepLastLines(this FileInfo @this, int count, int offsetInLines, LineBreakMode newLine) => _KeepLastLines(@this, count, null, newLine, offsetInLines); /// /// Keeps only the specified number of last lines in the file, discarding the rest, using the provided encoding and line break mode. @@ -1760,13 +1814,36 @@ public static void KeepLastLines(this FileInfo @this, int count, Encoding encodi public static void KeepLastLines(this FileInfo @this, int count, Encoding encoding, LineBreakMode newLine) { Against.ArgumentIsNull(encoding); - _KeepLastLines(@this, count, encoding, newLine); + _KeepLastLines(@this, count, encoding, newLine, 0); + } + + /// + /// Keeps only the specified number of last lines in the file, discarding the rest, using the provided encoding and line break mode. + /// + /// The object representing the file. + /// The number of lines from the end of the file to keep. + /// The number of lines to keep at the start of the file. + /// The encoding to use for interpreting the file's content. + /// The line break mode to determine the line endings in the file. + /// + /// + /// FileInfo fileInfo = new FileInfo("example.txt"); + /// fileInfo.KeepLastLines(5, 1, Encoding.UTF8, LineBreakMode.CrLf); + /// Console.WriteLine("Last 5 lines and the first kept using UTF-8 encoding and CrLf line breaks, others removed."); + /// + /// This example keeps the last 5 lines and the first of "example.txt" using UTF-8 encoding and CrLf line breaks, removing all other preceding lines. + /// + public static void KeepLastLines(this FileInfo @this, int count, int offsetInLines, Encoding encoding, LineBreakMode newLine) { + Against.ArgumentIsNull(encoding); + + _KeepLastLines(@this, count, encoding, newLine, offsetInLines); } - private static void _KeepLastLines(FileInfo @this, int count, Encoding encoding, LineBreakMode newLine) { + private static void _KeepLastLines(FileInfo @this, int count, Encoding encoding, LineBreakMode newLine, int offsetInLines) { Against.ThisIsNull(@this); Against.CountBelowOrEqualZero(count); Against.UnknownEnumValues(newLine); + Against.NegativeValues(offsetInLines); using var stream = @this.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None); var linePositions = new long[count]; @@ -1778,6 +1855,10 @@ private static void _KeepLastLines(FileInfo @this, int count, Encoding encoding, : new(stream, encoding, newLine) ; + var writePosition = reader.PreambleSize; + while (offsetInLines-- > 0 && reader.ReadLine()!=null) + writePosition = stream.Position; + for (;;) { var startOfLine = stream.Position; if (reader.ReadLine() == null) @@ -1792,7 +1873,6 @@ private static void _KeepLastLines(FileInfo @this, int count, Encoding encoding, return; --readPosition; - var writePosition = reader.PreambleSize; const int bufferSize = 64 * 1024; var buffer = new byte[bufferSize]; diff --git a/Corlib.Extensions/System/IO/FileInfoExtensions.CustomTextReader.cs b/Corlib.Extensions/System/IO/FileInfoExtensions.CustomTextReader.cs index 278bafe6..ddc1cc7e 100644 --- a/Corlib.Extensions/System/IO/FileInfoExtensions.CustomTextReader.cs +++ b/Corlib.Extensions/System/IO/FileInfoExtensions.CustomTextReader.cs @@ -323,9 +323,9 @@ public Initialized(Stream stream, bool detectEncodingFromByteOrderMark, StringEx stream, detectEncodingFromByteOrderMark, null, lineBreakMode) { } public Initialized(Stream stream, Encoding encoding, StringExtensions.LineBreakMode lineBreakMode = StringExtensions.LineBreakMode.AutoDetect) - : this(stream, false, encoding, lineBreakMode) { - Against.ArgumentIsNull(encoding); - } + : this(stream, false, encoding, lineBreakMode) + => Against.ArgumentIsNull(encoding) + ; public long PreambleSize { get; } diff --git a/Tests/Corlib.Tests/System/IO/FileInfoTest.cs b/Tests/Corlib.Tests/System/IO/FileInfoTest.cs index ab09e30a..e0f5e858 100644 --- a/Tests/Corlib.Tests/System/IO/FileInfoTest.cs +++ b/Tests/Corlib.Tests/System/IO/FileInfoTest.cs @@ -335,43 +335,54 @@ public enum TestEncoding { [TestCase("ab\nc\x0076def", 1, TestEncoding.Utf8, LineBreakMode.Zx, "ab\nc\x76")] [TestCase("ab\nc\0def", 1, TestEncoding.Utf8, LineBreakMode.Null, "ab\nc\0")] public void KeepFirstLines(string? input, int count, TestEncoding testEncoding, LineBreakMode newLine, string expected, Type? exception = null) - => this._ExecuteTest((f, c, e, l, a) => { + => this._ExecuteTest((f, c, e, l, o,a) => { if (a) f.KeepFirstLines(c, l); else f.KeepFirstLines(c, e, l); - }, input, count, testEncoding, newLine, expected, exception) + }, input, count, testEncoding, newLine, 0,expected, exception) ; [Test] - [TestCase(null, 1, TestEncoding.Utf8, LineBreakMode.LineFeed, null, typeof(NullReferenceException))] - [TestCase("", 0, TestEncoding.Utf8, LineBreakMode.LineFeed, null, typeof(ArgumentOutOfRangeException))] - [TestCase("", 1, TestEncoding.Null, LineBreakMode.LineFeed, null, typeof(ArgumentNullException))] - [TestCase("abc", 1, TestEncoding.ASCII, (LineBreakMode)short.MinValue, "", typeof(ArgumentException))] - [TestCase("abc", 1, TestEncoding.ASCII, LineBreakMode.None, "abc")] - [TestCase("abc\n", 1, TestEncoding.ASCII, LineBreakMode.LineFeed, "abc\n")] - [TestCase("abc\ndef", 1, TestEncoding.ASCII, LineBreakMode.LineFeed, "def")] - [TestCase("abc\ndef\n", 1, TestEncoding.ASCII, LineBreakMode.LineFeed, "def\n")] - [TestCase("abc\r\ndef", 1, TestEncoding.ASCII, LineBreakMode.CrLf, "def")] - [TestCase("abc\r\ndef\r\n", 1, TestEncoding.ASCII, LineBreakMode.CrLf, "def\r\n")] - [TestCase("abc\r\ndef", 1, TestEncoding.ASCII, LineBreakMode.All, "def")] - [TestCase("abc\r\ndef", 1, TestEncoding.ASCII, LineBreakMode.AutoDetect, "def")] - [TestCase("abc\rdef\r", 1, TestEncoding.AutoDetectFromBom, LineBreakMode.CarriageReturn, "def\r")] - [TestCase("abc\x000cdef", 1, TestEncoding.UnicodeBigEndian, LineBreakMode.FormFeed, "def")] - [TestCase("abc\x0085de\ff", 1, TestEncoding.UnicodeLittleEndianNoBOM, LineBreakMode.NextLine, "de\ff")] - [TestCase("abc\x0015de\u0085f", 1, TestEncoding.Utf8, LineBreakMode.NegativeAcknowledge, "de\u0085f")] - [TestCase("abc\x2028de\rf", 1, TestEncoding.Utf8NoBOM, LineBreakMode.LineSeparator, "de\rf")] - [TestCase("abc\x2029de\nf", 1, TestEncoding.Utf8, LineBreakMode.ParagraphSeparator, "de\nf")] - [TestCase("abc\x009Bde\nf", 1, TestEncoding.Utf8, LineBreakMode.EndOfLine, "de\nf")] - [TestCase("abc\x0076de\nf", 1, TestEncoding.Utf8, LineBreakMode.Zx, "de\nf")] - [TestCase("abc\0de\nf", 1, TestEncoding.Utf8, LineBreakMode.Null, "de\nf")] - public void KeepLastLines(string? input, int count, TestEncoding testEncoding, LineBreakMode newLine, string expected, Type? exception = null) - => this._ExecuteTest((f, c, e, l, a) => { - if (a) - f.KeepLastLines(c, l); - else - f.KeepLastLines(c, e, l); - }, input, count, testEncoding, newLine, expected, exception) + [TestCase(null, 1, TestEncoding.Utf8, LineBreakMode.LineFeed, 0, null, typeof(NullReferenceException))] + [TestCase("", 0, TestEncoding.Utf8, LineBreakMode.LineFeed, 0, null, typeof(ArgumentOutOfRangeException))] + [TestCase("", 1, TestEncoding.Null, LineBreakMode.LineFeed, 0, null, typeof(ArgumentNullException))] + [TestCase("abc", 1, TestEncoding.ASCII, (LineBreakMode)short.MinValue, 0, "", typeof(ArgumentException))] + [TestCase("abc", 1, TestEncoding.ASCII, LineBreakMode.None, 0, "abc")] + [TestCase("abc\n", 1, TestEncoding.ASCII, LineBreakMode.LineFeed, 0, "abc\n")] + [TestCase("abc\ndef", 1, TestEncoding.ASCII, LineBreakMode.LineFeed, 0, "def")] + [TestCase("abc\ndef\n", 1, TestEncoding.ASCII, LineBreakMode.LineFeed, 0, "def\n")] + [TestCase("abc\r\ndef", 1, TestEncoding.ASCII, LineBreakMode.CrLf, 0, "def")] + [TestCase("abc\r\ndef\r\n", 1, TestEncoding.ASCII, LineBreakMode.CrLf, 0, "def\r\n")] + [TestCase("abc\r\ndef", 1, TestEncoding.ASCII, LineBreakMode.All, 0, "def")] + [TestCase("abc\r\ndef", 1, TestEncoding.ASCII, LineBreakMode.AutoDetect, 0, "def")] + [TestCase("abc\rdef\r", 1, TestEncoding.AutoDetectFromBom, LineBreakMode.CarriageReturn, 0, "def\r")] + [TestCase("abc\x000cdef", 1, TestEncoding.UnicodeBigEndian, LineBreakMode.FormFeed, 0, "def")] + [TestCase("abc\x0085de\ff", 1, TestEncoding.UnicodeLittleEndianNoBOM, LineBreakMode.NextLine, 0, "de\ff")] + [TestCase("abc\x0015de\u0085f", 1, TestEncoding.Utf8, LineBreakMode.NegativeAcknowledge, 0, "de\u0085f")] + [TestCase("abc\x2028de\rf", 1, TestEncoding.Utf8NoBOM, LineBreakMode.LineSeparator, 0, "de\rf")] + [TestCase("abc\x2029de\nf", 1, TestEncoding.Utf8, LineBreakMode.ParagraphSeparator, 0, "de\nf")] + [TestCase("abc\x009Bde\nf", 1, TestEncoding.Utf8, LineBreakMode.EndOfLine, 0, "de\nf")] + [TestCase("abc\x0076de\nf", 1, TestEncoding.Utf8, LineBreakMode.Zx, 0, "de\nf")] + [TestCase("abc\0de\nf", 1, TestEncoding.Utf8, LineBreakMode.Null, 0, "de\nf")] + [TestCase("abc\n", 1, TestEncoding.ASCII, LineBreakMode.LineFeed, -1, "abc\n",typeof(ArgumentOutOfRangeException))] + [TestCase("abc\n", 1, TestEncoding.ASCII, LineBreakMode.LineFeed, 1, "abc\n")] + [TestCase("abc\ndef\n", 1, TestEncoding.ASCII, LineBreakMode.LineFeed, 1, "abc\ndef\n")] + [TestCase("abc\ndef\nghi\n", 1, TestEncoding.ASCII, LineBreakMode.LineFeed, 1, "abc\nghi\n")] + public void KeepLastLines(string? input, int count, TestEncoding testEncoding, LineBreakMode newLine, int offset, string expected, Type? exception = null) + => this._ExecuteTest((f, c, e, l, o, a) => { + if (o != 0) { + if (a) + f.KeepLastLines(c, o, l); + else + f.KeepLastLines(c, o, e, l); + } else{ + if (a) + f.KeepLastLines(c, l); + else + f.KeepLastLines(c, e, l); + } + }, input, count, testEncoding, newLine, offset, expected, exception) ; [Test] @@ -397,12 +408,12 @@ public void KeepLastLines(string? input, int count, TestEncoding testEncoding, L [TestCase("abc\x0076de\nf", 1, TestEncoding.Utf8, LineBreakMode.Zx, "de\nf")] [TestCase("abc\0de\nf", 1, TestEncoding.Utf8, LineBreakMode.Null, "de\nf")] public void RemoveFirstLines(string? input, int count, TestEncoding testEncoding, LineBreakMode newLine, string expected, Type? exception = null) - => this._ExecuteTest((f, c, e, l, a) => { + => this._ExecuteTest((f, c, e, l,o, a) => { if (a) f.RemoveFirstLines(c, l); else f.RemoveFirstLines(c, e, l); - }, input, count, testEncoding, newLine, expected, exception) + }, input, count, testEncoding, newLine,0, expected, exception) ; [Test] @@ -428,15 +439,15 @@ public void RemoveFirstLines(string? input, int count, TestEncoding testEncoding [TestCase("ab\nc\x0076def", 1, TestEncoding.Utf8, LineBreakMode.Zx, "ab\nc\x76")] [TestCase("ab\nc\0def", 1, TestEncoding.Utf8, LineBreakMode.Null, "ab\nc\0")] public void RemoveLastLines(string? input,int count, TestEncoding testEncoding, LineBreakMode newLine,string expected, Type? exception=null) - => this._ExecuteTest((f, c, e, l, a) => { + => this._ExecuteTest((f, c, e, l,o, a) => { if (a) f.RemoveLastLines(c, l); else f.RemoveLastLines(c, e, l); - }, input,count,testEncoding,newLine,expected,exception) + }, input,count,testEncoding,newLine,0,expected,exception) ; - private void _ExecuteTest(Action runner, string? input, int count, TestEncoding testEncoding, LineBreakMode newLine, string expected, Type? exception = null) { + private void _ExecuteTest(Action runner, string? input, int count, TestEncoding testEncoding, LineBreakMode newLine, int offset, string expected, Type? exception = null) { Encoding writeEncoding; Encoding? readEncoding; switch (testEncoding) { @@ -471,7 +482,7 @@ private void _ExecuteTest(Action run if (input == null) { file = null; ExecuteTest(() => { - runner(file, count, readEncoding, newLine,false); + runner(file, count, readEncoding, newLine, offset, false); return file.ReadAllText(writeEncoding); }, expected, exception); } else { @@ -479,13 +490,12 @@ private void _ExecuteTest(Action run file = token.File; file.WriteAllText(input, writeEncoding); ExecuteTest(() => { - runner(file, count, readEncoding, newLine,testEncoding==TestEncoding.AutoDetectFromBom); + runner(file, count, readEncoding, newLine, offset, testEncoding == TestEncoding.AutoDetectFromBom); return file.ReadAllText(writeEncoding); }, expected, exception); } } - } } \ No newline at end of file