27 Commits
1.3.7 ... 1.4.0

Author SHA1 Message Date
Matt Nadareski
8df3fe2473 Bump version 2024-04-23 14:06:04 -04:00
Matt Nadareski
a2bb83ab9a Make Linux publish script executable 2024-04-23 14:05:37 -04:00
Matt Nadareski
bcb77f2de6 Add publish scripts 2024-04-23 14:05:19 -04:00
Matt Nadareski
35d4c22a20 Clean usings 2024-04-23 14:01:51 -04:00
Matt Nadareski
893cb73b0d Slight formatting update 2024-04-23 13:40:39 -04:00
Matt Nadareski
607a0375c7 Better(?) string reading extensions 2024-04-23 13:23:09 -04:00
Matt Nadareski
87d1dfe266 Add ReadType extensions 2024-04-23 10:51:54 -04:00
Matt Nadareski
d8f16b12b5 Use correct variable name in BinaryReader extensions 2024-04-23 09:52:53 -04:00
Matt Nadareski
cf08658b1e Add tests for default BinaryReader methods for consistency 2024-04-23 09:38:58 -04:00
Matt Nadareski
9547e1a355 Add decimal extensions for byte array and stream 2024-04-23 09:34:15 -04:00
Matt Nadareski
20cdb5b65e Fix summary of BinaryReaderExtensions 2024-04-22 15:25:32 -04:00
Matt Nadareski
fc10565186 Add Int128 extensions for BinaryReader 2024-04-22 15:22:14 -04:00
Matt Nadareski
c824db6b18 Add GUID tests for BinaryReader 2024-04-22 15:18:00 -04:00
Matt Nadareski
a3c26fed38 Add GUID extensions for BinaryReader 2024-04-22 15:15:02 -04:00
Matt Nadareski
2dc259d978 Reorganize binary reader extensions a bit 2024-04-22 15:13:05 -04:00
Matt Nadareski
2d950ddc54 Add float and double tests for BinaryReader extensions 2024-04-22 15:02:04 -04:00
Matt Nadareski
294c5c26df Simplify ParentablePath logic a bit 2024-04-22 01:10:02 -04:00
Matt Nadareski
dc356767ab Simplify normalization for comparison 2024-04-22 00:32:41 -04:00
Matt Nadareski
0b6c7e9885 Whitespace 2024-04-22 00:30:50 -04:00
Matt Nadareski
339da9fc16 Hah, duplicate 2024-04-22 00:22:52 -04:00
Matt Nadareski
7bc18c6952 Ensure tests aren't packable 2024-04-22 00:17:13 -04:00
Matt Nadareski
ec3afeed73 Use new enumeration wrappers 2024-04-18 16:59:25 -04:00
Matt Nadareski
658ceb5d0e Add fake Enumeration versions for net20 and net35 2024-04-18 16:57:21 -04:00
Matt Nadareski
e9a905d4a3 Use extensions in PathTool 2024-04-18 16:47:36 -04:00
Matt Nadareski
1127e96f26 Convert ListEmpty to use new safe enumerators 2024-04-18 15:34:44 -04:00
Matt Nadareski
89a8ad3703 Add safe enumeration and file enumeration 2024-04-18 15:31:38 -04:00
Matt Nadareski
ff7f7c0b8c Fix namespace for extension tests 2024-04-18 15:06:27 -04:00
16 changed files with 1430 additions and 163 deletions

View File

@@ -1,8 +1,10 @@
using System;
using System.IO;
using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test
namespace SabreTools.IO.Test.Extensions
{
public class BinaryReaderExtensionsTests
{
@@ -12,6 +14,15 @@ namespace SabreTools.IO.Test
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
[Fact]
public void ReadInt16Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
short read = br.ReadInt16();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadInt16BigEndianTest()
{
@@ -21,6 +32,15 @@ namespace SabreTools.IO.Test
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadUInt16Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ushort read = br.ReadUInt16();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadUInt16BigEndianTest()
{
@@ -30,6 +50,15 @@ namespace SabreTools.IO.Test
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadInt32Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.ReadInt32();
Assert.Equal(0x03020100, read);
}
[Fact]
public void ReadInt32BigEndianTest()
{
@@ -39,6 +68,15 @@ namespace SabreTools.IO.Test
Assert.Equal(0x00010203, read);
}
[Fact]
public void ReadUInt32Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
uint read = br.ReadUInt32();
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadUInt32BigEndianTest()
{
@@ -48,6 +86,35 @@ namespace SabreTools.IO.Test
Assert.Equal((uint)0x00010203, read);
}
[Fact]
public void ReadSingleTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
float expected = BitConverter.Int32BitsToSingle(0x03020100);
float read = br.ReadSingle();
Assert.Equal(expected, read);
}
[Fact]
public void ReadSingleBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
float expected = BitConverter.Int32BitsToSingle(0x00010203);
float read = br.ReadSingleBigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadInt64Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
long read = br.ReadInt64();
Assert.Equal(0x0706050403020100, read);
}
[Fact]
public void ReadInt64BigEndianTest()
{
@@ -57,6 +124,15 @@ namespace SabreTools.IO.Test
Assert.Equal(0x0001020304050607, read);
}
[Fact]
public void ReadUInt64Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ulong read = br.ReadUInt64();
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadUInt64BigEndianTest()
{
@@ -66,8 +142,130 @@ namespace SabreTools.IO.Test
Assert.Equal((ulong)0x0001020304050607, read);
}
[Fact]
public void ReadDoubleTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
double expected = BitConverter.Int64BitsToDouble(0x0706050403020100);
double read = br.ReadDouble();
Assert.Equal(expected, read);
}
[Fact]
public void ReadDoubleBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
double expected = BitConverter.Int64BitsToDouble(0x0001020304050607);
double read = br.ReadDoubleBigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadGuidTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new Guid(_bytes);
Guid read = br.ReadGuid();
Assert.Equal(expected, read);
}
[Fact]
public void ReadGuidBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new Guid(_bytes.Reverse().ToArray());
Guid read = br.ReadGuidBigEndian();
Assert.Equal(expected, read);
}
#if NET7_0_OR_GREATER
[Fact]
public void ReadInt128Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new Int128(BitConverter.ToUInt64(_bytes, 0), BitConverter.ToUInt64(_bytes, 8));
Int128 read = br.ReadInt128();
Assert.Equal(expected, read);
}
[Fact]
public void ReadInt128BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var reversed = _bytes.Reverse().ToArray();
var expected = new Int128(BitConverter.ToUInt64(reversed, 0), BitConverter.ToUInt64(reversed, 8));
Int128 read = br.ReadInt128BigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new UInt128(BitConverter.ToUInt64(_bytes, 0), BitConverter.ToUInt64(_bytes, 8));
UInt128 read = br.ReadUInt128();
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var reversed = _bytes.Reverse().ToArray();
var expected = new UInt128(BitConverter.ToUInt64(reversed, 0), BitConverter.ToUInt64(reversed, 8));
UInt128 read = br.ReadUInt128BigEndian();
Assert.Equal(expected, read);
}
#endif
[Fact]
public void ReadTypeExplicitTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new TestStructExplicit
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
};
var read = br.ReadType<TestStructExplicit>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
[Fact]
public void ReadTypeSequentialTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new TestStructSequential
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
};
var read = br.ReadType<TestStructSequential>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
// TODO: Add byte[], char[] tests
// TODO: Add float, double tests
// TODO: Add decimal tests
// TODO: Add string reading tests
}
}

View File

@@ -3,7 +3,7 @@ using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test
namespace SabreTools.IO.Test.Extensions
{
public class ByteArrayExtensionsTests
{
@@ -196,7 +196,7 @@ namespace SabreTools.IO.Test
}
[Fact]
public void ReadGuidBigEndian()
public void ReadGuidBigEndianTest()
{
int offset = 0;
var expected = new Guid(_bytes.Reverse().ToArray());
@@ -244,6 +244,43 @@ namespace SabreTools.IO.Test
}
#endif
[Fact]
public void ReadTypeExplicitTest()
{
int offset = 0;
var expected = new TestStructExplicit
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
};
var read = _bytes.ReadType<TestStructExplicit>(ref offset);
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
[Fact]
public void ReadTypeSequentialTest()
{
int offset = 0;
var expected = new TestStructSequential
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
};
var read = _bytes.ReadType<TestStructSequential>(ref offset);
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
// TODO: Add decimal tests
// TODO: Add string reading tests
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
public class EnumerableExtensionsTests
{
[Fact]
public void SafeEnumerateEmptyTest()
{
var source = Enumerable.Empty<string>();
var safe = source.SafeEnumerate();
var list = safe.ToList();
Assert.Empty(list);
}
[Fact]
public void SafeEnumerateNoErrorTest()
{
var source = new List<string> { "a", "ab", "abc" };
var safe = source.SafeEnumerate();
var list = safe.ToList();
Assert.Equal(3, list.Count);
}
[Fact]
public void SafeEnumerateErrorMidTest()
{
var source = new List<string> { "a", "ab", "abc" };
var wrapper = new ErrorEnumerable(source);
var safe = wrapper.SafeEnumerate();
var list = safe.ToList();
Assert.Equal(2, list.Count);
}
[Fact]
public void SafeEnumerateErrorLastTest()
{
var source = new List<string> { "a", "ab", "abc", "abcd" };
var wrapper = new ErrorEnumerable(source);
var safe = wrapper.SafeEnumerate();
var list = safe.ToList();
Assert.Equal(2, list.Count);
}
/// <summary>
/// Fake enumerable that uses <see cref="ErrorEnumerator"/>
/// </summary>
private class ErrorEnumerable : IEnumerable<string>
{
/// <summary>
/// Enumerator to use during enumeration
/// </summary>
private readonly ErrorEnumerator _enumerator;
public ErrorEnumerable(IEnumerable<string> source)
{
_enumerator = new ErrorEnumerator(source);
}
/// <inheritdoc/>
public IEnumerator<string> GetEnumerator() => _enumerator;
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _enumerator;
}
/// <summary>
/// Fake enumerator that throws an exception every other item while moving to the next item
/// </summary>
private class ErrorEnumerator : IEnumerator<string>
{
/// <inheritdoc/>
public string Current
{
get
{
if (_index == -1)
throw new InvalidOperationException();
return _enumerator.Current;
}
}
/// <inheritdoc/>
object IEnumerator.Current => Current;
/// <summary>
/// Enumerator from the source enumerable
/// </summary>
private readonly IEnumerator<string> _enumerator;
/// <summary>
/// Enumerators start before the data
/// </summary>
private int _index = -1;
public ErrorEnumerator(IEnumerable<string> source)
{
_enumerator = source.GetEnumerator();
}
/// <inheritdoc/>
public void Dispose() { }
/// <inheritdoc/>
public bool MoveNext()
{
// Move to the next item, if possible
bool moved = _enumerator.MoveNext();
if (!moved)
return false;
// Get the next real item
_index++;
// Every other move, throw an exception
if (_index % 2 == 1)
throw new Exception("Access issue for this item in the enumerable");
return true;
}
/// <inheritdoc/>
public void Reset()
{
_enumerator.Reset();
_index = -1;
}
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test
namespace SabreTools.IO.Test.Extensions
{
public class StreamExtensionsTests
{
@@ -190,7 +190,7 @@ namespace SabreTools.IO.Test
}
[Fact]
public void ReadGuidBigEndian()
public void ReadGuidBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var expected = new Guid(_bytes.Reverse().ToArray());
@@ -238,6 +238,43 @@ namespace SabreTools.IO.Test
}
#endif
[Fact]
public void ReadTypeExplicitTest()
{
var stream = new MemoryStream(_bytes);
var expected = new TestStructExplicit
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
};
var read = stream.ReadType<TestStructExplicit>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
[Fact]
public void ReadTypeSequentialTest()
{
var stream = new MemoryStream(_bytes);
var expected = new TestStructSequential
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
};
var read = stream.ReadType<TestStructSequential>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
// TODO: Add decimal tests
// TODO: Add string reading tests
}
}

View File

@@ -0,0 +1,20 @@
using System.Runtime.InteropServices;
namespace SabreTools.IO.Test.Extensions
{
[StructLayout(LayoutKind.Explicit)]
internal struct TestStructExplicit
{
[FieldOffset(0)]
public int FirstValue;
[FieldOffset(4)]
public int SecondValue;
[FieldOffset(4)]
public ushort ThirdValue;
[FieldOffset(6)]
public short FourthValue;
}
}

View File

@@ -0,0 +1,16 @@
using System.Runtime.InteropServices;
namespace SabreTools.IO.Test.Extensions
{
[StructLayout(LayoutKind.Sequential)]
internal struct TestStructSequential
{
public int FirstValue;
public int SecondValue;
public ushort ThirdValue;
public short FourthValue;
}
}

View File

@@ -1,11 +1,15 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Big endian reading overloads for BinaryReader
/// Extensions for BinaryReader
/// </summary>
/// <remarks>TODO: Add U/Int24 and U/Int48 methods</remarks>
public static class BinaryReaderExtensions
{
/// <inheritdoc cref="BinaryReader.Read(byte[], int, int)"/>
@@ -30,104 +34,319 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static byte[] ReadBytesBigEndian(this BinaryReader reader, int count)
{
byte[] retval = reader.ReadBytes(count);
Array.Reverse(retval);
return retval;
byte[] buffer = reader.ReadBytes(count);
Array.Reverse(buffer);
return buffer;
}
/// <inheritdoc cref="BinaryReader.ReadChars(int)"/>
/// <remarks>Reads in big-endian format</remarks>
public static char[] ReadCharsBigEndian(this BinaryReader reader, int count)
{
char[] retval = reader.ReadChars(count);
Array.Reverse(retval);
return retval;
}
/// <inheritdoc cref="BinaryReader.ReadDecimal"/>
/// <remarks>Reads in big-endian format</remarks>
public static decimal ReadDecimalBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(16);
Array.Reverse(retval);
int i1 = BitConverter.ToInt32(retval, 0);
int i2 = BitConverter.ToInt32(retval, 4);
int i3 = BitConverter.ToInt32(retval, 8);
int i4 = BitConverter.ToInt32(retval, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <inheritdoc cref="BinaryReader.ReadDouble"/>
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToDouble(retval, 0);
char[] buffer = reader.ReadChars(count);
Array.Reverse(buffer);
return buffer;
}
/// <inheritdoc cref="BinaryReader.ReadInt16"/>
/// <remarks>Reads in big-endian format</remarks>
public static short ReadInt16BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToInt16(retval, 0);
}
/// <inheritdoc cref="BinaryReader.ReadInt32"/>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToInt32(retval, 0);
}
/// <inheritdoc cref="BinaryReader.ReadInt64"/>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToInt64(retval, 0);
}
/// <inheritdoc cref="BinaryReader.ReadSingle"/>
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToSingle(retval, 0);
byte[] buffer = reader.ReadBytes(2);
Array.Reverse(buffer);
return BitConverter.ToInt16(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadUInt16"/>
/// <remarks>Reads in big-endian format</remarks>
public static ushort ReadUInt16BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToUInt16(retval, 0);
byte[] buffer = reader.ReadBytes(2);
Array.Reverse(buffer);
return BitConverter.ToUInt16(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadInt32"/>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt32BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(4);
Array.Reverse(buffer);
return BitConverter.ToInt32(buffer, 0);
}
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToUInt32(retval, 0);
byte[] buffer = reader.ReadBytes(4);
Array.Reverse(buffer);
return BitConverter.ToUInt32(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadSingle"/>
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(4);
Array.Reverse(buffer);
return BitConverter.ToSingle(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadInt64"/>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt64BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(8);
Array.Reverse(buffer);
return BitConverter.ToInt64(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadUInt64"/>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToUInt64(retval, 0);
byte[] buffer = reader.ReadBytes(8);
Array.Reverse(buffer);
return BitConverter.ToUInt64(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadDouble"/>
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(8);
Array.Reverse(buffer);
return BitConverter.ToDouble(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadDecimal"/>
/// <remarks>Reads in big-endian format</remarks>
public static decimal ReadDecimalBigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
Array.Reverse(buffer);
int i1 = BitConverter.ToInt32(buffer, 0);
int i2 = BitConverter.ToInt32(buffer, 4);
int i3 = BitConverter.ToInt32(buffer, 8);
int i4 = BitConverter.ToInt32(buffer, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// Read a Guid from the underlying stream
/// </summary>
public static Guid ReadGuid(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
return new Guid(buffer);
}
/// <summary>
/// Read a Guid from the underlying stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Guid ReadGuidBigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
Array.Reverse(buffer);
return new Guid(buffer);
}
// TODO: Determine if the reverse reads are doing what are expected
#if NET7_0_OR_GREATER
/// <summary>
/// Read an Int128 from the underlying stream
/// </summary>
public static Int128 ReadInt128(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
return new Int128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read an Int128 from the underlying stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Int128 ReadInt128BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
Array.Reverse(buffer);
return new Int128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read a UInt128 from the underlying stream
/// </summary>
public static UInt128 ReadUInt128(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
return new UInt128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read a UInt128 from the underlying stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static UInt128 ReadUInt128BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
Array.Reverse(buffer);
return new UInt128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
#endif
/// <summary>
/// Read a null-terminated string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedString(this BinaryReader reader, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.Equals(Encoding.ASCII))
return reader.ReadNullTerminatedAnsiString();
else if (encoding.Equals(Encoding.Unicode))
return reader.ReadNullTerminatedUnicodeString();
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
List<byte> buffer = [];
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte ch = reader.ReadByte();
buffer.Add(ch);
if (ch == '\0')
break;
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated ASCII string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedAnsiString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
List<byte> buffer = [];
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte ch = reader.ReadByte();
buffer.Add(ch);
if (ch == '\0')
break;
}
return Encoding.ASCII.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated Unicode string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
List<byte> buffer = [];
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte[] ch = reader.ReadBytes(2);
buffer.AddRange(ch);
if (ch[0] == '\0' && ch[1] == '\0')
break;
}
return Encoding.Unicode.GetString([.. buffer]);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the underlying stream
/// </summary>
public static string? ReadPrefixedAnsiString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte size = reader.ReadByte();
if (reader.BaseStream.Position + size >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the underlying stream
/// </summary>
public static string? ReadPrefixedUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
ushort size = reader.ReadUInt16();
if (reader.BaseStream.Position + size >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline from the stream
/// </summary>
public static string? ReadQuotedString(this BinaryReader reader)
=> reader.ReadQuotedString(Encoding.Default);
/// <summary>
/// Read a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline from the stream
/// </summary>
public static string? ReadQuotedString(this BinaryReader reader, Encoding encoding)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
var bytes = new List<byte>();
bool openQuote = false;
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
// Read the byte value
byte b = reader.ReadByte();
// If we have a quote, flip the flag
if (b == (byte)'"')
openQuote = !openQuote;
// If we have a newline not in a quoted string, exit the loop
else if (b == (byte)'\n' && !openQuote)
break;
// Add the byte to the set
bytes.Add(b);
}
var line = encoding.GetString([.. bytes]);
return line.TrimEnd();
}
/// <summary>
/// Read a <typeparamref name="T"/> from the underlying stream
/// </summary>
public static T? ReadType<T>(this BinaryReader reader)
{
int typeSize = Marshal.SizeOf(typeof(T));
byte[] buffer = reader.ReadBytes(typeSize);
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = (T?)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return data;
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
@@ -31,6 +32,17 @@ namespace SabreTools.IO.Extensions
public static byte[] ReadBytes(this byte[] content, ref int offset, int count)
=> ReadToBuffer(content, ref offset, count);
/// <summary>
/// Read a UInt8[] and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static byte[] ReadBytesBigEndian(this byte[] content, ref int offset, int count)
{
byte[] buffer = ReadToBuffer(content, ref offset, count);
Array.Reverse(buffer);
return buffer;
}
/// <summary>
/// Read an Int8 and increment the pointer to an array
/// </summary>
@@ -59,8 +71,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read an Int16 in big-endian format and increment the pointer to an array
/// Read an Int16 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static short ReadInt16BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
@@ -78,8 +91,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a UInt16 in big-endian format and increment the pointer to an array
/// Read a UInt16 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ushort ReadUInt16BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
@@ -97,8 +111,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read an Int32 in big-endian format and increment the pointer to an array
/// Read an Int32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt32BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
@@ -116,8 +131,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a UInt32 in big-endian format and increment the pointer to an array
/// Read a UInt32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt32BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
@@ -135,8 +151,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a Single in big-endian format and increment the pointer to an array
/// Read a Single and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
@@ -154,8 +171,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read an Int64 in big-endian format and increment the pointer to an array
/// Read an Int64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt64BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
@@ -173,8 +191,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a UInt64 in big-endian format and increment the pointer to an array
/// Read a UInt64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt64BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
@@ -192,8 +211,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a Double in big-endian format and increment the pointer to an array
/// Read a Double and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
@@ -201,6 +221,38 @@ namespace SabreTools.IO.Extensions
return BitConverter.ToDouble(buffer, 0);
}
/// <summary>
/// Read a Decimal and increment the pointer to an array
/// </summary>
public static decimal ReadDecimal(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
int i1 = BitConverter.ToInt32(buffer, 0);
int i2 = BitConverter.ToInt32(buffer, 4);
int i3 = BitConverter.ToInt32(buffer, 8);
int i4 = BitConverter.ToInt32(buffer, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// Read a Decimal and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static decimal ReadDecimalBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
Array.Reverse(buffer);
int i1 = BitConverter.ToInt32(buffer, 0);
int i2 = BitConverter.ToInt32(buffer, 4);
int i3 = BitConverter.ToInt32(buffer, 8);
int i4 = BitConverter.ToInt32(buffer, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// Read a Guid and increment the pointer to an array
/// </summary>
@@ -211,8 +263,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a Guid in big-endian format and increment the pointer to an array
/// Read a Guid and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Guid ReadGuidBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
@@ -232,8 +285,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read an Int128 in big-endian format and increment the pointer to an array
/// Read an Int128 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Int128 ReadInt128BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
@@ -251,8 +305,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a UInt128 in big-endian format and increment the pointer to an array
/// Read a UInt128 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static UInt128 ReadUInt128BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
@@ -262,34 +317,101 @@ namespace SabreTools.IO.Extensions
#endif
/// <summary>
/// Read a null-terminated string from the byte array
/// Read a null-terminated string from the array
/// </summary>
public static string? ReadString(this byte[] content, ref int offset)
=> content.ReadString(ref offset, Encoding.Default);
public static string? ReadNullTerminatedString(this byte[] content, ref int offset, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.Equals(Encoding.ASCII))
return content.ReadNullTerminatedAnsiString(ref offset);
else if (encoding.Equals(Encoding.Unicode))
return content.ReadNullTerminatedUnicodeString(ref offset);
if (offset >= content.Length)
return null;
List<byte> buffer = [];
while (offset < content.Length)
{
byte ch = content.ReadByteValue(ref offset);
buffer.Add(ch);
if (ch == '\0')
break;
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated string from the byte array
/// Read a null-terminated ASCII string from the byte array
/// </summary>
public static string? ReadString(this byte[] content, ref int offset, Encoding encoding)
public static string? ReadNullTerminatedAnsiString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] nullTerminator = encoding.GetBytes("\0");
int charWidth = nullTerminator.Length;
var keyChars = new List<char>();
List<byte> buffer = [];
while (offset < content.Length)
{
char c = encoding.GetChars(content, offset, charWidth)[0];
keyChars.Add(c);
offset += charWidth;
if (c == '\0')
byte ch = content.ReadByteValue(ref offset);
buffer.Add(ch);
if (ch == '\0')
break;
}
return new string([.. keyChars]).TrimEnd('\0');
return Encoding.ASCII.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated Unicode string from the byte array
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
List<byte> buffer = [];
while (offset < content.Length)
{
byte[] ch = content.ReadBytes(ref offset, 2);
buffer.AddRange(ch);
if (ch[0] == '\0' && ch[1] == '\0')
break;
}
return Encoding.Unicode.GetString([.. buffer]);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the byte array
/// </summary>
public static string? ReadPrefixedAnsiString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte size = content.ReadByteValue(ref offset);
if (offset + size >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the byte array
/// </summary>
public static string? ReadPrefixedUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
ushort size = content.ReadUInt16(ref offset);
if (offset + size >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
@@ -331,6 +453,21 @@ namespace SabreTools.IO.Extensions
return new string([.. keyChars]).TrimEnd();
}
/// <summary>
/// Read a <typeparamref name="T"/> from the underlying stream
/// </summary>
public static T? ReadType<T>(this byte[] content, ref int offset)
{
int typeSize = Marshal.SizeOf(typeof(T));
byte[] buffer = ReadToBuffer(content, ref offset, typeSize);
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = (T?)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return data;
}
/// <summary>
/// Read a number of bytes from the current byte array to a buffer
/// </summary>

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
namespace SabreTools.IO.Extensions
{
public static class EnumerableExtensions
{
/// <summary>
/// Safely iterate through an enumerable, skipping any errors
/// </summary>
public static IEnumerable<T> SafeEnumerate<T>(this IEnumerable<T> enumerable)
{
// Get the enumerator for the enumerable
var enumerator = enumerable.GetEnumerator();
// Iterate through and absorb any errors
while (true)
{
// Attempt to move to the next item
bool moved;
try
{
moved = enumerator.MoveNext();
}
catch
{
continue;
}
// If the end of the enumeration is reached
if (!moved)
yield break;
// Return the next value from the enumeration
yield return enumerator.Current;
}
}
}
}

View File

@@ -118,23 +118,262 @@ namespace SabreTools.IO.Extensions
return null;
// If it does and it is empty, return a blank enumerable
#if NET20 || NET35
if (!Directory.GetFiles(root, "*", SearchOption.AllDirectories).Any())
#else
if (!Directory.EnumerateFileSystemEntries(root, "*", SearchOption.AllDirectories).Any())
#endif
if (!root!.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories).Any())
return [];
// Otherwise, get the complete list
#if NET20 || NET35
return Directory.GetDirectories(root, "*", SearchOption.AllDirectories)
.Where(dir => !Directory.GetFiles(dir, "*", SearchOption.AllDirectories).Any())
return root!.SafeEnumerateDirectories("*", SearchOption.AllDirectories)
.Where(dir => !dir.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories).Any())
.ToList();
#else
return Directory.EnumerateDirectories(root, "*", SearchOption.AllDirectories)
.Where(dir => !Directory.EnumerateFileSystemEntries(dir, "*", SearchOption.AllDirectories).Any())
.ToList();
#endif
}
#region Safe Directory Enumeration
/// <inheritdoc cref="Directory.GetDirectories(string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetDirectories(this string path)
{
try
{
var enumerable = Directory.GetDirectories(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetDirectories(string, string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetDirectories(this string path, string searchPattern)
{
try
{
var enumerable = Directory.GetDirectories(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetDirectories(string, string, SearchOption)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetDirectories(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.GetDirectories(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetFiles(string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFiles(this string path)
{
try
{
var enumerable = Directory.GetFiles(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetFiles(string, string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFiles(this string path, string searchPattern)
{
try
{
var enumerable = Directory.GetFiles(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetFiles(string, string, SearchOption)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFiles(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.GetFiles(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetFileSystemEntries(string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFileSystemEntries(this string path)
{
try
{
var enumerable = Directory.GetFileSystemEntries(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetDirectories(string, string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFileSystemEntries(this string path, string searchPattern)
{
try
{
var enumerable = Directory.GetFileSystemEntries(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetDirectories(string, string, SearchOption)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.GetFileSystemEntries(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
#if NET20 || NET35
/// <inheritdoc cref="Directory.GetDirectories(string)"/>
/// <remarks>Calls <see cref="SafeGetDirectories(string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateDirectories(this string path)
=> path.SafeGetDirectories();
/// <inheritdoc cref="Directory.GetDirectories(string, string)"/>
/// <remarks>Calls <see cref="SafeGetDirectories(string, string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern)
=> path.SafeGetDirectories(searchPattern);
/// <inheritdoc cref="Directory.GetDirectories(string, string, SearchOption)"/>
/// <remarks>Calls <see cref="SafeGetDirectories(string, string, SearchOption)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern, SearchOption searchOption)
=> path.SafeGetDirectories(searchPattern, searchOption);
/// <inheritdoc cref="Directory.GetFiles(string)"/>
/// <remarks>Calls <see cref="SafeGetFiles(string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFiles(this string path)
=> path.SafeGetFiles();
/// <inheritdoc cref="Directory.GetFiles(string, string)"/>
/// <remarks>Calls <see cref="SafeGetFiles(string, string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern)
=> path.SafeGetFiles(searchPattern);
/// <inheritdoc cref="Directory.GetFiles(string, string, SearchOption)"/>
/// <remarks>Calls <see cref="SafeGetFiles(string, string, SearchOption)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern, SearchOption searchOption)
=> path.SafeGetFiles(searchPattern, searchOption);
/// <inheritdoc cref="Directory.GetFileSystemEntries(string)"/>
/// <remarks>Calls <see cref="SafeGetFileSystemEntries(string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path)
=> path.SafeGetFileSystemEntries();
/// <inheritdoc cref="Directory.GetFileSystemEntries(string, string)"/>
/// <remarks>Calls <see cref="SafeGetFileSystemEntries(string, string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern)
=> path.SafeGetFileSystemEntries(searchPattern);
/// <inheritdoc cref="Directory.GetFileSystemEntries(string, string)"/>
/// <remarks>Calls <see cref="SafeGetFileSystemEntries(string, string, SearchOption)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
=> path.SafeGetFileSystemEntries(searchPattern, searchOption);
#else
/// <inheritdoc cref="Directory.EnumerateDirectories(string)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path)
{
var enumerable = Directory.EnumerateDirectories(path);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateDirectories(string, string)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern)
{
var enumerable = Directory.EnumerateDirectories(path, searchPattern);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateDirectories(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern, SearchOption searchOption)
{
var enumerable = Directory.EnumerateDirectories(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFiles(string)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path)
{
var enumerable = Directory.EnumerateFiles(path);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFiles(string, string)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern)
{
var enumerable = Directory.EnumerateFiles(path, searchPattern);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFiles(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern, SearchOption searchOption)
{
var enumerable = Directory.EnumerateFiles(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path)
{
var enumerable = Directory.EnumerateFileSystemEntries(path);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string, string)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern)
{
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
{
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
#endif
#endregion
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
@@ -55,8 +55,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read an Int16 from the stream in big-endian format
/// Read an Int16 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static short ReadInt16BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
@@ -74,8 +75,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a UInt16 from the stream in big-endian format
/// Read a UInt16 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ushort ReadUInt16BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
@@ -93,8 +95,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read an Int32 from the stream in big-endian format
/// Read an Int32 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt32BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
@@ -112,8 +115,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a UInt32 from the stream in big-endian format
/// Read a UInt32 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt32BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
@@ -131,8 +135,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a Single from the stream in big-endian format
/// Read a Single from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
@@ -150,8 +155,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read an Int64 from the stream in big-endian format
/// Read an Int64 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt64BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
@@ -169,8 +175,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a UInt64 from the stream in big-endian format
/// Read a UInt64 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt64BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
@@ -188,8 +195,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a Double from the stream in big-endian format
/// Read a Double from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
@@ -197,6 +205,38 @@ namespace SabreTools.IO.Extensions
return BitConverter.ToDouble(buffer, 0);
}
/// <summary>
/// Read a Decimal from the stream
/// </summary>
public static decimal ReadDecimal(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
int i1 = BitConverter.ToInt32(buffer, 0);
int i2 = BitConverter.ToInt32(buffer, 4);
int i3 = BitConverter.ToInt32(buffer, 8);
int i4 = BitConverter.ToInt32(buffer, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// Read a Decimal from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static decimal ReadDecimalBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
Array.Reverse(buffer);
int i1 = BitConverter.ToInt32(buffer, 0);
int i2 = BitConverter.ToInt32(buffer, 4);
int i3 = BitConverter.ToInt32(buffer, 8);
int i4 = BitConverter.ToInt32(buffer, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// Read a Guid from the stream
/// </summary>
@@ -207,8 +247,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a Guid from the stream in big-endian format
/// Read a Guid from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Guid ReadGuidBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
@@ -228,8 +269,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read an Int128 from the stream in big-endian format
/// Read an Int128 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Int128 ReadInt128BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
@@ -247,8 +289,9 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a UInt128 from the stream in big-endian format
/// Read a UInt128 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static UInt128 ReadUInt128BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
@@ -260,29 +303,99 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read a null-terminated string from the stream
/// </summary>
public static string? ReadString(this Stream stream)
=> stream.ReadString(Encoding.Default);
public static string? ReadNullTerminatedString(this Stream stream, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.Equals(Encoding.ASCII))
return stream.ReadNullTerminatedAnsiString();
else if (encoding.Equals(Encoding.Unicode))
return stream.ReadNullTerminatedUnicodeString();
if (stream.Position >= stream.Length)
return null;
List<byte> buffer = [];
while (stream.Position < stream.Length)
{
byte ch = stream.ReadByteValue();
buffer.Add(ch);
if (ch == '\0')
break;
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated string from the stream
/// Read a null-terminated ASCII string from the stream
/// </summary>
public static string? ReadString(this Stream stream, Encoding encoding)
public static string? ReadNullTerminatedAnsiString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] nullTerminator = encoding.GetBytes("\0");
int charWidth = nullTerminator.Length;
var tempBuffer = new List<byte>();
byte[] buffer = new byte[charWidth];
while (stream.Position < stream.Length && stream.Read(buffer, 0, charWidth) != 0 && !buffer.SequenceEqual(nullTerminator))
List<byte> buffer = [];
while (stream.Position < stream.Length)
{
tempBuffer.AddRange(buffer);
byte ch = stream.ReadByteValue();
buffer.Add(ch);
if (ch == '\0')
break;
}
return encoding.GetString([.. tempBuffer]);
return Encoding.ASCII.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated Unicode string from the stream
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
List<byte> buffer = [];
while (stream.Position < stream.Length)
{
byte[] ch = stream.ReadBytes(2);
buffer.AddRange(ch);
if (ch[0] == '\0' && ch[1] == '\0')
break;
}
return Encoding.Unicode.GetString([.. buffer]);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the stream
/// </summary>
public static string? ReadPrefixedAnsiString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte size = stream.ReadByteValue();
if (stream.Position + size >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the stream
/// </summary>
public static string? ReadPrefixedUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
ushort size = stream.ReadUInt16();
if (stream.Position + size >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
@@ -324,6 +437,21 @@ namespace SabreTools.IO.Extensions
return line.TrimEnd();
}
/// <summary>
/// Read a <typeparamref name="T"/> from the stream
/// </summary>
public static T? ReadType<T>(this Stream stream)
{
int typeSize = Marshal.SizeOf(typeof(T));
byte[] buffer = ReadToBuffer(stream, typeSize);
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = (T?)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return data;
}
/// <summary>
/// Seek to a specific point in the stream, if possible
/// </summary>

View File

@@ -39,7 +39,7 @@ namespace SabreTools.IO
string filename = Path.GetFileName(CurrentPath);
// If we have a true ParentPath, remove it from CurrentPath and return the remainder
if (!string.IsNullOrEmpty(ParentPath) && !PathsEqual(CurrentPath, ParentPath))
if (!string.IsNullOrEmpty(ParentPath) && !PathsEqual(CurrentPath, ParentPath))
filename = CurrentPath.Remove(0, ParentPath!.Length + 1);
// If we're sanitizing the path after, do so
@@ -57,34 +57,31 @@ namespace SabreTools.IO
/// <returns>Complete output path</returns>
public string? GetOutputPath(string? outDir, bool inplace)
{
// If the current path is empty, we can't do anything
// If the current path is empty
if (string.IsNullOrEmpty(CurrentPath))
return null;
// If the output dir is empty (and we're not inplace), we can't do anything
outDir = outDir?.Trim();
if (string.IsNullOrEmpty(outDir) && !inplace)
return null;
// Check if we have a split path or not
bool splitpath = !string.IsNullOrEmpty(ParentPath);
// If we have an inplace output, use the directory name from the input path
// If we have an inplace output
if (inplace)
return Path.GetDirectoryName(CurrentPath);
// If the current and parent paths are the same, just use the output directory
if (!splitpath || CurrentPath.Length == (ParentPath?.Length ?? 0))
// If the output dir is empty after trimming
outDir = outDir?.Trim();
if (string.IsNullOrEmpty(outDir))
return null;
// If the parent path is empty or the paths are equal
if (string.IsNullOrEmpty(ParentPath) || PathsEqual(CurrentPath, ParentPath))
return outDir;
// By default, the working parent directory is the parent path
string workingParent = ParentPath ?? string.Empty;
string workingParent = ParentPath!;
// 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 ?? string.Empty) ?? string.Empty;
workingParent = Path.GetDirectoryName(ParentPath) ?? string.Empty;
// Handle bizarre Windows-like paths on Linux
if (workingParent.EndsWith(":") && Path.DirectorySeparatorChar == '/')
workingParent += '/';
@@ -98,7 +95,7 @@ namespace SabreTools.IO
string combinedPath = Path.Combine(outDir!, strippedPath);
return Path.GetDirectoryName(combinedPath);
}
/// <summary>
/// Determine if two paths are equal or not
/// </summary>
@@ -129,8 +126,8 @@ namespace SabreTools.IO
if (input == null)
return null;
// Replace alternate directory separators with the correct one
return input.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
// Replace '\' with '/'
return input.Replace('\\', '/');
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
namespace SabreTools.IO
@@ -70,7 +71,7 @@ namespace SabreTools.IO
private static List<string> GetDirectoriesOrderedHelper(string dir, List<string> infiles, string pattern)
{
// Take care of the files in the top directory
List<string> toadd = [.. Directory.GetDirectories(dir, pattern, SearchOption.TopDirectoryOnly)];
List<string> toadd = [.. dir.SafeEnumerateDirectories(pattern, SearchOption.TopDirectoryOnly)];
toadd.Sort(new NaturalComparer());
infiles.AddRange(toadd);
@@ -149,12 +150,12 @@ namespace SabreTools.IO
private static List<string> GetFilesOrderedHelper(string dir, List<string> infiles, string pattern)
{
// Take care of the files in the top directory
List<string> toadd = [.. Directory.GetFiles(dir, pattern, SearchOption.TopDirectoryOnly)];
List<string> toadd = [.. dir.SafeEnumerateFiles(pattern, SearchOption.TopDirectoryOnly)];
toadd.Sort(new NaturalComparer());
infiles.AddRange(toadd);
// Then recurse through and add from the directories
List<string> subDirs = [.. Directory.GetDirectories(dir, pattern, SearchOption.TopDirectoryOnly)];
List<string> subDirs = [.. dir.SafeEnumerateDirectories(pattern, SearchOption.TopDirectoryOnly)];
subDirs.Sort(new NaturalComparer());
foreach (string subdir in subDirs)
{

View File

@@ -7,7 +7,7 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.3.7</Version>
<Version>1.4.0</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>

36
publish-nix.sh Executable file
View File

@@ -0,0 +1,36 @@
#! /bin/bash
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
NO_BUILD=false
while getopts "uba" OPTION
do
case $OPTION in
b)
NO_BUILD=true
;;
*)
echo "Invalid option provided"
exit 1
;;
esac
done
# Set the current directory as a variable
BUILD_FOLDER=$PWD
# Only build if requested
if [ $NO_BUILD = false ]
then
# Restore Nuget packages for all builds
echo "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.IO/SabreTools.IO.csproj --output $BUILD_FOLDER
fi

26
publish-win.ps1 Normal file
View File

@@ -0,0 +1,26 @@
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
param(
[Parameter(Mandatory = $false)]
[Alias("NoBuild")]
[switch]$NO_BUILD
)
# Set the current directory as a variable
$BUILD_FOLDER = $PSScriptRoot
# Only build if requested
if (!$NO_BUILD.IsPresent)
{
# Restore Nuget packages for all builds
Write-Host "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.IO\SabreTools.IO.csproj --output $BUILD_FOLDER
}