diff --git a/PowerKit/Disposable.cs b/PowerKit/Disposable.cs
index eb2179f..b144ef0 100644
--- a/PowerKit/Disposable.cs
+++ b/PowerKit/Disposable.cs
@@ -3,17 +3,31 @@
namespace PowerKit;
+///
+/// Provides utility methods for creating and composing instances.
+///
internal partial class Disposable(Action dispose) : IDisposable
{
+ ///
public void Dispose() => dispose();
}
internal partial class Disposable
{
+ ///
+ /// Gets a disposable that performs no action when disposed.
+ ///
public static IDisposable Null { get; } = Create(() => { });
+ ///
+ /// Creates a disposable that invokes the specified action when disposed.
+ ///
public static IDisposable Create(Action dispose) => new Disposable(dispose);
+ ///
+ /// Creates a disposable that disposes all specified disposables when disposed,
+ /// aggregating any exceptions thrown during disposal.
+ ///
public static IDisposable Merge(params IEnumerable disposables) =>
Create(() =>
{
diff --git a/PowerKit/Extensions/AggregateExceptionExtensions.cs b/PowerKit/Extensions/AggregateExceptionExtensions.cs
index c0d0fea..1b5d6f8 100644
--- a/PowerKit/Extensions/AggregateExceptionExtensions.cs
+++ b/PowerKit/Extensions/AggregateExceptionExtensions.cs
@@ -6,6 +6,10 @@ internal static class AggregateExceptionExtensions
{
extension(AggregateException exception)
{
+ ///
+ /// Returns the single inner exception if the aggregate contains exactly one after flattening;
+ /// otherwise, returns .
+ ///
public Exception? TryGetSingle() =>
exception.Flatten().InnerExceptions is [var single] ? single : null;
}
diff --git a/PowerKit/Extensions/AsyncEnumerableExtensions.cs b/PowerKit/Extensions/AsyncEnumerableExtensions.cs
index b3ac565..ec506f9 100644
--- a/PowerKit/Extensions/AsyncEnumerableExtensions.cs
+++ b/PowerKit/Extensions/AsyncEnumerableExtensions.cs
@@ -10,6 +10,9 @@ internal static class AsyncEnumerableExtensions
{
extension(IAsyncEnumerable source)
{
+ ///
+ /// Returns a specified number of elements from the start of the async sequence.
+ ///
public async IAsyncEnumerable TakeAsync(
int count,
[EnumeratorCancellation] CancellationToken cancellationToken = default
@@ -32,6 +35,10 @@ public async IAsyncEnumerable TakeAsync(
}
}
+ ///
+ /// Projects each element of the async sequence to an
+ /// and flattens the resulting sequences into one async sequence.
+ ///
public async IAsyncEnumerable SelectManyAsync(
Func> transform,
[EnumeratorCancellation] CancellationToken cancellationToken = default
@@ -50,6 +57,9 @@ var item in source
}
}
+ ///
+ /// Materializes the async sequence into a .
+ ///
public async ValueTask> ToListAsync(
CancellationToken cancellationToken = default
)
@@ -68,6 +78,9 @@ var item in source
return list;
}
+ ///
+ /// Enables directly awaiting the async sequence, materializing it into a .
+ ///
public ValueTaskAwaiter> GetAwaiter() => source.ToListAsync().GetAwaiter();
}
}
diff --git a/PowerKit/Extensions/ComparableExtensions.cs b/PowerKit/Extensions/ComparableExtensions.cs
index b8e0639..2a96616 100644
--- a/PowerKit/Extensions/ComparableExtensions.cs
+++ b/PowerKit/Extensions/ComparableExtensions.cs
@@ -6,6 +6,9 @@ internal static class ComparableExtensions
{
extension(T value) where T : IComparable
{
+ ///
+ /// Clamps the value to the specified range.
+ ///
public T Clamp(T min, T max)
{
if (value.CompareTo(min) < 0)
@@ -17,8 +20,14 @@ public T Clamp(T min, T max)
return value;
}
+ ///
+ /// Returns the smaller of the current value and the specified value.
+ ///
public T Min(T other) => value.CompareTo(other) <= 0 ? value : other;
+ ///
+ /// Returns the larger of the current value and the specified value.
+ ///
public T Max(T other) => value.CompareTo(other) >= 0 ? value : other;
}
}
diff --git a/PowerKit/Extensions/EnumerableExtensions.cs b/PowerKit/Extensions/EnumerableExtensions.cs
index 378c712..d7be50a 100644
--- a/PowerKit/Extensions/EnumerableExtensions.cs
+++ b/PowerKit/Extensions/EnumerableExtensions.cs
@@ -7,6 +7,9 @@ internal static class EnumerableExtensions
{
extension(T obj)
{
+ ///
+ /// Wraps the object in an enumerable containing a single element.
+ ///
public IEnumerable ToSingletonEnumerable()
{
yield return obj;
@@ -16,6 +19,9 @@ public IEnumerable ToSingletonEnumerable()
extension(IEnumerable source)
where T : class
{
+ ///
+ /// Filters out elements from the sequence.
+ ///
public IEnumerable WhereNotNull()
{
foreach (var item in source)
@@ -31,6 +37,9 @@ public IEnumerable WhereNotNull()
extension(IEnumerable source)
where T : struct
{
+ ///
+ /// Filters out elements from the sequence of nullable value types.
+ ///
public IEnumerable WhereNotNull()
{
foreach (var item in source)
@@ -45,6 +54,9 @@ public IEnumerable WhereNotNull()
extension(IEnumerable source)
{
+ ///
+ /// Filters out and empty strings from the sequence.
+ ///
public IEnumerable WhereNotNullOrEmpty()
{
foreach (var item in source)
@@ -56,6 +68,9 @@ public IEnumerable WhereNotNullOrEmpty()
}
}
+ ///
+ /// Filters out , empty, and whitespace-only strings from the sequence.
+ ///
public IEnumerable WhereNotNullOrWhiteSpace()
{
foreach (var item in source)
@@ -71,6 +86,9 @@ public IEnumerable WhereNotNullOrWhiteSpace()
extension(IEnumerable source)
where T : struct
{
+ ///
+ /// Returns the first element of the sequence, or if the sequence is empty.
+ ///
public T? FirstOrNull()
{
foreach (var item in source)
@@ -81,6 +99,9 @@ public IEnumerable WhereNotNullOrWhiteSpace()
return null;
}
+ ///
+ /// Returns the last element of the sequence, or if the sequence is empty.
+ ///
public T? LastOrNull()
{
if (source is IReadOnlyList list)
@@ -98,6 +119,9 @@ public IEnumerable WhereNotNullOrWhiteSpace()
return last;
}
+ ///
+ /// Returns the element at the specified index, or if the index is out of range.
+ ///
public T? ElementAtOrNull(int index)
{
var list = source as IReadOnlyList ?? source.ToArray();
diff --git a/PowerKit/Extensions/ExceptionExtensions.cs b/PowerKit/Extensions/ExceptionExtensions.cs
index ab721f7..08cd54c 100644
--- a/PowerKit/Extensions/ExceptionExtensions.cs
+++ b/PowerKit/Extensions/ExceptionExtensions.cs
@@ -7,6 +7,10 @@ internal static class ExceptionExtensions
{
extension(Exception exception)
{
+ ///
+ /// Returns a flat list containing the exception itself and all of its
+ /// nested inner exceptions, recursively unwrapping instances.
+ ///
public IReadOnlyList GetSelfAndDescendants()
{
static void PopulateDescendants(Exception ex, ICollection result)
diff --git a/PowerKit/Extensions/FunctionalExtensions.cs b/PowerKit/Extensions/FunctionalExtensions.cs
index 5e40566..894c3d1 100644
--- a/PowerKit/Extensions/FunctionalExtensions.cs
+++ b/PowerKit/Extensions/FunctionalExtensions.cs
@@ -7,22 +7,38 @@ internal static class FunctionalExtensions
{
extension(TIn input)
{
+ ///
+ /// Passes the value through the specified transform function and returns the result.
+ ///
public TOut Pipe(Func transform) => transform(input);
}
extension(T value)
where T : struct
{
+ ///
+ /// Returns if the value matches the specified predicate; otherwise, returns the value.
+ ///
public T? NullIf(Func predicate) => !predicate(value) ? value : null;
+ ///
+ /// Returns if the value equals the default value for its type; otherwise, returns the value.
+ ///
public T? NullIfDefault() =>
value.NullIf(v => EqualityComparer.Default.Equals(v, default));
}
extension(string value)
{
+ ///
+ /// Returns if the string is or empty; otherwise, returns the string.
+ ///
public string? NullIfEmpty() => !string.IsNullOrEmpty(value) ? value : null;
+ ///
+ /// Returns if the string is , empty, or consists only of whitespace;
+ /// otherwise, returns the string.
+ ///
public string? NullIfWhiteSpace() => !string.IsNullOrWhiteSpace(value) ? value : null;
}
}
diff --git a/PowerKit/Extensions/PathExtensions.cs b/PowerKit/Extensions/PathExtensions.cs
index 003ddc7..87e705d 100644
--- a/PowerKit/Extensions/PathExtensions.cs
+++ b/PowerKit/Extensions/PathExtensions.cs
@@ -42,16 +42,31 @@ internal static class PathExtensions
{
extension(Path)
{
+ ///
+ /// Gets the characters that are invalid in file names.
+ /// When is , returns characters
+ /// invalid across all major filesystems; otherwise, returns the OS-specific set.
+ ///
public static char[] GetInvalidFileNameChars(bool crossPlatform) =>
crossPlatform
? PathEx.CrossPlatformInvalidFileNameChars
: Path.GetInvalidFileNameChars();
+ ///
+ /// Gets the characters that are invalid in paths.
+ /// When is , returns characters
+ /// invalid across all major filesystems; otherwise, returns the OS-specific set.
+ ///
public static char[] GetInvalidPathChars(bool crossPlatform) =>
crossPlatform
? PathEx.CrossPlatformInvalidPathChars
: Path.GetInvalidPathChars();
+ ///
+ /// Replaces invalid file name characters with underscores and strips trailing dots and whitespace.
+ /// When is , considers characters
+ /// invalid across all major filesystems.
+ ///
public static string EscapeFileName(string fileName, bool crossPlatform = true)
{
var invalidChars = new HashSet(Path.GetInvalidFileNameChars(crossPlatform));
diff --git a/PowerKit/Extensions/StreamExtensions.cs b/PowerKit/Extensions/StreamExtensions.cs
index e392e83..cb865a1 100644
--- a/PowerKit/Extensions/StreamExtensions.cs
+++ b/PowerKit/Extensions/StreamExtensions.cs
@@ -10,6 +10,9 @@ internal static class StreamExtensions
{
extension(Stream source)
{
+ ///
+ /// Copies the contents of the stream to the destination stream, optionally flushing after each write.
+ ///
public async Task CopyToAsync(
Stream destination,
bool autoFlush,
@@ -40,6 +43,10 @@ await destination
}
}
+ ///
+ /// Copies the contents of the stream to the destination stream, reporting progress
+ /// as a ratio of bytes read to .
+ ///
public async ValueTask CopyToAsync(
Stream destination,
long contentLength,
@@ -75,6 +82,10 @@ await destination
}
}
+ ///
+ /// Copies the contents of the stream to the destination stream, reporting progress
+ /// based on the source stream's length when available.
+ ///
public async ValueTask CopyToAsync(
Stream destination,
IProgress? progress = null,
diff --git a/PowerKit/Extensions/StringBuilderExtensions.cs b/PowerKit/Extensions/StringBuilderExtensions.cs
index d8e73df..4a12767 100644
--- a/PowerKit/Extensions/StringBuilderExtensions.cs
+++ b/PowerKit/Extensions/StringBuilderExtensions.cs
@@ -6,9 +6,15 @@ internal static class StringBuilderExtensions
{
extension(StringBuilder builder)
{
+ ///
+ /// Appends the specified character only if the builder is not empty.
+ ///
public StringBuilder AppendIfNotEmpty(char value) =>
builder.Length > 0 ? builder.Append(value) : builder;
+ ///
+ /// Removes leading and trailing whitespace characters from the builder.
+ ///
public StringBuilder Trim()
{
var start = 0;
diff --git a/PowerKit/Extensions/StringExtensions.cs b/PowerKit/Extensions/StringExtensions.cs
index 2320da9..101fe1c 100644
--- a/PowerKit/Extensions/StringExtensions.cs
+++ b/PowerKit/Extensions/StringExtensions.cs
@@ -7,6 +7,10 @@ internal static class StringExtensions
{
extension(string str)
{
+ ///
+ /// Returns the substring before the first occurrence of the specified substring.
+ /// If the substring is not found, returns the original string.
+ ///
public string SubstringUntil(
string sub,
StringComparison comparison = StringComparison.Ordinal
@@ -17,6 +21,10 @@ public string SubstringUntil(
_ => str,
};
+ ///
+ /// Returns the substring before the last occurrence of the specified substring.
+ /// If the substring is not found, returns the original string.
+ ///
public string SubstringUntilLast(
string sub,
StringComparison comparison = StringComparison.Ordinal
@@ -27,6 +35,10 @@ public string SubstringUntilLast(
_ => str,
};
+ ///
+ /// Returns the substring after the first occurrence of the specified substring.
+ /// If the substring is not found, returns an empty string.
+ ///
public string SubstringAfter(
string sub,
StringComparison comparison = StringComparison.Ordinal
@@ -37,6 +49,10 @@ public string SubstringAfter(
_ => "",
};
+ ///
+ /// Returns the substring after the last occurrence of the specified substring.
+ /// If the substring is not found, returns an empty string.
+ ///
public string SubstringAfterLast(
string sub,
StringComparison comparison = StringComparison.Ordinal
@@ -47,8 +63,14 @@ public string SubstringAfterLast(
_ => "",
};
+ ///
+ /// Truncates the string to the specified maximum number of characters.
+ ///
public string Truncate(int charCount) => str.Length > charCount ? str[..charCount] : str;
+ ///
+ /// Inserts the specified separator before each uppercase letter, splitting PascalCase words.
+ ///
public string SeparateWords(char separator)
{
var builder = new StringBuilder(str.Length * 2);
@@ -66,8 +88,14 @@ public string SeparateWords(char separator)
return builder.ToString();
}
+ ///
+ /// Converts the PascalCase string to kebab-case (e.g., "FooBar" → "foo-bar").
+ ///
public string ToKebabCase() => str.SeparateWords('-').ToLowerInvariant();
+ ///
+ /// Converts the PascalCase string to snake_case (e.g., "FooBar" → "foo_bar").
+ ///
public string ToSnakeCase() => str.SeparateWords('_').ToLowerInvariant();
}
}
diff --git a/PowerKit/Extensions/TextReaderExtensions.cs b/PowerKit/Extensions/TextReaderExtensions.cs
index 08e1323..72acb84 100644
--- a/PowerKit/Extensions/TextReaderExtensions.cs
+++ b/PowerKit/Extensions/TextReaderExtensions.cs
@@ -9,6 +9,9 @@ internal static class TextReaderExtensions
{
extension(TextReader reader)
{
+ ///
+ /// Reads all lines from the text reader as an async sequence.
+ ///
public async IAsyncEnumerable ReadLinesAsync(
[EnumeratorCancellation] CancellationToken cancellationToken = default
)