Add ProgressReporter

Closes #14
This commit is contained in:
Alexey Golub
2019-08-23 17:29:23 +03:00
parent 80465e0e51
commit 13b15b98ed
5 changed files with 153 additions and 4 deletions

View File

@@ -0,0 +1,57 @@
using System.Globalization;
using System.IO;
using System.Linq;
using CliFx.Services;
using CliFx.Utilities;
using FluentAssertions;
using NUnit.Framework;
namespace CliFx.Tests.Utilities
{
[TestFixture]
public class ProgressReporterTests
{
[Test]
public void Report_Test()
{
// Arrange
var formatProvider = CultureInfo.InvariantCulture;
using (var stdout = new StringWriter(formatProvider))
{
var console = new VirtualConsole(TextReader.Null, false, stdout, false, TextWriter.Null, false);
var reporter = console.CreateProgressReporter();
var progressValues = Enumerable.Range(0, 100).Select(p => p / 100.0).ToArray();
var progressStringValues = progressValues.Select(p => p.ToString("P2", formatProvider)).ToArray();
// Act
foreach (var progress in progressValues)
reporter.Report(progress);
// Assert
stdout.ToString().Should().ContainAll(progressStringValues);
}
}
[Test]
public void Report_Redirected_Test()
{
// Arrange
using (var stdout = new StringWriter())
{
var console = new VirtualConsole(stdout);
var reporter = console.CreateProgressReporter();
var progressValues = Enumerable.Range(0, 100).Select(p => p / 100.0).ToArray();
// Act
foreach (var progress in progressValues)
reporter.Report(progress);
// Assert
stdout.ToString().Should().BeEmpty();
}
}
}
}

View File

@@ -15,19 +15,19 @@ namespace CliFx.Services
public TextReader Input { get; }
/// <inheritdoc />
public bool IsInputRedirected => true;
public bool IsInputRedirected { get; }
/// <inheritdoc />
public TextWriter Output { get; }
/// <inheritdoc />
public bool IsOutputRedirected => true;
public bool IsOutputRedirected { get; }
/// <inheritdoc />
public TextWriter Error { get; }
/// <inheritdoc />
public bool IsErrorRedirected => true;
public bool IsErrorRedirected { get; }
/// <inheritdoc />
public ConsoleColor ForegroundColor { get; set; } = ConsoleColor.Gray;
@@ -38,11 +38,24 @@ namespace CliFx.Services
/// <summary>
/// Initializes an instance of <see cref="VirtualConsole"/>.
/// </summary>
public VirtualConsole(TextReader input, TextWriter output, TextWriter error)
public VirtualConsole(TextReader input, bool isInputRedirected,
TextWriter output, bool isOutputRedirected,
TextWriter error, bool isErrorRedirected)
{
Input = input.GuardNotNull(nameof(input));
IsInputRedirected = isInputRedirected;
Output = output.GuardNotNull(nameof(output));
IsOutputRedirected = isOutputRedirected;
Error = error.GuardNotNull(nameof(error));
IsErrorRedirected = isErrorRedirected;
}
/// <summary>
/// Initializes an instance of <see cref="VirtualConsole"/>.
/// </summary>
public VirtualConsole(TextReader input, TextWriter output, TextWriter error)
: this(input, true, output, true, error, true)
{
}
/// <summary>

View File

@@ -0,0 +1,15 @@
using CliFx.Services;
namespace CliFx.Utilities
{
/// <summary>
/// Extensions for <see cref="Utilities"/>.
/// </summary>
public static class Extensions
{
/// <summary>
/// Creates a progress reporter for this console.
/// </summary>
public static ProgressReporter CreateProgressReporter(this IConsole console) => new ProgressReporter(console);
}
}

View File

@@ -0,0 +1,50 @@
using System;
using CliFx.Services;
namespace CliFx.Utilities
{
/// <summary>
/// Utility that provides continuous progress reporting to the console.
/// </summary>
public class ProgressReporter : IProgress<double>
{
private readonly IConsole _console;
private string _lastOutput = "";
/// <summary>
/// Initializes an instance of <see cref="ProgressReporter"/>.
/// </summary>
public ProgressReporter(IConsole console)
{
_console = console;
}
private void EraseLastOutput()
{
for (var i = 0; i < _lastOutput.Length; i++)
_console.Output.Write('\b');
}
private void RenderProgress(double progress)
{
_lastOutput = progress.ToString("P2", _console.Output.FormatProvider);
_console.Output.Write(_lastOutput);
}
/// <summary>
/// Reports progress and renders it to console.
/// If console's stdout is redirected, this method returns without doing anything.
/// </summary>
public void Report(double progress)
{
// We don't do anything if stdout is redirected to avoid polluting output
//...when there's no active console window.
if (!_console.IsOutputRedirected)
{
EraseLastOutput();
RenderProgress(progress);
}
}
}
}

View File

@@ -27,6 +27,7 @@ _CliFx is to command line interfaces what ASP.NET Core is to web applications._
- Generates contextual help text
- Prints errors and routes exit codes on exceptions
- Highly testable and easy to customize
- Contains utilties such as progress reporting
- Targets .NET Framework 4.5+ and .NET Standard 2.0+
- No external dependencies
@@ -262,6 +263,19 @@ var app = new CliApplicationBuilder()
.Build();
```
### Report progress
CliFx comes with a simple utility for rendering progress to the console, `ProgressReporter`. It implements a well-known `IProgress<double>` interface so you can pass it to methods that are aware of this abstraction.
To avoid polluting output when it's not bound to a console, `ProgressReporter` will simply no-op if stdout is redirected.
```c#
var progressReporter = console.CreateProgressReporter();
for (var i = 0.0; i <= 1; i += 0.01)
progressReporter.Report(i);
```
### Testing
CliFx makes it really easy to test your commands thanks to the `IConsole` interface.