Add console abstraction, remove CommandContext

This commit is contained in:
Alexey Golub
2019-07-30 17:35:06 +03:00
parent 5174d5354b
commit 041a995c62
26 changed files with 399 additions and 391 deletions

View File

@@ -1,6 +1,6 @@
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Models;
using CliFx.Services;
namespace CliFx.Benchmarks.Commands
{
@@ -16,6 +16,6 @@ namespace CliFx.Benchmarks.Commands
[CommandOption("bool", 'b')]
public bool BoolOption { get; set; }
public Task ExecuteAsync(CommandContext context) => Task.CompletedTask;
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
}
}

View File

@@ -1,7 +1,6 @@
using System.Text;
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Models;
using CliFx.Services;
namespace CliFx.Tests.Dummy.Commands
@@ -15,7 +14,7 @@ namespace CliFx.Tests.Dummy.Commands
[CommandOption('e', Description = "Whether the greeting should be exclaimed.")]
public bool IsExclaimed { get; set; }
public Task ExecuteAsync(CommandContext context)
public Task ExecuteAsync(IConsole console)
{
var buffer = new StringBuilder();
@@ -24,7 +23,7 @@ namespace CliFx.Tests.Dummy.Commands
if (IsExclaimed)
buffer.Append('!');
context.Output.WriteLine(buffer.ToString());
console.Output.WriteLine(buffer.ToString());
return Task.CompletedTask;
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Models;
using CliFx.Services;
namespace CliFx.Tests.Dummy.Commands
@@ -15,10 +14,10 @@ namespace CliFx.Tests.Dummy.Commands
[CommandOption("base", 'b', Description = "Logarithm base.")]
public double Base { get; set; } = 10;
public Task ExecuteAsync(CommandContext context)
public Task ExecuteAsync(IConsole console)
{
var result = Math.Log(Value, Base);
context.Output.WriteLine(result);
console.Output.WriteLine(result);
return Task.CompletedTask;
}

View File

@@ -2,7 +2,6 @@
using System.Linq;
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Models;
using CliFx.Services;
namespace CliFx.Tests.Dummy.Commands
@@ -13,10 +12,10 @@ namespace CliFx.Tests.Dummy.Commands
[CommandOption("values", 'v', IsRequired = true, Description = "Input values.")]
public IReadOnlyList<double> Values { get; set; }
public Task ExecuteAsync(CommandContext context)
public Task ExecuteAsync(IConsole console)
{
var result = Values.Sum();
context.Output.WriteLine(result);
console.Output.WriteLine(result);
return Task.CompletedTask;
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Exceptions;
using CliFx.Models;
using CliFx.Services;
using FluentAssertions;
using NUnit.Framework;
@@ -14,19 +14,19 @@ namespace CliFx.Tests
[Command]
private class TestDefaultCommand : ICommand
{
public Task ExecuteAsync(CommandContext context) => Task.CompletedTask;
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
}
[Command("command")]
private class TestNamedCommand : ICommand
{
public Task ExecuteAsync(CommandContext context) => Task.CompletedTask;
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
}
[Command("faulty command")]
private class TestFaultyCommand : ICommand
{
public Task ExecuteAsync(CommandContext context) => Task.FromException(new CommandErrorException(-1337));
public Task ExecuteAsync(IConsole console) => Task.FromException(new CommandErrorException(-1337));
}
}

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Models;
using CliFx.Services;
using FluentAssertions;
using NUnit.Framework;
@@ -14,7 +13,7 @@ namespace CliFx.Tests
[Command]
private class TestCommand : ICommand
{
public Task ExecuteAsync(CommandContext context) => throw new NotImplementedException();
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
}
}
@@ -32,10 +31,9 @@ namespace CliFx.Tests
{
// Arrange
var factory = new CommandFactory();
var schema = new CommandSchemaResolver().GetCommandSchema(commandType);
// Act
var command = factory.CreateCommand(schema);
var command = factory.CreateCommand(commandType);
// Assert
command.Should().BeOfType(commandType);

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using CliFx.Attributes;
@@ -27,7 +26,7 @@ namespace CliFx.Tests
[CommandOption("bool", 'b', GroupName = "other-group")]
public bool BoolOption { get; set; }
public Task ExecuteAsync(CommandContext context) => throw new NotImplementedException();
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
}
}

View File

@@ -26,7 +26,7 @@ namespace CliFx.Tests
[CommandOption("option-c", Description = "Option C description")]
public bool OptionC { get; set; }
public Task ExecuteAsync(CommandContext context) => throw new NotImplementedException();
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
}
}

View File

@@ -15,29 +15,33 @@ namespace CliFx
{
private readonly ApplicationMetadata _applicationMetadata;
private readonly IReadOnlyList<Type> _commandTypes;
private readonly IConsole _console;
private readonly ICommandInputParser _commandInputParser;
private readonly ICommandSchemaResolver _commandSchemaResolver;
private readonly ICommandFactory _commandFactory;
private readonly ICommandInitializer _commandInitializer;
private readonly ICommandHelpTextBuilder _commandHelpTextBuilder;
private readonly ICommandHelpTextRenderer _commandHelpTextRenderer;
public CliApplication(ApplicationMetadata applicationMetadata, IReadOnlyList<Type> commandTypes,
ICommandInputParser commandInputParser, ICommandSchemaResolver commandSchemaResolver,
ICommandFactory commandFactory, ICommandInitializer commandInitializer, ICommandHelpTextBuilder commandHelpTextBuilder)
IConsole console, ICommandInputParser commandInputParser, ICommandSchemaResolver commandSchemaResolver,
ICommandFactory commandFactory, ICommandInitializer commandInitializer, ICommandHelpTextRenderer commandHelpTextRenderer)
{
_applicationMetadata = applicationMetadata;
_commandTypes = commandTypes;
_console = console;
_commandInputParser = commandInputParser;
_commandSchemaResolver = commandSchemaResolver;
_commandFactory = commandFactory;
_commandInitializer = commandInitializer;
_commandHelpTextBuilder = commandHelpTextBuilder;
_commandHelpTextRenderer = commandHelpTextRenderer;
}
public CliApplication(ApplicationMetadata applicationMetadata, IReadOnlyList<Type> commandTypes)
: this(applicationMetadata, commandTypes,
new CommandInputParser(), new CommandSchemaResolver(), new CommandFactory(),
new CommandInitializer(), new CommandHelpTextBuilder())
new SystemConsole(), new CommandInputParser(), new CommandSchemaResolver(),
new CommandFactory(), new CommandInitializer(), new CommandHelpTextRenderer())
{
}
@@ -75,9 +79,6 @@ namespace CliFx
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
{
var stdOut = ConsoleWriter.GetStandardOutput();
var stdErr = ConsoleWriter.GetStandardError();
try
{
var commandInput = _commandInputParser.ParseInput(commandLineArguments);
@@ -88,65 +89,60 @@ namespace CliFx
// Fail if there are no commands defined
if (!availableCommandSchemas.Any())
{
stdErr.WriteLine("There are no commands defined in this application.");
_console.WithColor(ConsoleColor.Red,
c => c.Error.WriteLine("There are no commands defined in this application."));
return -1;
}
// Fail if specified a command which is not defined
if (!commandInput.CommandName.IsNullOrWhiteSpace() && matchingCommandSchema == null)
{
stdErr.WriteLine($"Specified command [{commandInput.CommandName}] is not defined.");
return -1;
}
_console.WithColor(ConsoleColor.Red,
c => c.Error.WriteLine($"Specified command [{commandInput.CommandName}] is not defined."));
// Use a stub if command was not specified but there is no default command defined
if (matchingCommandSchema == null)
{
matchingCommandSchema = _commandSchemaResolver.GetCommandSchema(typeof(StubDefaultCommand));
return -1;
}
// Show version if it was requested without specifying a command
if (IsVersionRequested(commandInput) && commandInput.CommandName.IsNullOrWhiteSpace())
{
stdOut.WriteLine(_applicationMetadata.VersionText);
_console.Output.WriteLine(_applicationMetadata.VersionText);
return 0;
}
// Show help if it was requested
if (IsHelpRequested(commandInput))
{
var helpText = _commandHelpTextBuilder.Build(_applicationMetadata, availableCommandSchemas, matchingCommandSchema);
stdOut.WriteLine(helpText);
_commandHelpTextRenderer.RenderHelpText(_applicationMetadata, availableCommandSchemas, matchingCommandSchema);
return 0;
}
// Show help if command wasn't specified but a default command isn't defined
if (commandInput.CommandName.IsNullOrWhiteSpace() && matchingCommandSchema == null)
{
_commandHelpTextRenderer.RenderHelpText(_applicationMetadata, availableCommandSchemas);
return 0;
}
// Create an instance of the command
var command = matchingCommandSchema.Type == typeof(StubDefaultCommand)
? new StubDefaultCommand(_commandHelpTextBuilder)
: _commandFactory.CreateCommand(matchingCommandSchema);
var command = _commandFactory.CreateCommand(matchingCommandSchema.Type);
// Populate command with options according to its schema
_commandInitializer.InitializeCommand(command, matchingCommandSchema, commandInput);
// Create context and execute command
var commandContext = new CommandContext(_applicationMetadata,
availableCommandSchemas, matchingCommandSchema,
commandInput, stdOut, stdErr);
await command.ExecuteAsync(commandContext);
await command.ExecuteAsync(_console);
return 0;
}
catch (Exception ex)
{
stdErr.WriteLine(ex.ToString());
_console.WithColor(ConsoleColor.Red, c => c.Error.WriteLine(ex));
return ex is CommandErrorException errorException ? errorException.ExitCode : -1;
}
finally
{
stdOut.Dispose();
stdErr.Dispose();
}
}
}
@@ -157,7 +153,7 @@ namespace CliFx
// Entry assembly is null in tests
var entryAssembly = Assembly.GetEntryAssembly();
var title = entryAssembly?.GetName().FullName ?? "App";
var title = entryAssembly?.GetName().Name ?? "App";
var executableName = Path.GetFileNameWithoutExtension(entryAssembly?.Location) ?? "app";
var versionText = entryAssembly?.GetName().Version.ToString() ?? "1.0";
@@ -174,26 +170,5 @@ namespace CliFx
return entryAssembly.ExportedTypes.Where(t => t.Implements(typeof(ICommand))).ToArray();
}
private sealed class StubDefaultCommand : ICommand
{
private readonly ICommandHelpTextBuilder _commandHelpTextBuilder;
public StubDefaultCommand(ICommandHelpTextBuilder commandHelpTextBuilder)
{
_commandHelpTextBuilder = commandHelpTextBuilder;
}
public Task ExecuteAsync(CommandContext context)
{
var helpText = _commandHelpTextBuilder.Build(context.ApplicationMetadata,
context.AvailableCommandSchemas,
context.MatchingCommandSchema);
context.Output.WriteLine(helpText);
return Task.CompletedTask;
}
}
}
}

View File

@@ -1,10 +1,10 @@
using System.Threading.Tasks;
using CliFx.Models;
using CliFx.Services;
namespace CliFx
{
public interface ICommand
{
Task ExecuteAsync(CommandContext context);
Task ExecuteAsync(IConsole console);
}
}

View File

@@ -1,32 +0,0 @@
using System.Collections.Generic;
using CliFx.Services;
namespace CliFx.Models
{
public class CommandContext
{
public ApplicationMetadata ApplicationMetadata { get; }
public IReadOnlyList<CommandSchema> AvailableCommandSchemas { get; }
public CommandSchema MatchingCommandSchema { get; }
public CommandInput CommandInput { get; }
public IConsoleWriter Output { get; }
public IConsoleWriter Error { get; }
public CommandContext(ApplicationMetadata applicationMetadata,
IReadOnlyList<CommandSchema> availableCommandSchemas, CommandSchema matchingCommandSchema,
CommandInput commandInput, IConsoleWriter output, IConsoleWriter error)
{
ApplicationMetadata = applicationMetadata;
AvailableCommandSchemas = availableCommandSchemas;
MatchingCommandSchema = matchingCommandSchema;
CommandInput = commandInput;
Output = output;
Error = error;
}
}
}

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CliFx.Internal;
@@ -37,12 +36,15 @@ namespace CliFx.Models
var buffer = new StringBuilder();
if (!CommandName.IsNullOrWhiteSpace())
{
buffer.Append(CommandName);
buffer.Append(' ');
}
foreach (var option in Options)
{
buffer.Append(' ');
buffer.Append(option);
buffer.Append(' ');
}
return buffer.Trim().ToString();

View File

@@ -28,14 +28,17 @@ namespace CliFx.Models
var buffer = new StringBuilder();
if (!Name.IsNullOrWhiteSpace())
{
buffer.Append(Name);
buffer.Append(' ');
}
foreach (var option in Options)
{
buffer.Append(' ');
buffer.Append('[');
buffer.Append(option);
buffer.Append(']');
buffer.Append(' ');
}
return buffer.Trim().ToString();

View File

@@ -1,24 +0,0 @@
using System.Drawing;
namespace CliFx.Models
{
public class TextSpan
{
public string Text { get; }
public Color Color { get; }
public TextSpan(string text, Color color)
{
Text = text;
Color = color;
}
public TextSpan(string text)
: this(text, Color.Gray)
{
}
public override string ToString() => Text;
}
}

View File

@@ -1,10 +1,9 @@
using System;
using CliFx.Models;
namespace CliFx.Services
{
public class CommandFactory : ICommandFactory
{
public ICommand CreateCommand(CommandSchema schema) => (ICommand) Activator.CreateInstance(schema.Type);
public ICommand CreateCommand(Type commandType) => (ICommand) Activator.CreateInstance(commandType);
}
}

View File

@@ -1,173 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CliFx.Internal;
using CliFx.Models;
namespace CliFx.Services
{
// TODO: add color
public class CommandHelpTextBuilder : ICommandHelpTextBuilder
{
private IReadOnlyList<string> GetOptionAliasesWithPrefixes(CommandOptionSchema optionSchema)
{
var result = new List<string>();
if (!optionSchema.Name.IsNullOrWhiteSpace())
result.Add("--" + optionSchema.Name);
if (optionSchema.ShortName != null)
result.Add("-" + optionSchema.ShortName.Value);
return result;
}
private IReadOnlyList<CommandSchema> GetChildCommandSchemas(IReadOnlyList<CommandSchema> availableCommandSchemas,
CommandSchema parentCommandSchema)
{
// TODO: this doesn't really work properly, it shows all descendants instead of direct children
var prefix = !parentCommandSchema.Name.IsNullOrWhiteSpace() ? parentCommandSchema.Name + " " : "";
return availableCommandSchemas
.Where(c => !c.Name.IsNullOrWhiteSpace() && c.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToArray();
}
private void AddDescription(StringBuilder buffer, CommandSchema commands)
{
if (commands.Description.IsNullOrWhiteSpace())
return;
buffer.AppendLine("Description:");
buffer.Append(" ");
buffer.AppendLine(commands.Description);
buffer.AppendLine();
}
private void AddUsage(StringBuilder buffer, ApplicationMetadata applicationMetadata, CommandSchema command,
IReadOnlyList<CommandSchema> subCommands)
{
buffer.AppendLine("Usage:");
buffer.Append(" ");
buffer.Append(applicationMetadata.ExecutableName);
if (!command.Name.IsNullOrWhiteSpace())
{
buffer.Append(' ');
buffer.Append(command.Name);
}
if (subCommands.Any())
{
buffer.Append(' ');
buffer.Append("[command]");
}
buffer.Append(' ');
buffer.Append("[options]");
buffer.AppendLine().AppendLine();
}
private void AddOptions(StringBuilder buffer, CommandSchema command)
{
buffer.AppendLine("Options:");
foreach (var option in command.Options)
{
buffer.Append(option.IsRequired ? "* " : " ");
buffer.Append(GetOptionAliasesWithPrefixes(option).JoinToString("|"));
if (!option.Description.IsNullOrWhiteSpace())
{
buffer.Append(" ");
buffer.Append(option.Description);
}
buffer.AppendLine();
}
// Help option
{
buffer.Append(" ");
buffer.Append("--help|-h");
buffer.Append(" ");
buffer.Append("Shows helps text.");
buffer.AppendLine();
}
// Version option
if (command.Name.IsNullOrWhiteSpace())
{
buffer.Append(" ");
buffer.Append("--version");
buffer.Append(" ");
buffer.Append("Shows application version.");
buffer.AppendLine();
}
buffer.AppendLine();
}
private void AddSubCommands(StringBuilder buffer, IReadOnlyList<CommandSchema> subCommands)
{
if (!subCommands.Any())
return;
buffer.AppendLine("Commands:");
foreach (var command in subCommands)
{
buffer.Append(" ");
buffer.Append(command.Name);
if (!command.Description.IsNullOrWhiteSpace())
{
buffer.Append(" ");
buffer.Append(command.Description);
}
buffer.AppendLine();
}
buffer.AppendLine();
}
public string Build(ApplicationMetadata applicationMetadata,
IReadOnlyList<CommandSchema> availableCommandSchemas,
CommandSchema matchingCommandSchema)
{
var childCommandSchemas = GetChildCommandSchemas(availableCommandSchemas, matchingCommandSchema);
var buffer = new StringBuilder();
if (matchingCommandSchema.Name.IsNullOrWhiteSpace())
{
buffer.Append(applicationMetadata.Title);
buffer.Append(" v");
buffer.Append(applicationMetadata.VersionText);
buffer.AppendLine().AppendLine();
}
AddDescription(buffer, matchingCommandSchema);
AddUsage(buffer, applicationMetadata, matchingCommandSchema, childCommandSchemas);
AddOptions(buffer, matchingCommandSchema);
AddSubCommands(buffer, childCommandSchemas);
if (matchingCommandSchema.Name.IsNullOrWhiteSpace() && childCommandSchemas.Any())
{
buffer.Append("You can run ");
buffer.Append('`').Append(applicationMetadata.ExecutableName).Append(" [command] --help").Append('`');
buffer.Append(" to show help on a specific command.");
buffer.AppendLine();
}
return buffer.ToString().Trim();
}
}
}

View File

@@ -0,0 +1,210 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CliFx.Internal;
using CliFx.Models;
namespace CliFx.Services
{
public partial class CommandHelpTextRenderer : ICommandHelpTextRenderer
{
private readonly IConsole _console;
public CommandHelpTextRenderer(IConsole console)
{
_console = console;
}
public CommandHelpTextRenderer()
: this(new SystemConsole())
{
}
public void RenderHelpText(ApplicationMetadata applicationMetadata,
IReadOnlyList<CommandSchema> availableCommandSchemas, CommandSchema matchingCommandSchema = null) =>
new RenderHelpTextImpl(_console, applicationMetadata, availableCommandSchemas, matchingCommandSchema).Render();
}
public partial class CommandHelpTextRenderer
{
private class RenderHelpTextImpl
{
private readonly IConsole _console;
private readonly ApplicationMetadata _applicationMetadata;
private readonly IReadOnlyList<CommandSchema> _availableCommandSchemas;
private readonly CommandSchema _matchingCommandSchema;
private readonly IReadOnlyList<CommandSchema> _childCommandSchemas;
public RenderHelpTextImpl(IConsole console, ApplicationMetadata applicationMetadata,
IReadOnlyList<CommandSchema> availableCommandSchemas, CommandSchema matchingCommandSchema)
{
_console = console;
_applicationMetadata = applicationMetadata;
_availableCommandSchemas = availableCommandSchemas;
_matchingCommandSchema = matchingCommandSchema;
_childCommandSchemas = GetChildCommandSchemas();
}
private IReadOnlyList<CommandSchema> GetChildCommandSchemas()
{
// TODO:
var prefix = _matchingCommandSchema == null || _matchingCommandSchema.Name.IsNullOrWhiteSpace()
? ""
: _matchingCommandSchema.Name + " ";
return new CommandSchema[0];
}
private void RenderAppInfo()
{
if (_matchingCommandSchema != null && !_matchingCommandSchema.Name.IsNullOrWhiteSpace())
return;
_console.Output.Write(_applicationMetadata.Title);
_console.Output.Write(" v");
_console.Output.Write(_applicationMetadata.VersionText);
_console.Output.WriteLine();
_console.Output.WriteLine();
}
private void RenderDescription()
{
if (_matchingCommandSchema == null || _matchingCommandSchema.Description.IsNullOrWhiteSpace())
return;
_console.WithColor(ConsoleColor.Black, ConsoleColor.DarkCyan, c =>
{
c.Output.WriteLine("Description");
});
_console.Output.Write(" ");
_console.Output.Write(_matchingCommandSchema.Description);
_console.Output.WriteLine();
_console.Output.WriteLine();
}
private void RenderUsage()
{
var hasChildCommands = _childCommandSchemas.Any();
_console.WithColor(ConsoleColor.Black, ConsoleColor.DarkCyan, c =>
{
c.Output.WriteLine("Usage");
});
_console.Output.Write(" ");
_console.Output.Write(_applicationMetadata.ExecutableName);
_console.Output.Write(' ');
if (_matchingCommandSchema != null && !_matchingCommandSchema.Name.IsNullOrWhiteSpace())
{
_console.Output.Write(_matchingCommandSchema.Name);
_console.Output.Write(' ');
}
if (hasChildCommands)
{
_console.Output.Write("[command]");
_console.Output.Write(' ');
}
_console.Output.Write("[options]");
_console.Output.WriteLine();
_console.Output.WriteLine();
}
private void RenderOptions()
{
var options = new List<CommandOptionSchema>();
options.AddRange(_matchingCommandSchema?.Options ?? Enumerable.Empty<CommandOptionSchema>());
options.Add(new CommandOptionSchema(null, "help", 'h', null, false, "Shows help text."));
if (_matchingCommandSchema == null || _matchingCommandSchema.Name.IsNullOrWhiteSpace())
options.Add(new CommandOptionSchema(null, "version", 'v', null, false, "Shows application version."));
_console.WithColor(ConsoleColor.Black, ConsoleColor.DarkCyan, c =>
{
c.Output.WriteLine("Options");
});
foreach (var option in options)
{
_console.Output.Write(" ");
if (!option.Name.IsNullOrWhiteSpace())
{
_console.WithColor(option.IsRequired ? ConsoleColor.Yellow : ConsoleColor.White, c =>
{
c.Output.Write("--");
c.Output.Write(option.Name);
});
}
if (!option.Name.IsNullOrWhiteSpace() && option.ShortName != null)
{
_console.Output.Write('|');
}
if (option.ShortName != null)
{
_console.WithColor(option.IsRequired ? ConsoleColor.Yellow : ConsoleColor.White, c =>
{
c.Output.Write('-');
c.Output.Write(option.ShortName);
});
}
if (!option.Description.IsNullOrWhiteSpace())
{
_console.Output.Write(" ");
_console.Output.Write(option.Description);
}
_console.Output.WriteLine();
}
_console.Output.WriteLine();
}
private void RenderChildCommands()
{
// TODO
}
private void RenderSubCommandHelpTip()
{
if (!_childCommandSchemas.Any())
return;
_console.Output.Write("You can run `");
_console.Output.Write(_applicationMetadata.ExecutableName);
_console.Output.Write(' ');
if (_matchingCommandSchema != null && !_matchingCommandSchema.Name.IsNullOrWhiteSpace())
{
_console.Output.Write(_matchingCommandSchema.Name);
_console.Output.Write(' ');
}
_console.Output.Write("[command] --help` to show help on a specific command.");
_console.Output.WriteLine();
}
public void Render()
{
RenderAppInfo();
RenderDescription();
RenderUsage();
RenderOptions();
RenderChildCommands();
RenderSubCommandHelpTip();
}
}
}
}

View File

@@ -20,7 +20,8 @@ namespace CliFx.Services
attribute.Name,
attribute.ShortName,
attribute.GroupName,
attribute.IsRequired, attribute.Description);
attribute.IsRequired,
attribute.Description);
}
// TODO: validate stuff like duplicate names, multiple default commands, etc

View File

@@ -1,32 +0,0 @@
using System;
using System.IO;
using CliFx.Models;
namespace CliFx.Services
{
public partial class ConsoleWriter : IConsoleWriter, IDisposable
{
private readonly TextWriter _textWriter;
private readonly bool _isRedirected;
public ConsoleWriter(TextWriter textWriter, bool isRedirected)
{
_textWriter = textWriter;
_isRedirected = isRedirected;
}
// TODO: handle colors
public void Write(TextSpan text) => _textWriter.Write(text.Text);
public void WriteLine(TextSpan text) => _textWriter.WriteLine(text.Text);
public void Dispose() => _textWriter.Dispose();
}
public partial class ConsoleWriter
{
public static ConsoleWriter GetStandardOutput() => new ConsoleWriter(Console.Out, Console.IsOutputRedirected);
public static ConsoleWriter GetStandardError() => new ConsoleWriter(Console.Error, Console.IsErrorRedirected);
}
}

View File

@@ -1,37 +1,28 @@
using System;
using System.Globalization;
using CliFx.Models;
namespace CliFx.Services
{
public static class Extensions
{
public static void Write(this IConsoleWriter consoleWriter, string text) =>
consoleWriter.Write(new TextSpan(text));
public static void Write(this IConsoleWriter consoleWriter, IFormattable formattable) =>
consoleWriter.Write(formattable.ToString(null, CultureInfo.InvariantCulture));
public static void Write(this IConsoleWriter consoleWriter, object obj)
public static void WithColor(this IConsole console, ConsoleColor foregroundColor, Action<IConsole> action)
{
if (obj is IFormattable formattable)
consoleWriter.Write(formattable);
else
consoleWriter.Write(obj.ToString());
var lastForegroundColor = console.ForegroundColor;
console.ForegroundColor = foregroundColor;
action(console);
console.ForegroundColor = lastForegroundColor;
}
public static void WriteLine(this IConsoleWriter consoleWriter, string text) =>
consoleWriter.WriteLine(new TextSpan(text));
public static void WriteLine(this IConsoleWriter consoleWriter, IFormattable formattable) =>
consoleWriter.WriteLine(formattable.ToString(null, CultureInfo.InvariantCulture));
public static void WriteLine(this IConsoleWriter consoleWriter, object obj)
public static void WithColor(this IConsole console, ConsoleColor foregroundColor, ConsoleColor backgroundColor,
Action<IConsole> action)
{
if (obj is IFormattable formattable)
consoleWriter.WriteLine(formattable);
else
consoleWriter.WriteLine(obj.ToString());
var lastBackgroundColor = console.BackgroundColor;
console.BackgroundColor = backgroundColor;
console.WithColor(foregroundColor, action);
console.BackgroundColor = lastBackgroundColor;
}
}
}

View File

@@ -1,10 +1,9 @@
using System;
using CliFx.Models;
namespace CliFx.Services
{
public interface ICommandFactory
{
ICommand CreateCommand(CommandSchema schema);
ICommand CreateCommand(Type commandType);
}
}

View File

@@ -3,10 +3,9 @@ using CliFx.Models;
namespace CliFx.Services
{
public interface ICommandHelpTextBuilder
public interface ICommandHelpTextRenderer
{
string Build(ApplicationMetadata applicationMetadata,
IReadOnlyList<CommandSchema> availableCommandSchemas,
CommandSchema matchingCommandSchema);
void RenderHelpText(ApplicationMetadata applicationMetadata,
IReadOnlyList<CommandSchema> availableCommandSchemas, CommandSchema matchingCommandSchema = null);
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.IO;
namespace CliFx.Services
{
public interface IConsole
{
TextReader Input { get; }
bool IsInputRedirected { get; }
TextWriter Output { get; }
bool IsOutputRedirected { get; }
TextWriter Error { get; }
bool IsErrorRedirected { get; }
ConsoleColor ForegroundColor { get; set; }
ConsoleColor BackgroundColor { get; set; }
void ResetColor();
}
}

View File

@@ -1,11 +0,0 @@
using CliFx.Models;
namespace CliFx.Services
{
public interface IConsoleWriter
{
void Write(TextSpan text);
void WriteLine(TextSpan text);
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.IO;
namespace CliFx.Services
{
public class SystemConsole : IConsole
{
public TextReader Input => Console.In;
public bool IsInputRedirected => Console.IsInputRedirected;
public TextWriter Output => Console.Out;
public bool IsOutputRedirected => Console.IsOutputRedirected;
public TextWriter Error => Console.Error;
public bool IsErrorRedirected => Console.IsErrorRedirected;
public ConsoleColor ForegroundColor
{
get => Console.ForegroundColor;
set => Console.ForegroundColor = value;
}
public ConsoleColor BackgroundColor
{
get => Console.BackgroundColor;
set => Console.BackgroundColor = value;
}
public void ResetColor() => Console.ResetColor();
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.IO;
namespace CliFx.Services
{
public class TestConsole : IConsole
{
public TextReader Input { get; }
public bool IsInputRedirected => true;
public TextWriter Output { get; }
public bool IsOutputRedirected => true;
public TextWriter Error { get; }
public bool IsErrorRedirected => true;
public ConsoleColor ForegroundColor { get; set; } = ConsoleColor.Gray;
public ConsoleColor BackgroundColor { get; set; } = ConsoleColor.Black;
public TestConsole(TextReader input, TextWriter output, TextWriter error)
{
Input = input;
Output = output;
Error = error;
}
public TestConsole(TextWriter output, TextWriter error)
: this(TextReader.Null, output, error)
{
}
public TestConsole(TextWriter output)
: this(output, TextWriter.Null)
{
}
public void ResetColor()
{
ForegroundColor = ConsoleColor.Gray;
BackgroundColor = ConsoleColor.Black;
}
}
}