Skip to content

Commit 2341b76

Browse files
committed
feat: add progress and status payloads with configuration options
1 parent 1250331 commit 2341b76

7 files changed

Lines changed: 180 additions & 33 deletions

File tree

src/Extensions/ILoggerExtensions.cs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ namespace WB.Logging.LogSinks.Console.Spectre;
1010
/// </summary>
1111
public static class ILoggerExtensions
1212
{
13+
private static readonly ProgressConfiguration defaultProgressConfiguration = new();
14+
1315
// ┌─────────────────────────────────────────────────────────────────────────────┐
1416
// │ Public Methods │
1517
// └─────────────────────────────────────────────────────────────────────────────┘
@@ -74,21 +76,22 @@ public static ILogger HorizontalRule(this ILogger @this, string? title = null)
7476
}
7577

7678
/// <summary>
77-
/// Starts a progress with the specified <paramref name="title"/> and <paramref name="progress"/> function.
78-
/// The progress will be automatically completed when the <paramref name="progress"/> function completes.
79+
/// Starts a progress with the <paramref name="progressConfiguration"/> running the specified <paramref name="action"/> function.
80+
/// The progress will be automatically completed when the <paramref name="action"/> function completes.
7981
/// </summary>
8082
/// <param name="this">The <see cref="ILogger"/> instance to start the progress on.</param>
81-
/// <param name="title">The title of the progress.</param>
82-
/// <param name="progress">The function that performs the progress.</param>
83+
/// <param name="progressConfiguration">The configuration for the progress. This includes settings such as
84+
/// <param name="action">The function that performs the progress.</param>
8385
/// <returns>A task that represents the asynchronous operation.</returns>
84-
public static async Task StartProgressAsync(this ILogger @this, string title, Func<ProgressContext, Task> progress)
86+
/// <seealso cref="ProgressConfiguration"/>
87+
public static async Task StartProgressAsync(this ILogger @this, ProgressConfiguration progressConfiguration, Func<ProgressContext, Task> action)
8588
{
8689
ArgumentNullException.ThrowIfNull(@this);
8790

8891
ProgressPayload progressPayload = new()
8992
{
90-
Title = title,
91-
Progress = progress,
93+
Configuration = progressConfiguration,
94+
Action = action,
9295
};
9396

9497
await @this.FlushAsync().ConfigureAwait(false);
@@ -97,4 +100,40 @@ public static async Task StartProgressAsync(this ILogger @this, string title, Fu
97100

98101
await progressPayload.Completed.ConfigureAwait(false);
99102
}
103+
104+
/// <summary>
105+
/// Starts a progress running the specified <paramref name="action"/> function.
106+
/// The progress will be automatically completed when the <paramref name="action"/> function completes.
107+
/// </summary>
108+
/// <param name="this">The <see cref="ILogger"/> instance to start the progress on.</param>
109+
/// <param name="action">The function that performs the progress.</param>
110+
/// <returns>A task that represents the asynchronous operation.</returns>
111+
public static async Task StartProgressAsync(this ILogger @this, Func<ProgressContext, Task> action)
112+
=> await StartProgressAsync(@this, defaultProgressConfiguration, action).ConfigureAwait(false);
113+
114+
public static async Task StartStatusAsync(this ILogger @this, StatusConfiguration statusConfiguration, Func<StatusContext, Task> action)
115+
{
116+
ArgumentNullException.ThrowIfNull(@this);
117+
118+
StatusPayload statusPayload = new()
119+
{
120+
Configuration = statusConfiguration,
121+
Action = action,
122+
};
123+
124+
await @this.FlushAsync().ConfigureAwait(false);
125+
126+
@this.Log(null, statusPayload);
127+
128+
await statusPayload.Completed.ConfigureAwait(false);
129+
}
130+
131+
public static Task StartStatusAsync(this ILogger @this, string statusMessage, Func<StatusContext, Task> action)
132+
{
133+
ArgumentNullException.ThrowIfNull(@this);
134+
135+
StatusConfiguration statusConfiguration = new(statusMessage);
136+
137+
return StartStatusAsync(@this, statusConfiguration, action);
138+
}
100139
}

src/LogMessageWriters/ProgressConsoleMessageWriter.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ internal sealed class ProgressConsoleMessageWriter : IAsyncLogMessageWriter<Prog
2929
/// <inheritdoc/>
3030
public async ValueTask WriteAsync(DateTimeOffset timestamp, LogLevel? logLevel, IEnumerable<string> senders, ProgressPayload payload)
3131
{
32+
ProgressConfiguration progressConfiguration = payload.Configuration;
33+
3234
#pragma warning disable CA1031 // Do not catch general exception types
3335
try
3436
{
3537
await Writer.Progress()
36-
.AutoClear(payload.AutoClear)
37-
.AutoRefresh(payload.AutoRefresh)
38-
.HideCompleted(payload.HideCompleted)
39-
.StartAsync(payload.Progress).ConfigureAwait(false);
38+
.AutoClear(progressConfiguration.AutoClear)
39+
.AutoRefresh(progressConfiguration.AutoRefresh)
40+
.HideCompleted(progressConfiguration.HideCompleted)
41+
.StartAsync(payload.Action).ConfigureAwait(false);
4042

4143
payload.SetCompleted();
4244
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Spectre.Console;
5+
using WB.Logging.LogSinks.Base;
6+
7+
namespace WB.Logging.LogSinks.Console.Spectre;
8+
9+
/// <summary>
10+
/// A log message writer that uses Spectre.Console's status to
11+
/// render status updates in the console.
12+
/// </summary>
13+
internal sealed class StatusConsoleMessageWriter : IAsyncLogMessageWriter<StatusPayload, IAnsiConsole>
14+
{
15+
// ┌─────────────────────────────────────────────────────────────────────────────┐
16+
// │ Public Properties │
17+
// └─────────────────────────────────────────────────────────────────────────────┘
18+
19+
/// <inheritdoc/>
20+
public IAnsiConsole Writer { get; set; } = AnsiConsole.Console;
21+
22+
/// <inheritdoc/>
23+
public IAsyncLogSink? LogSink { get; set; }
24+
25+
// ┌─────────────────────────────────────────────────────────────────────────────┐
26+
// │ Public Methods │
27+
// └─────────────────────────────────────────────────────────────────────────────┘
28+
29+
/// <inheritdoc/>
30+
public async ValueTask WriteAsync(DateTimeOffset timestamp, LogLevel? logLevel, IEnumerable<string> senders, StatusPayload payload)
31+
{
32+
StatusConfiguration statusConfiguration = payload.Configuration;
33+
34+
#pragma warning disable CA1031 // Do not catch general exception types
35+
try
36+
{
37+
await Writer.Status()
38+
.AutoRefresh(statusConfiguration.AutoRefresh)
39+
.Spinner(statusConfiguration.Spinner)
40+
.StartAsync(statusConfiguration.StatusMessage, payload.Action).ConfigureAwait(false);
41+
42+
payload.SetCompleted();
43+
}
44+
catch (Exception exception)
45+
{
46+
payload.SetException(exception);
47+
}
48+
#pragma warning restore CA1031 // Do not catch general exception types
49+
}
50+
}

src/Payloads/ProgressPayload.cs

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace WB.Logging.LogSinks.Console.Spectre;
77
/// <summary>
88
/// Represents a payload for logging progress using Spectre.Console's progress bars.
99
/// </summary>
10-
public sealed class ProgressPayload
10+
internal sealed class ProgressPayload
1111
{
1212
// ┌─────────────────────────────────────────────────────────────────────────────┐
1313
// │ Private Fields │
@@ -20,29 +20,13 @@ public sealed class ProgressPayload
2020
// └─────────────────────────────────────────────────────────────────────────────┘
2121

2222
/// <summary>
23-
/// Gets or sets the title of the progress.
23+
/// Gets or sets the configuration for the progress. This includes settings such as
24+
/// whether the progress should be automatically cleared, refreshed, or hide completed tasks.
2425
/// </summary>
25-
public required string Title { get; init; }
26+
/// <seealso cref="ProgressConfiguration"/>
27+
public required ProgressConfiguration Configuration { get; init; }
2628

27-
/// <summary>
28-
/// Gets or sets the function that performs the progress.
29-
/// </summary>
30-
public required Func<ProgressContext, Task> Progress { get; init; }
31-
32-
/// <summary>
33-
/// Gets or sets a value indicating whether the progress should be automatically cleared from the console when completed. Default is <c>true</c>.
34-
/// </summary>
35-
public bool AutoClear { get; init; } = true;
36-
37-
/// <summary>
38-
/// Gets or sets a value indicating whether the progress should be automatically refreshed in the console. Default is <c>true</c>.
39-
/// </summary>
40-
public bool AutoRefresh { get; init; } = true;
41-
42-
/// <summary>
43-
/// Gets or sets a value indicating whether to hide completed tasks in the progress. Default is <c>true</c>.
44-
/// </summary>
45-
public bool HideCompleted { get; init; } = true;
29+
public required Func<ProgressContext, Task> Action { get; init; }
4630

4731
/// <summary>
4832
/// Gets a <see cref="Task"/> that represents the completion of the progress. The

src/Payloads/StatusPayload.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Spectre.Console;
4+
5+
namespace WB.Logging.LogSinks.Console.Spectre;
6+
7+
/// <summary>
8+
/// Represents a payload for logging progress using Spectre.Console's progress bars.
9+
/// </summary>
10+
internal sealed class StatusPayload
11+
{
12+
// ┌─────────────────────────────────────────────────────────────────────────────┐
13+
// │ Private Fields │
14+
// └─────────────────────────────────────────────────────────────────────────────┘
15+
16+
private readonly TaskCompletionSource taskCompletionSource = new();
17+
18+
// ┌─────────────────────────────────────────────────────────────────────────────┐
19+
// │ Public Properties │
20+
// └─────────────────────────────────────────────────────────────────────────────┘
21+
22+
public required StatusConfiguration Configuration { get; init; }
23+
24+
/// <summary>
25+
/// Gets or sets the action that performs the status update.
26+
/// </summary>
27+
public required Func<StatusContext, Task> Action { get; init; }
28+
29+
/// <summary>
30+
/// Gets a <see cref="Task"/> that represents the completion of the status. The
31+
/// <see cref="Task"/> will complete when the status is completed or if an exception
32+
/// occurs during the status update. The <see cref="Task"/> will complete successfully if
33+
/// the status update completes successfully, or with an <see cref="Exception"/> if an <see cref="Exception"/>
34+
/// occurs during the status update.
35+
/// </summary>
36+
public Task Completed => taskCompletionSource.Task;
37+
38+
// ┌─────────────────────────────────────────────────────────────────────────────┐
39+
// │ Public Methods │
40+
// └─────────────────────────────────────────────────────────────────────────────┘
41+
42+
/// <summary>
43+
/// Sets the status as completed, which will complete the <see cref="Completed"/>
44+
/// task. This method should be called when the status is completed successfully.
45+
/// </summary>
46+
public void SetCompleted()
47+
=> taskCompletionSource.TrySetResult();
48+
49+
/// <summary>
50+
/// Sets the status as failed, which will complete the <see cref="Completed"/>
51+
/// task with an exception. This method should be called when an error occurs during the status update.
52+
/// </summary>
53+
/// <param name="exception">The exception that caused the status update to fail.</param>
54+
public void SetException(Exception exception)
55+
=> taskCompletionSource.TrySetException(exception);
56+
}

src/ProgressConfiguration.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace WB.Logging.LogSinks.Console.Spectre;
2+
3+
public sealed record ProgressConfiguration(
4+
bool AutoClear = true,
5+
bool AutoRefresh = true,
6+
bool HideCompleted = true);

src/StatusConfiguration.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Spectre.Console;
2+
3+
namespace WB.Logging.LogSinks.Console.Spectre;
4+
5+
public sealed record StatusConfiguration(
6+
string StatusMessage,
7+
bool AutoRefresh = true)
8+
{
9+
public Spinner Spinner { get; init; } = Spinner.Known.Dots;
10+
}

0 commit comments

Comments
 (0)