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
27 changes: 26 additions & 1 deletion Documentation/screenplay/tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ You can also point it at a specific directory:
screenplay path/to/screenplays
```

Or at a single file, which is what you want when a generator just produced one and you only care about that one:

```bash
screenplay path/to/invoicing.play
```

Each problem is reported with its file, line and column, the offending source line and a caret pointing at the exact location:

```text
Expand All @@ -36,7 +42,20 @@ nested/broken.play(3,5): error: Unknown slice type 'Wat' - expected StateChange,
2 file(s) compiled - 1 error(s), 0 warning(s)
```

The exit code is `0` when everything compiles without errors and `1` otherwise, so the command slots straight into CI pipelines. Colors are enabled automatically on interactive terminals; disable them with `--no-color` or by setting the `NO_COLOR` environment variable.
The exit code is `0` when everything compiles without errors and `1` otherwise, so the command slots straight into CI pipelines.

Warnings do not fail the run by default. When a pipeline demands a spotless document - a generated one, say - add `--warnaserror` and a single warning is enough to exit `1`:

```bash
screenplay path/to/invoicing.play --warnaserror
```

| Option | Effect |
|---|---|
| `--warnaserror` | Warnings fail the run - exit code `1` even with zero errors |
| `--no-color` | Never colorize output |

Colors are enabled automatically on interactive terminals; disable them with `--no-color` or by setting the `NO_COLOR` environment variable.

## Use the compiler as a library

Expand Down Expand Up @@ -70,4 +89,10 @@ using Cratis.Screenplay.Files;
var compilations = new PlayFileCompiler().CompileIn(rootDirectory);
```

A single file is a single call too:

```csharp
var compilation = new PlayFileCompiler().CompileFile(path);
```

To go the other way - turn a syntax tree back into `.play` text, or generate Screenplay from a model - see [Printing and generating](printing.md).
13 changes: 12 additions & 1 deletion Source/DotNET/Screenplay/Files/IPlayFileCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Cratis.Screenplay.Files;

/// <summary>
/// Defines a system that discovers and compiles every <c>.play</c> file beneath a directory.
/// Defines a system that compiles <c>.play</c> files - a single file, or every file beneath a directory.
/// </summary>
public interface IPlayFileCompiler
{
Expand All @@ -14,4 +14,15 @@ public interface IPlayFileCompiler
/// <param name="root">The root directory to search from.</param>
/// <returns>A <see cref="PlayFileCompilation"/> per discovered file.</returns>
IEnumerable<PlayFileCompilation> CompileIn(string root);

/// <summary>
/// Compiles a single <c>.play</c> file.
/// </summary>
/// <param name="path">The path of the file to compile.</param>
/// <returns>The <see cref="PlayFileCompilation"/> of the file.</returns>
/// <remarks>
/// The resulting <see cref="PlayFile.RelativePath"/> is the file name, so diagnostics read the same
/// way they do for a discovered file.
/// </remarks>
PlayFileCompilation CompileFile(string path);
}
20 changes: 14 additions & 6 deletions Source/DotNET/Screenplay/Files/PlayFileCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ public PlayFileCompiler()

/// <inheritdoc/>
public IEnumerable<PlayFileCompilation> CompileIn(string root) =>
[.. playFiles.FindIn(root)
.Select(file =>
{
var source = playFiles.ReadContent(file);
return new PlayFileCompilation(file, source, compiler.Compile(source));
})];
[.. playFiles.FindIn(root).Select(Compile)];

/// <inheritdoc/>
public PlayFileCompilation CompileFile(string path)
{
var full = System.IO.Path.GetFullPath(path);
return Compile(new(full, System.IO.Path.GetFileName(full)));
}

PlayFileCompilation Compile(PlayFile file)
{
var source = playFiles.ReadContent(file);
return new(file, source, compiler.Compile(source));
}
}
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.

using Cratis.Screenplay.Files;

namespace Cratis.Screenplay.for_PlayFileCompiler.given;

public class a_play_file_compiler : Specification
{
protected IPlayFiles _playFiles;
protected PlayFileCompiler _compiler;

void Establish()
{
_playFiles = Substitute.For<IPlayFiles>();
_compiler = new(_playFiles, new ScreenplayCompiler());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Cratis.Screenplay.Files;

namespace Cratis.Screenplay.for_PlayFileCompiler;

public class when_compiling_a_single_file : given.a_play_file_compiler
{
const string Source =
"""
module Invoicing
feature InvoiceManagement
slice StateChange RegisterInvoice
command RegisterInvoice
invoiceId Uuid
""";

PlayFileCompilation _compilation;

void Establish() => _playFiles.ReadContent(Arg.Any<PlayFile>()).Returns(Source);

void Because() => _compilation = _compiler.CompileFile(Path.Combine("some", "where", "invoicing.play"));

[Fact] void should_not_discover_any_files() => _playFiles.DidNotReceive().FindIn(Arg.Any<string>());
[Fact] void should_compile_the_file() => _compilation.Result.Success.ShouldBeTrue();
[Fact] void should_have_no_diagnostics() => _compilation.Result.Diagnostics.ShouldBeEmpty();
[Fact] void should_keep_the_source() => _compilation.Source.ShouldEqual(Source);
[Fact] void should_use_the_file_name_as_the_relative_path() => _compilation.File.RelativePath.ShouldEqual("invoicing.play");
[Fact] void should_resolve_the_full_path() => Path.IsPathFullyQualified(_compilation.File.Path).ShouldBeTrue();
}
30 changes: 23 additions & 7 deletions Source/DotNET/Tool/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,36 @@
using Cratis.Screenplay.Diagnostics;
using Cratis.Screenplay.Files;

var root = args.FirstOrDefault(arg => !arg.StartsWith('-')) ?? Directory.GetCurrentDirectory();
if (!Directory.Exists(root))
const string PlayExtension = ".play";

var target = args.FirstOrDefault(arg => !arg.StartsWith('-')) ?? Directory.GetCurrentDirectory();
var isFile = File.Exists(target);
if (!isFile && !Directory.Exists(target))
{
Console.Error.WriteLine($"Directory '{root}' does not exist");
Console.Error.WriteLine($"'{target}' does not exist");
return 1;
}

if (isFile && !target.EndsWith(PlayExtension, StringComparison.OrdinalIgnoreCase))
{
Console.Error.WriteLine($"'{target}' is not a {PlayExtension} file");
return 1;
}

var useColors = !Console.IsOutputRedirected &&
Environment.GetEnvironmentVariable("NO_COLOR") is null &&
!args.Contains("--no-color");

var compilations = new PlayFileCompiler().CompileIn(root).ToArray();
var warnAsError = args.Contains("--warnaserror");

var playFileCompiler = new PlayFileCompiler();
var compilations = isFile
? [playFileCompiler.CompileFile(target)]
: playFileCompiler.CompileIn(target).ToArray();

if (compilations.Length == 0)
{
Console.WriteLine($"No .play files found beneath {root}");
Console.WriteLine($"No {PlayExtension} files found beneath {target}");
return 0;
}

Expand Down Expand Up @@ -49,11 +64,12 @@
Console.WriteLine();
}

var failed = errors > 0 || (warnAsError && warnings > 0);
var summary = $"{compilations.Length} file(s) compiled - {errors} error(s), {warnings} warning(s)";
if (useColors)
{
var color = "\e[32m";
if (errors > 0)
if (failed)
{
color = "\e[31m";
}
Expand All @@ -69,4 +85,4 @@
Console.WriteLine(summary);
}

return errors > 0 ? 1 : 0;
return failed ? 1 : 0;
Loading