Skip to content

Commit

Permalink
+ offsetInLines to allow header lines to be kept
Browse files Browse the repository at this point in the history
  • Loading branch information
Hawkynt committed Jun 6, 2024
1 parent a432109 commit dd416b8
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 47 deletions.
92 changes: 86 additions & 6 deletions Corlib.Extensions/System/IO/FileInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1704,7 +1704,23 @@ private static void _KeepFirstLines(FileInfo @this, int count, Encoding encoding
/// </code>
/// This example keeps the last 5 lines of "example.txt" and removes all other preceding lines.
/// </example>
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);

/// <summary>
/// Keeps only the specified number of last lines in the file, discarding the rest.
/// </summary>
/// <param name="this">The <see cref="FileInfo"/> object representing the file.</param>
/// <param name="count">The number of lines from the end of the file to keep.</param>
/// <param name="offsetInLines">The number of lines to keep at the start of the file.</param>
/// <example>
/// <code>
/// FileInfo fileInfo = new FileInfo("example.txt");
/// fileInfo.KeepLastLines(5, 1);
/// Console.WriteLine("Last 5 lines and the first kept, others removed.");
/// </code>
/// This example keeps the last 5 lines and the first of "example.txt" and removes all other preceding lines.
/// </example>
public static void KeepLastLines(this FileInfo @this, int count, int offsetInLines) => _KeepLastLines(@this, count, null, LineBreakMode.AutoDetect, offsetInLines);

/// <summary>
/// Keeps only the specified number of last lines in the file, discarding the rest, using the provided encoding.
Expand All @@ -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);
}

/// <summary>
/// Keeps only the specified number of last lines in the file, discarding the rest, using the provided encoding.
/// </summary>
/// <param name="this">The <see cref="FileInfo"/> object representing the file.</param>
/// <param name="count">The number of lines from the end of the file to keep.</param>
/// <param name="offsetInLines">The number of lines to keep at the start of the file.</param>
/// <param name="encoding">The encoding to use for interpreting the file's content.</param>
/// <example>
/// <code>
/// 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.");
/// </code>
/// This example keeps the last 5 lines and the first of "example.txt" using UTF-8 encoding and removes all other preceding lines.
/// </example>
public static void KeepLastLines(this FileInfo @this, int count, int offsetInLines, Encoding encoding) {
Against.ArgumentIsNull(encoding);

_KeepLastLines(@this, count, encoding, LineBreakMode.AutoDetect, offsetInLines);
}

/// <summary>
Expand All @@ -1740,7 +1777,24 @@ public static void KeepLastLines(this FileInfo @this, int count, Encoding encodi
/// </code>
/// This example keeps the last 5 lines of "example.txt" based on CrLf line breaks and removes all other preceding lines.
/// </example>
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);

/// <summary>
/// Keeps only the specified number of last lines in the file, discarding the rest, based on the specified line break mode.
/// </summary>
/// <param name="this">The <see cref="FileInfo"/> object representing the file.</param>
/// <param name="count">The number of lines from the end of the file to keep.</param>
/// <param name="offsetInLines">The number of lines to keep at the start of the file.</param>
/// <param name="newLine">The line break mode to determine the line endings in the file.</param>
/// <example>
/// <code>
/// 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.");
/// </code>
/// This example keeps the last 5 lines and the first of "example.txt" based on CrLf line breaks and removes all other preceding lines.
/// </example>
public static void KeepLastLines(this FileInfo @this, int count, int offsetInLines, LineBreakMode newLine) => _KeepLastLines(@this, count, null, newLine, offsetInLines);

/// <summary>
/// Keeps only the specified number of last lines in the file, discarding the rest, using the provided encoding and line break mode.
Expand All @@ -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);
}

/// <summary>
/// Keeps only the specified number of last lines in the file, discarding the rest, using the provided encoding and line break mode.
/// </summary>
/// <param name="this">The <see cref="FileInfo"/> object representing the file.</param>
/// <param name="count">The number of lines from the end of the file to keep.</param>
/// <param name="offsetInLines">The number of lines to keep at the start of the file.</param>
/// <param name="encoding">The encoding to use for interpreting the file's content.</param>
/// <param name="newLine">The line break mode to determine the line endings in the file.</param>
/// <example>
/// <code>
/// 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.");
/// </code>
/// 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.
/// </example>
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];
Expand All @@ -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)
Expand All @@ -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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
86 changes: 48 additions & 38 deletions Tests/Corlib.Tests/System/IO/FileInfoTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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<FileInfo?,int,Encoding?,LineBreakMode,bool> runner, string? input, int count, TestEncoding testEncoding, LineBreakMode newLine, string expected, Type? exception = null) {
private void _ExecuteTest(Action<FileInfo?, int, Encoding?, LineBreakMode, int, bool> runner, string? input, int count, TestEncoding testEncoding, LineBreakMode newLine, int offset, string expected, Type? exception = null) {
Encoding writeEncoding;
Encoding? readEncoding;
switch (testEncoding) {
Expand Down Expand Up @@ -471,21 +482,20 @@ private void _ExecuteTest(Action<FileInfo?,int,Encoding?,LineBreakMode,bool> 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 {
using var token = PathExtensions.GetTempFileToken();
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);
}
}


}

}

0 comments on commit dd416b8

Please sign in to comment.