Initial sync
This commit is contained in:
91
Nbt/Util/IndentedWriter.cs
Executable file
91
Nbt/Util/IndentedWriter.cs
Executable file
@@ -0,0 +1,91 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Nbt;
|
||||
|
||||
public sealed class IndentedWriter(TextWriter baseWriter, string indent, bool leaveOpen = false) : TextWriter
|
||||
{
|
||||
private readonly TextWriter baseWriter = baseWriter;
|
||||
private readonly string indent = indent;
|
||||
private readonly bool leaveOpen = leaveOpen;
|
||||
private uint depth = 0u;
|
||||
|
||||
public IndentedWriter(StringBuilder sb, string indent) : this(new StringWriter(sb), indent) { }
|
||||
|
||||
public override Encoding Encoding => baseWriter.Encoding;
|
||||
|
||||
public IndentationLevel NewIndentationLevel() => new(this);
|
||||
|
||||
public override void Write(char value) => baseWriter.Write(value);
|
||||
|
||||
public override void WriteLine()
|
||||
{
|
||||
baseWriter.WriteLine();
|
||||
|
||||
for (var i = 0u; i < depth; i++)
|
||||
{
|
||||
baseWriter.Write(indent);
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(char value) => await baseWriter.WriteAsync(value);
|
||||
|
||||
public override async Task WriteLineAsync()
|
||||
{
|
||||
await baseWriter.WriteLineAsync();
|
||||
|
||||
for (var i = 0u; i < depth; i++)
|
||||
{
|
||||
await baseWriter.WriteAsync(indent);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!leaveOpen)
|
||||
{
|
||||
baseWriter.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!leaveOpen)
|
||||
{
|
||||
await baseWriter.DisposeAsync();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await base.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct IndentationLevel : IDisposable
|
||||
{
|
||||
private readonly IndentedWriter writer;
|
||||
|
||||
public IndentationLevel(IndentedWriter writer)
|
||||
{
|
||||
this.writer = writer;
|
||||
|
||||
writer.depth++;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
writer.depth--;
|
||||
}
|
||||
}
|
||||
}
|
||||
128
Nbt/Util/QuotedWriter.cs
Executable file
128
Nbt/Util/QuotedWriter.cs
Executable file
@@ -0,0 +1,128 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Nbt;
|
||||
|
||||
public sealed class QuotedWriter(TextWriter baseWriter, char quoteChar, char escapeChar, bool leaveOpen = false) : TextWriter
|
||||
{
|
||||
private readonly TextWriter baseWriter = baseWriter;
|
||||
private readonly char quoteChar = quoteChar;
|
||||
private readonly char escapeChar = escapeChar;
|
||||
private readonly bool leaveOpen = leaveOpen;
|
||||
private bool hasWrittenOneChar = false;
|
||||
|
||||
public QuotedWriter(StringBuilder sb, char quoteChar, char escapeChar) : this(new StringWriter(sb), quoteChar, escapeChar) { }
|
||||
|
||||
public override Encoding Encoding => baseWriter.Encoding;
|
||||
|
||||
public override void Write(char value)
|
||||
{
|
||||
if (!hasWrittenOneChar)
|
||||
{
|
||||
baseWriter.Write(quoteChar);
|
||||
hasWrittenOneChar = true;
|
||||
}
|
||||
|
||||
foreach (var c in Escape(value))
|
||||
{
|
||||
baseWriter.Write(c);
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(char value)
|
||||
{
|
||||
if (!hasWrittenOneChar)
|
||||
{
|
||||
await baseWriter.WriteAsync(quoteChar);
|
||||
hasWrittenOneChar = true;
|
||||
}
|
||||
|
||||
foreach (var c in Escape(value))
|
||||
{
|
||||
await baseWriter.WriteAsync(c);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<char> Escape(char c)
|
||||
{
|
||||
var escape = false;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '\t':
|
||||
escape = true;
|
||||
c = 't';
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
escape = true;
|
||||
c = 'r';
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
escape = true;
|
||||
c = 'n';
|
||||
break;
|
||||
|
||||
default:
|
||||
if (c == quoteChar || c == escapeChar)
|
||||
{
|
||||
escape = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (escape)
|
||||
{
|
||||
yield return escapeChar;
|
||||
}
|
||||
|
||||
yield return c;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!hasWrittenOneChar)
|
||||
{
|
||||
baseWriter.Write(quoteChar);
|
||||
}
|
||||
|
||||
baseWriter.Write(quoteChar);
|
||||
|
||||
if (!leaveOpen)
|
||||
{
|
||||
baseWriter.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!hasWrittenOneChar)
|
||||
{
|
||||
await baseWriter.WriteAsync(quoteChar);
|
||||
}
|
||||
|
||||
await baseWriter.WriteAsync(quoteChar);
|
||||
|
||||
if (!leaveOpen)
|
||||
{
|
||||
await baseWriter.DisposeAsync();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await base.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
251
Nbt/Util/Utils.cs
Executable file
251
Nbt/Util/Utils.cs
Executable file
@@ -0,0 +1,251 @@
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using K4os.Compression.LZ4.Streams;
|
||||
|
||||
namespace Nbt;
|
||||
|
||||
internal static class Utils
|
||||
{
|
||||
#region conversion stuff
|
||||
|
||||
public static TTo Convert<TFrom, TTo>(this TFrom value, Converter<TFrom, TTo> converter) => value is TTo sameValue ? sameValue : converter(value);
|
||||
|
||||
#endregion
|
||||
|
||||
#region enumerable stuff
|
||||
|
||||
public static T[] AsArray<T>(this IEnumerable<T> enumerable) => Convert(enumerable, Enumerable.ToArray);
|
||||
|
||||
public static IList<T> AsIList<T>(this IEnumerable<T> enumerable) => Convert<IEnumerable<T>, IList<T>>(enumerable, Enumerable.ToList);
|
||||
|
||||
public static List<T> AsList<T>(this IEnumerable<T> enumerable) => Convert(enumerable, Enumerable.ToList);
|
||||
|
||||
public static IEnumerable<T> Insert<T>(this IEnumerable<T> e, int index, T value)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
if (e.TryGetNonEnumeratedCount(out var count))
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(index, count);
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var o in e)
|
||||
{
|
||||
if (i == index)
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
|
||||
yield return o;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == index)
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Replace<T>(this IEnumerable<T> e, int index, T value)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
if (e.TryGetNonEnumeratedCount(out var count))
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, count);
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var o in e)
|
||||
{
|
||||
yield return i == index ? value : o;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Yield<T>(this T value)
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region streams, readers and writers
|
||||
|
||||
public static GZipStream CreateGZipDeflater(Stream stream, bool leaveOpen) => new(stream, CompressionMode.Compress, leaveOpen);
|
||||
public static ZLibStream CreateZLibDeflater(Stream stream, bool leaveOpen) => new(stream, CompressionMode.Compress, leaveOpen);
|
||||
public static LZ4EncoderStream CreateLZ4Deflater(Stream stream, bool leaveOpen) => LZ4Stream.Encode(stream, leaveOpen: leaveOpen);
|
||||
|
||||
public static GZipStream CreateGZipInflater(Stream stream, bool leaveOpen) => new(stream, CompressionMode.Decompress, leaveOpen);
|
||||
public static ZLibStream CreateZLibInflater(Stream stream, bool leaveOpen) => new(stream, CompressionMode.Decompress, leaveOpen);
|
||||
public static LZ4DecoderStream CreateLZ4Inflater(Stream stream, bool leaveOpen) => LZ4Stream.Decode(stream, leaveOpen: leaveOpen);
|
||||
|
||||
public static char SkipWhiteSpaces(TextReader reader)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var i = reader.Read();
|
||||
|
||||
if (i < 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
// return i;
|
||||
}
|
||||
|
||||
var c = (char)i;
|
||||
|
||||
if (char.IsWhiteSpace(c))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region string and char quotation and whatnot
|
||||
|
||||
public static string Repeat(this string s, int c) => c switch
|
||||
{
|
||||
< 0 => throw new ArgumentException("Count must be positive.", nameof(c)),
|
||||
0 => string.Empty,
|
||||
1 => s,
|
||||
_ => string.IsNullOrEmpty(s) ? s : string.Create(s.Length * c, (s, c), static (span, state) =>
|
||||
{
|
||||
var (s, c) = state;
|
||||
var n = s.Length;
|
||||
|
||||
for (var i = 0; i < c; i++)
|
||||
{
|
||||
var ss = span.Slice(n * i, n);
|
||||
s.CopyTo(ss);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
public static string Unquote(string s, char quoteChar, char escapeChar)
|
||||
{
|
||||
var length = s.Length;
|
||||
|
||||
if (length < 2 || s[0] != quoteChar || s[^1] != quoteChar)
|
||||
{
|
||||
throw new ArgumentException("Invalid quoted string", nameof(s));
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var escape = false;
|
||||
|
||||
for (var i = 1; i < length - 1; i++)
|
||||
{
|
||||
var c = s[i];
|
||||
|
||||
if (escape)
|
||||
{
|
||||
escape = false;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 't':
|
||||
c = '\t';
|
||||
break;
|
||||
case 'r':
|
||||
c = '\r';
|
||||
break;
|
||||
case 'n':
|
||||
c = '\n';
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (c == quoteChar)
|
||||
{
|
||||
throw new ArgumentException("Invalid quoted string", nameof(s));
|
||||
}
|
||||
else if (c == escapeChar)
|
||||
{
|
||||
escape = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string Quote(string s, char quoteChar, char escapeChar)
|
||||
{
|
||||
using var writer = new StringWriter();
|
||||
|
||||
Quote(writer, s, quoteChar, escapeChar);
|
||||
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
public static void Quote(TextWriter writer, string s, char quoteChar, char escapeChar)
|
||||
{
|
||||
if (s.Length == 0)
|
||||
{
|
||||
writer.Write(quoteChar);
|
||||
writer.Write(quoteChar);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using var quoter = new QuotedWriter(writer, quoteChar, escapeChar, true);
|
||||
|
||||
quoter.Write(s);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region equalities and comparisons
|
||||
|
||||
public static bool EnumerableEquals<T>(IEnumerable<T> a, IEnumerable<T> b, IEqualityComparer<T>? elementComparer = null) => ReferenceEquals(a, b) || a is not null && b is not null && a.SequenceEqual(b, elementComparer);
|
||||
|
||||
public static bool DictionaryEquals<K, V>(IDictionary<K, V> a, IDictionary<K, V> b, IEqualityComparer<V>? valueComparer = null)
|
||||
{
|
||||
if (ReferenceEquals(a, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a is null || b is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var n = a.Count;
|
||||
|
||||
if (n != b.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n is 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
valueComparer ??= EqualityComparer<V>.Default;
|
||||
|
||||
foreach (var entry in a)
|
||||
{
|
||||
if (!b.TryGetValue(entry.Key, out var value) || !valueComparer.Equals(entry.Value, value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user