mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Refactor
This commit is contained in:
@@ -8,6 +8,16 @@ namespace CliFx.Attributes;
|
|||||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||||
public sealed class CommandAttribute : Attribute
|
public sealed class CommandAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandAttribute" />.
|
||||||
|
/// </summary>
|
||||||
|
public CommandAttribute(string name) => Name = name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandAttribute" />.
|
||||||
|
/// </summary>
|
||||||
|
public CommandAttribute() { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command name.
|
/// Command name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -23,17 +33,4 @@ public sealed class CommandAttribute : Attribute
|
|||||||
/// This is shown to the user in the help text.
|
/// This is shown to the user in the help text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandAttribute" />.
|
|
||||||
/// </summary>
|
|
||||||
public CommandAttribute(string name)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandAttribute" />.
|
|
||||||
/// </summary>
|
|
||||||
public CommandAttribute() { }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,33 @@ namespace CliFx.Attributes;
|
|||||||
[AttributeUsage(AttributeTargets.Property)]
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
public sealed class CommandOptionAttribute : Attribute
|
public sealed class CommandOptionAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandOptionAttribute" />.
|
||||||
|
/// </summary>
|
||||||
|
private CommandOptionAttribute(string? name, char? shortName)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
ShortName = shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandOptionAttribute" />.
|
||||||
|
/// </summary>
|
||||||
|
public CommandOptionAttribute(string name, char shortName)
|
||||||
|
: this(name, (char?)shortName) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandOptionAttribute" />.
|
||||||
|
/// </summary>
|
||||||
|
public CommandOptionAttribute(string name)
|
||||||
|
: this(name, null) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandOptionAttribute" />.
|
||||||
|
/// </summary>
|
||||||
|
public CommandOptionAttribute(char shortName)
|
||||||
|
: this(null, (char?)shortName) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option name.
|
/// Option name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -67,31 +94,4 @@ public sealed class CommandOptionAttribute : Attribute
|
|||||||
/// Validators must derive from <see cref="BindingValidator{T}" />.
|
/// Validators must derive from <see cref="BindingValidator{T}" />.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Type[] Validators { get; set; } = Array.Empty<Type>();
|
public Type[] Validators { get; set; } = Array.Empty<Type>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute" />.
|
|
||||||
/// </summary>
|
|
||||||
private CommandOptionAttribute(string? name, char? shortName)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
ShortName = shortName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute" />.
|
|
||||||
/// </summary>
|
|
||||||
public CommandOptionAttribute(string name, char shortName)
|
|
||||||
: this(name, (char?)shortName) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute" />.
|
|
||||||
/// </summary>
|
|
||||||
public CommandOptionAttribute(string name)
|
|
||||||
: this(name, null) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute" />.
|
|
||||||
/// </summary>
|
|
||||||
public CommandOptionAttribute(char shortName)
|
|
||||||
: this(null, (char?)shortName) { }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ namespace CliFx.Infrastructure;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
// Both the underlying stream AND the stream reader must be synchronized!
|
// Both the underlying stream AND the stream reader must be synchronized!
|
||||||
// https://github.com/Tyrrrz/CliFx/issues/123
|
// https://github.com/Tyrrrz/CliFx/issues/123
|
||||||
public partial class ConsoleReader(IConsole console, Stream stream, Encoding encoding)
|
public class ConsoleReader(IConsole console, Stream stream, Encoding encoding)
|
||||||
: StreamReader(stream, encoding, false, 4096)
|
: StreamReader(Stream.Synchronized(stream), encoding, false, 4096)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="ConsoleReader" />.
|
/// Initializes an instance of <see cref="ConsoleReader" />.
|
||||||
@@ -86,9 +86,3 @@ public partial class ConsoleReader(IConsole console, Stream stream, Encoding enc
|
|||||||
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
protected override void Dispose(bool disposing) => base.Dispose(disposing);
|
protected override void Dispose(bool disposing) => base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class ConsoleReader
|
|
||||||
{
|
|
||||||
internal static ConsoleReader Create(IConsole console, Stream stream) =>
|
|
||||||
new(console, Stream.Synchronized(stream));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,9 +12,18 @@ namespace CliFx.Infrastructure;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
// Both the underlying stream AND the stream writer must be synchronized!
|
// Both the underlying stream AND the stream writer must be synchronized!
|
||||||
// https://github.com/Tyrrrz/CliFx/issues/123
|
// https://github.com/Tyrrrz/CliFx/issues/123
|
||||||
public partial class ConsoleWriter(IConsole console, Stream stream, Encoding encoding)
|
public class ConsoleWriter : StreamWriter
|
||||||
: StreamWriter(stream, encoding.WithoutPreamble(), 256)
|
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="ConsoleWriter" />.
|
||||||
|
/// </summary>
|
||||||
|
public ConsoleWriter(IConsole console, Stream stream, Encoding encoding)
|
||||||
|
: base(Stream.Synchronized(stream), encoding.WithoutPreamble(), 256)
|
||||||
|
{
|
||||||
|
Console = console;
|
||||||
|
AutoFlush = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="ConsoleWriter" />.
|
/// Initializes an instance of <see cref="ConsoleWriter" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -24,7 +33,7 @@ public partial class ConsoleWriter(IConsole console, Stream stream, Encoding enc
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Console that owns this stream.
|
/// Console that owns this stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IConsole Console { get; } = console;
|
public IConsole Console { get; }
|
||||||
|
|
||||||
// The following overrides are required to establish thread-safe behavior
|
// The following overrides are required to establish thread-safe behavior
|
||||||
// in methods deriving from StreamWriter.
|
// in methods deriving from StreamWriter.
|
||||||
@@ -260,9 +269,3 @@ public partial class ConsoleWriter(IConsole console, Stream stream, Encoding enc
|
|||||||
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
protected override void Dispose(bool disposing) => base.Dispose(disposing);
|
protected override void Dispose(bool disposing) => base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class ConsoleWriter
|
|
||||||
{
|
|
||||||
internal static ConsoleWriter Create(IConsole console, Stream stream) =>
|
|
||||||
new(console, Stream.Synchronized(stream)) { AutoFlush = true };
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,15 +6,24 @@ using System.Threading;
|
|||||||
namespace CliFx.Infrastructure;
|
namespace CliFx.Infrastructure;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implementation of <see cref="IConsole" /> that uses the provided fake
|
/// Implementation of <see cref="IConsole" /> that uses the provided fake standard input, output, and error streams.
|
||||||
/// standard input, output, and error streams.
|
/// Use this implementation in tests to verify how a command interacts with the console.
|
||||||
/// Use this implementation in tests to verify command interactions with the console.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FakeConsole : IConsole, IDisposable
|
public class FakeConsole : IConsole, IDisposable
|
||||||
{
|
{
|
||||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||||
private readonly ConcurrentQueue<ConsoleKeyInfo> _keys = new();
|
private readonly ConcurrentQueue<ConsoleKeyInfo> _keys = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="FakeConsole" />.
|
||||||
|
/// </summary>
|
||||||
|
public FakeConsole(Stream? input = null, Stream? output = null, Stream? error = null)
|
||||||
|
{
|
||||||
|
Input = new ConsoleReader(this, input ?? Stream.Null);
|
||||||
|
Output = new ConsoleWriter(this, output ?? Stream.Null);
|
||||||
|
Error = new ConsoleWriter(this, error ?? Stream.Null);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ConsoleReader Input { get; }
|
public ConsoleReader Input { get; }
|
||||||
|
|
||||||
@@ -52,14 +61,9 @@ public class FakeConsole : IConsole, IDisposable
|
|||||||
public int CursorTop { get; set; }
|
public int CursorTop { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="FakeConsole" />.
|
/// Enqueues a simulated key press, which can then be read by calling <see cref="ReadKey" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FakeConsole(Stream? input = null, Stream? output = null, Stream? error = null)
|
public void EnqueueKey(ConsoleKeyInfo key) => _keys.Enqueue(key);
|
||||||
{
|
|
||||||
Input = ConsoleReader.Create(this, input ?? Stream.Null);
|
|
||||||
Output = ConsoleWriter.Create(this, output ?? Stream.Null);
|
|
||||||
Error = ConsoleWriter.Create(this, error ?? Stream.Null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ConsoleKeyInfo ReadKey(bool intercept = false) =>
|
public ConsoleKeyInfo ReadKey(bool intercept = false) =>
|
||||||
@@ -70,11 +74,6 @@ public class FakeConsole : IConsole, IDisposable
|
|||||||
+ $"Use the `{nameof(EnqueueKey)}(...)` method to simulate a key press."
|
+ $"Use the `{nameof(EnqueueKey)}(...)` method to simulate a key press."
|
||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enqueues a simulated key press, which can then be read by calling <see cref="ReadKey" />.
|
|
||||||
/// </summary>
|
|
||||||
public void EnqueueKey(ConsoleKeyInfo key) => _keys.Enqueue(key);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ResetColor()
|
public void ResetColor()
|
||||||
{
|
{
|
||||||
@@ -85,11 +84,8 @@ public class FakeConsole : IConsole, IDisposable
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Clear() { }
|
public void Clear() { }
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public CancellationToken RegisterCancellationHandler() => _cancellationTokenSource.Token;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a cancellation signal to the currently executing command.
|
/// Simulates a cancellation request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If the command is not cancellation-aware (i.e. it doesn't call <see cref="IConsole.RegisterCancellationHandler" />),
|
/// If the command is not cancellation-aware (i.e. it doesn't call <see cref="IConsole.RegisterCancellationHandler" />),
|
||||||
@@ -108,6 +104,9 @@ public class FakeConsole : IConsole, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public CancellationToken RegisterCancellationHandler() => _cancellationTokenSource.Token;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual void Dispose() => _cancellationTokenSource.Dispose();
|
public virtual void Dispose() => _cancellationTokenSource.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace CliFx.Infrastructure;
|
namespace CliFx.Infrastructure;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implementation of <see cref="IConsole" /> that uses fake
|
/// Implementation of <see cref="IConsole" /> that uses fake standard input, output, and error streams
|
||||||
/// standard input, output, and error streams backed by in-memory stores.
|
/// backed by in-memory stores.
|
||||||
/// Use this implementation in tests to verify command interactions with the console.
|
/// Use this implementation in tests to verify how a command interacts with the console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FakeInMemoryConsole : FakeConsole
|
public class FakeInMemoryConsole : FakeConsole
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,47 +5,47 @@ using CliFx.Utils;
|
|||||||
namespace CliFx.Infrastructure;
|
namespace CliFx.Infrastructure;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstraction for the console layer.
|
/// Abstraction for interacting with the console layer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IConsole
|
public interface IConsole
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Input stream (stdin).
|
/// Console's standard input stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ConsoleReader Input { get; }
|
ConsoleReader Input { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value that indicates whether input has been redirected from the standard input stream.
|
/// Whether the input stream has been redirected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsInputRedirected { get; }
|
bool IsInputRedirected { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Output stream (stdout).
|
/// Console's standard output stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ConsoleWriter Output { get; }
|
ConsoleWriter Output { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value that indicates whether output has been redirected from the standard output stream.
|
/// Whether the output stream has been redirected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsOutputRedirected { get; }
|
bool IsOutputRedirected { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Error stream (stderr).
|
/// Console's standard error stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ConsoleWriter Error { get; }
|
ConsoleWriter Error { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value that indicates whether error output has been redirected from the standard error stream.
|
/// Whether the error stream has been redirected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsErrorRedirected { get; }
|
bool IsErrorRedirected { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the foreground color of the console
|
/// Gets or sets the current foreground color of the console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ConsoleColor ForegroundColor { get; set; }
|
ConsoleColor ForegroundColor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the background color of the console.
|
/// Gets or sets the current background color of the console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ConsoleColor BackgroundColor { get; set; }
|
ConsoleColor BackgroundColor { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ public class SystemConsole : IConsole, IDisposable
|
|||||||
{
|
{
|
||||||
private CancellationTokenSource? _cancellationTokenSource;
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="SystemConsole" />.
|
||||||
|
/// </summary>
|
||||||
|
public SystemConsole()
|
||||||
|
{
|
||||||
|
Input = new ConsoleReader(this, Console.OpenStandardInput());
|
||||||
|
Output = new ConsoleWriter(this, Console.OpenStandardOutput());
|
||||||
|
Error = new ConsoleWriter(this, Console.OpenStandardError());
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ConsoleReader Input { get; }
|
public ConsoleReader Input { get; }
|
||||||
|
|
||||||
@@ -70,16 +80,6 @@ public class SystemConsole : IConsole, IDisposable
|
|||||||
set => Console.CursorTop = value;
|
set => Console.CursorTop = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="SystemConsole" />.
|
|
||||||
/// </summary>
|
|
||||||
public SystemConsole()
|
|
||||||
{
|
|
||||||
Input = ConsoleReader.Create(this, Console.OpenStandardInput());
|
|
||||||
Output = ConsoleWriter.Create(this, Console.OpenStandardOutput());
|
|
||||||
Error = ConsoleWriter.Create(this, Console.OpenStandardError());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ConsoleKeyInfo ReadKey(bool intercept = false) => Console.ReadKey(intercept);
|
public ConsoleKeyInfo ReadKey(bool intercept = false) => Console.ReadKey(intercept);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user