diff --git a/Documentation/screenplay/tool.md b/Documentation/screenplay/tool.md
index e48ea79..2d436e3 100644
--- a/Documentation/screenplay/tool.md
+++ b/Documentation/screenplay/tool.md
@@ -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
@@ -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
@@ -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).
diff --git a/Source/DotNET/Screenplay/Files/IPlayFileCompiler.cs b/Source/DotNET/Screenplay/Files/IPlayFileCompiler.cs
index ebed6f8..93c3713 100644
--- a/Source/DotNET/Screenplay/Files/IPlayFileCompiler.cs
+++ b/Source/DotNET/Screenplay/Files/IPlayFileCompiler.cs
@@ -4,7 +4,7 @@
namespace Cratis.Screenplay.Files;
///
-/// Defines a system that discovers and compiles every .play file beneath a directory.
+/// Defines a system that compiles .play files - a single file, or every file beneath a directory.
///
public interface IPlayFileCompiler
{
@@ -14,4 +14,15 @@ public interface IPlayFileCompiler
/// The root directory to search from.
/// A per discovered file.
IEnumerable CompileIn(string root);
+
+ ///
+ /// Compiles a single .play file.
+ ///
+ /// The path of the file to compile.
+ /// The of the file.
+ ///
+ /// The resulting is the file name, so diagnostics read the same
+ /// way they do for a discovered file.
+ ///
+ PlayFileCompilation CompileFile(string path);
}
diff --git a/Source/DotNET/Screenplay/Files/PlayFileCompiler.cs b/Source/DotNET/Screenplay/Files/PlayFileCompiler.cs
index 0e42cc7..39ad58b 100644
--- a/Source/DotNET/Screenplay/Files/PlayFileCompiler.cs
+++ b/Source/DotNET/Screenplay/Files/PlayFileCompiler.cs
@@ -20,10 +20,18 @@ public PlayFileCompiler()
///
public IEnumerable 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)];
+
+ ///
+ 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));
+ }
}
diff --git a/Source/DotNET/Screenplay/for_PlayFileCompiler/given/a_play_file_compiler.cs b/Source/DotNET/Screenplay/for_PlayFileCompiler/given/a_play_file_compiler.cs
new file mode 100644
index 0000000..9553e55
--- /dev/null
+++ b/Source/DotNET/Screenplay/for_PlayFileCompiler/given/a_play_file_compiler.cs
@@ -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();
+ _compiler = new(_playFiles, new ScreenplayCompiler());
+ }
+}
diff --git a/Source/DotNET/Screenplay/for_PlayFileCompiler/when_compiling_a_single_file.cs b/Source/DotNET/Screenplay/for_PlayFileCompiler/when_compiling_a_single_file.cs
new file mode 100644
index 0000000..e037b92
--- /dev/null
+++ b/Source/DotNET/Screenplay/for_PlayFileCompiler/when_compiling_a_single_file.cs
@@ -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()).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());
+ [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();
+}
diff --git a/Source/DotNET/Tool/Program.cs b/Source/DotNET/Tool/Program.cs
index 98e2b58..ee1e3ec 100644
--- a/Source/DotNET/Tool/Program.cs
+++ b/Source/DotNET/Tool/Program.cs
@@ -4,10 +4,19 @@
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;
}
@@ -15,10 +24,16 @@
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;
}
@@ -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";
}
@@ -69,4 +85,4 @@
Console.WriteLine(summary);
}
-return errors > 0 ? 1 : 0;
+return failed ? 1 : 0;