Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<PackageVersion Include="Cratis.Prologue.Interpreter.Contracts" Version="1.2.0" />
<PackageVersion Include="Cratis.Prologue.Screenplay" Version="1.2.0" />
<PackageVersion Include="Cratis.Screenplay" Version="2.1.0" />
<PackageVersion Include="Cratis.Stage.Contracts" Version="2.4.0" />
<PackageVersion Include="Cratis.Stage.Rendering.Cratis" Version="2.4.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.10" />
<!-- CLI -->
<PackageVersion Include="MongoDB.Driver" Version="3.10.0" />
Expand Down
2 changes: 2 additions & 0 deletions Source/Cli.Specs/Cli.Specs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
</ItemGroup>

<ItemGroup>
<!-- MSBuildLocator refuses to run with a copy of this beside the test host; it comes from the SDK. -->
<PackageReference Include="Microsoft.Build.Framework" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Cratis.Specifications.XUnit" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
Expand Down
1 change: 1 addition & 0 deletions Source/Cli.Specs/Usings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
global using Cratis.Cli.Commands.Init;
global using Cratis.Cli.Commands.Llm;
global using Cratis.Cli.Commands.Prologue;
global using Cratis.Cli.Commands.Render;
global using Cratis.Cli.Commands.Run;
global using Cratis.Cli.Commands.Screenplay;
global using Cratis.Specifications;
Expand Down
56 changes: 56 additions & 0 deletions Source/Cli.Specs/for_RenderCommand/given/a_render_command.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.for_RenderCommand.given;

/// <summary>
/// Base context that puts a document in a temporary folder and substitutes the rendering.
/// </summary>
public class a_render_command : Specification
{
protected string _folder;
protected string _document;
protected string _previousDirectory;
protected IScreenplayRendering _rendering;
protected RenderCommand _command;
protected RenderSettings _settings;

void Establish()
{
var created = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())).FullName;
_previousDirectory = Directory.GetCurrentDirectory();
Directory.SetCurrentDirectory(created);

// Read the folder back so that specs compare against the same fully resolved path the command sees —
// the temp folder is reached through a symbolic link on macOS.
_folder = Directory.GetCurrentDirectory();
_document = Path.Combine(_folder, "MyApp.play");
File.WriteAllText(_document, "domain Library\n");

_rendering = Substitute.For<IScreenplayRendering>();
_rendering.Render(Arg.Any<string>(), Arg.Any<string>()).Returns(new RenderedScreenplay(1, [], []));

_command = new RenderCommand(_rendering);
_settings = new RenderSettings { Output = OutputFormats.JsonCompact };
}

/// <summary>
/// Executes the command with the established settings.
/// </summary>
/// <returns>The exit code.</returns>
protected Task<int> Execute() =>
((ICommand<RenderSettings>)_command).ExecuteAsync(
new CommandContext([], Substitute.For<IRemainingArguments>(), "render", null),
_settings,
CancellationToken.None);

void Destroy()
{
Directory.SetCurrentDirectory(_previousDirectory);

if (Directory.Exists(_folder))
{
Directory.Delete(_folder, true);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.for_RenderCommand.when_rendering;

[Collection(CliSpecsCollection.Name)]
public class and_an_output_directory_is_given : given.a_render_command
{
int _result;

void Establish()
{
_settings.Path = "MyApp.play";
_settings.Target = "src/MyApp";
}

async Task Because() => _result = await Execute();

[Fact] void should_succeed() => _result.ShouldEqual(ExitCodes.Success);
[Fact] void should_render_into_it() => _rendering.Received(1).Render(_document, Path.Combine(_folder, "src", "MyApp"));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.for_RenderCommand.when_rendering;

[Collection(CliSpecsCollection.Name)]
public class and_no_documents_are_found : given.a_render_command
{
int _result;

void Establish() => _rendering.Render(Arg.Any<string>(), Arg.Any<string>()).Returns(new RenderedScreenplay(0, [], []));

async Task Because() => _result = await Execute();

[Fact] void should_report_that_nothing_was_found() => _result.ShouldEqual(ExitCodes.NotFound);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.for_RenderCommand.when_rendering;

/// <summary>
/// A Screenplay document states more than any one target can express. What does not survive the crossing is
/// reported rather than dropped silently — and it is not a failure, so the command still succeeds.
/// </summary>
[Collection(CliSpecsCollection.Name)]
public class and_the_application_cannot_carry_everything : given.a_render_command
{
int _result;
string _error;
TextWriter _previousError;
StringWriter _capturedError;

void Establish()
{
_previousError = Console.Error;
_capturedError = new StringWriter();
Console.SetError(_capturedError);

_rendering
.Render(Arg.Any<string>(), Arg.Any<string>())
.Returns(new RenderedScreenplay(1, [], ["Slice 'Register' declares 2 screen declaration(s) with no rendered equivalent"]));
}

async Task Because()
{
_result = await Execute();
_error = _capturedError.ToString();
}

[Fact] void should_succeed() => _result.ShouldEqual(ExitCodes.Success);
[Fact] void should_say_what_the_rendered_application_does_not_carry() =>
_error.ShouldContain("Slice 'Register' declares 2 screen declaration(s) with no rendered equivalent");

void Destroy() => Console.SetError(_previousError);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.for_RenderCommand.when_rendering;

[Collection(CliSpecsCollection.Name)]
public class and_the_document_has_an_error : given.a_render_command
{
int _result;

void Establish() =>
_rendering
.Render(Arg.Any<string>(), Arg.Any<string>())
.Returns(new RenderedScreenplay(
1,
[new ScreenplayDiagnostic(ScreenplayDiagnosticSeverity.Error, "PLAY0001", "an error", "MyApp.play(3,1)")],
[]));

async Task Because() => _result = await Execute();

[Fact] void should_report_a_validation_error() => _result.ShouldEqual(ExitCodes.ValidationError);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.for_RenderCommand.when_rendering;

[Collection(CliSpecsCollection.Name)]
public class and_the_document_renders : given.a_render_command
{
int _result;

void Establish() => _settings.Path = "MyApp.play";

async Task Because() => _result = await Execute();

[Fact] void should_succeed() => _result.ShouldEqual(ExitCodes.Success);
[Fact] void should_render_the_resolved_document() =>
_rendering.Received(1).Render(_document, Path.Combine(_folder, RenderCommand.DefaultTarget));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.for_RenderCommand.when_rendering;

[Collection(CliSpecsCollection.Name)]
public class and_the_path_cannot_be_resolved : given.a_render_command
{
int _result;

void Establish() => _settings.Path = "Missing/MyApp.play";

async Task Because() => _result = await Execute();

[Fact] void should_report_that_it_was_not_found() => _result.ShouldEqual(ExitCodes.NotFound);
[Fact] void should_not_render_anything() => _rendering.DidNotReceive().Render(Arg.Any<string>(), Arg.Any<string>());
}
3 changes: 3 additions & 0 deletions Source/Cli/Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
<PackageReference Include="Cratis.Prologue.Interpreter.Contracts" />
<PackageReference Include="Cratis.Prologue.Screenplay" />
<PackageReference Include="Cratis.Screenplay" />
<!-- Rendering a Screenplay document into a Cratis application for 'cratis render'. -->
<PackageReference Include="Cratis.Stage.Contracts" />
<PackageReference Include="Cratis.Stage.Rendering.Cratis" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Spectre.Console" />
<PackageReference Include="Spectre.Console.Cli" />
Expand Down
25 changes: 25 additions & 0 deletions Source/Cli/Commands/Render/IScreenplayRendering.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Cratis.Cli.Commands.Screenplay;

namespace Cratis.Cli.Commands.Render;

/// <summary>
/// Defines a system that renders Screenplay documents into a working application.
/// </summary>
/// <remarks>
/// The seam between the CLI and the Stage renderer, mirroring <see cref="IScreenplayValidation"/> on the other
/// arrow. Everything the CLI does around rendering — resolving the path, reporting, deciding the exit code — is
/// expressed against this interface so it stays independent of which target is rendered into.
/// </remarks>
public interface IScreenplayRendering
{
/// <summary>
/// Renders the Screenplay document, or every document beneath the folder, into the target directory.
/// </summary>
/// <param name="targetPath">The full path of a <c>.play</c> file, or of a folder to search.</param>
/// <param name="outputDirectory">The full path of the directory to render into.</param>
/// <returns>The <see cref="RenderedScreenplay"/> holding what was rendered and what could not be.</returns>
Task<RenderedScreenplay> Render(string targetPath, string outputDirectory);
}
Loading
Loading