Commit 60e316e0 authored by Tim Bureck's avatar Tim Bureck

Add async application and commands API

* Add IAsyncApplication and AsyncApplication with RunAsync methods
* Extract IApplication interface from Application
* Move ArgsInput.Bind method to IInput interface
* Simplify Application.Run implementation
parent eaa88258
Pipeline #17 passed with stages
in 1 minute and 46 seconds
......@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Async application and commands API
## [0.2.0-alpha2] - 2019-07-06
### Changed
......
......@@ -7,7 +7,7 @@ using TBureck.Terminal.IO;
namespace TBureck.Terminal
{
public class Application : ICommandContainer
public class Application : ICommandContainer, IApplication
{
public IList<ICommand> Commands { get; }
......@@ -42,37 +42,31 @@ namespace TBureck.Terminal
public int Run(IList<string> args, IInput input = null, IOutput output = null)
{
if (output == null) {
output = new ConsoleOutput();
}
if (args.Count == 0) {
return 1;
}
output = output ?? CreateDefaultOutput();
int exitCode;
try {
// Treat first argument as the command name:
string commandName = args.FirstOrDefault();
ICommand command = this.FindCommand(commandName);
if (input == null) {
ArgsInput argsInput = ArgsInput
.Of(args.Skip(1).ToList());
argsInput.Bind(command.InputDefinition);
input = argsInput;
}
input = input ?? CreateDefaultInput(args);
input.Bind(command.InputDefinition);
exitCode = command.Execute(input, output);
return command.Execute(input, output);
} catch (Exception e) {
output.WriteLine(e.Message);
exitCode = 1;
return 1;
}
}
public static ArgsInput CreateDefaultInput(IList<string> args)
{
return ArgsInput.Of(args.Skip(1).ToList());
}
return exitCode;
public static IOutput CreateDefaultOutput()
{
return new ConsoleOutput();
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TBureck.Terminal.Commands;
using TBureck.Terminal.IO;
namespace TBureck.Terminal
{
public class AsyncApplication : Application, IAsyncApplication
{
public async Task<int> RunAsync()
{
return await this.RunAsync(new List<string>());
}
public async Task<int> RunAsync(IList<string> arguments, IInput input = null, IOutput output = null)
{
output = output ?? CreateDefaultOutput();
try {
string commandName = arguments.FirstOrDefault();
ICommand command = this.FindCommand(commandName);
input = input ?? CreateDefaultInput(arguments);
input.Bind(command.InputDefinition);
if (command is IAsyncCommand asyncCommand) {
return await asyncCommand.ExecuteAsync(input, output);
}
return command.Execute(input, output);
} catch (Exception e) {
output.WriteLine(e.Message);
return 1;
}
}
}
}
\ No newline at end of file
using System.Threading.Tasks;
using TBureck.Terminal.IO;
namespace TBureck.Terminal.Commands
{
public interface IAsyncCommand : ICommand
{
Task<int> ExecuteAsync(IInput input, IOutput output);
}
}
\ No newline at end of file
using System.Collections.Generic;
using TBureck.Terminal.Commands;
using TBureck.Terminal.IO;
namespace TBureck.Terminal
{
public interface IApplication
{
void Add(ICommand command);
int Run();
int Run(IList<string> args, IInput input = null, IOutput output = null);
}
}
\ No newline at end of file
using System.Collections.Generic;
using System.Threading.Tasks;
using TBureck.Terminal.IO;
namespace TBureck.Terminal
{
public interface IAsyncApplication
{
Task<int> RunAsync();
Task<int> RunAsync(IList<string> arguments, IInput input = null, IOutput output = null);
}
}
\ No newline at end of file
......@@ -8,5 +8,7 @@ namespace TBureck.Terminal.IO
IDictionary<string, string> Arguments { get; }
IDictionary<string, object> Options { get; }
void Bind(InputDefinition inputDefinition);
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using Moq;
using TBureck.Terminal;
using TBureck.Terminal.Commands;
using TBureck.Terminal.IO;
using Xunit;
namespace TBureck.Tests.Terminal
{
public class AsyncApplicationTest
{
[Fact]
public async void Run_NoCommandGiven_ReturnsOne()
{
AsyncApplication app = new AsyncApplication();
Assert.Equal(1, await app.RunAsync());
}
[Fact]
public async void Run_UnknownCommandGiven_Returns1()
{
Mock<IOutput> output = new Mock<IOutput>();
AsyncApplication app = new AsyncApplication();
Assert.Equal(1, await app.RunAsync(new List<string> {
"unknown"
}, output: output.Object));
output.Verify(o => o.WriteLine(It.IsRegex("Command \"unknown\" could not be found\\.")));
}
[Fact]
public async void Run_UnknownCommandGivenWithoutSpecifiedOuput_StillReturns1()
{
AsyncApplication app = new AsyncApplication();
Assert.Equal(1, await app.RunAsync(new List<string> { "unknown" }));
}
[Fact]
public async void Run_WithCommand_ReturnsCommandsReturnCode()
{
AsyncApplication app = new AsyncApplication();
Mock<ICommand> command1 = new Mock<ICommand>();
command1.SetupGet(c => c.Name).Returns("command1");
command1.SetupGet(c => c.InputDefinition).Returns(new InputDefinition());
command1.Setup(c => c.Execute(It.IsAny<IInput>(), It.IsAny<IOutput>())).Returns(0);
Mock<ICommand> command2 = new Mock<ICommand>();
command2.SetupGet(c => c.Name).Returns("command2");
command2.SetupGet(c => c.InputDefinition).Returns(new InputDefinition());
command2.Setup(c => c.Execute(It.IsAny<IInput>(), It.IsAny<IOutput>())).Returns(5);
app.Add(command1.Object);
app.Add(command2.Object);
Assert.Equal(0, await app.RunAsync(new List<string> { "command1" }));
Assert.Equal(5, await app.RunAsync(new List<string> { "command2" }));
}
[Fact]
public async void Run_WithAsyncCommand_IsBeingCalledAsync()
{
AsyncApplication app = new AsyncApplication();
Mock<IAsyncCommand> command1 = new Mock<IAsyncCommand>();
command1.SetupGet(c => c.Name).Returns("command1");
command1.SetupGet(c => c.InputDefinition).Returns(new InputDefinition());
command1.Setup(c => c.ExecuteAsync(It.IsAny<IInput>(), It.IsAny<IOutput>()))
.ReturnsAsync(0);
app.Add(command1.Object);
await app.RunAsync(new List<string> {"command1"});
command1.Verify(c => c.ExecuteAsync(It.IsAny<IInput>(), It.IsAny<IOutput>()));
}
[Fact]
public async void Run_CommandWithException_RendersException()
{
Mock<IOutput> output = new Mock<IOutput>();
AsyncApplication app = new AsyncApplication();
Mock<ICommand> command = new Mock<ICommand>();
command.SetupGet(c => c.Name).Returns("command");
command.SetupGet(c => c.InputDefinition).Returns(new InputDefinition());
command
.Setup(c => c.Execute(It.IsAny<IInput>(), It.IsAny<IOutput>()))
.Throws(new Exception("This command has failed executing!"));
app.Add(command.Object);
Assert.Equal(1, await app.RunAsync(new List<string> { "command" }, output: output.Object));
output.Verify(o => o.WriteLine(It.IsRegex("This command has failed executing!")));
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment