Compare commits
13 commits
Author | SHA1 | Date | |
---|---|---|---|
D. Moonfire | 5a993e85cb | ||
D. Moonfire | aac4b4373d | ||
D. Moonfire | 22ddae11f8 | ||
D. Moonfire | b32ca7582c | ||
D. Moonfire | 07eb12414a | ||
D. Moonfire | 82e1bc3c28 | ||
D. Moonfire | 070cf2bfb8 | ||
D. Moonfire | 185980b5c4 | ||
D. Moonfire | 2e93ebdb7c | ||
D. Moonfire | bca501d4e5 | ||
D. Moonfire | 189273692c | ||
D. Moonfire | 2892ec3445 | ||
D. Moonfire | 08aafb144c |
|
@ -10,20 +10,14 @@ pipeline:
|
|||
commands:
|
||||
- nix develop --command scripts/build.sh
|
||||
when:
|
||||
event: [push, pull_request, tag]
|
||||
tag: v*
|
||||
event: [push, pull_request, manual]
|
||||
|
||||
test:
|
||||
image: registry.gitlab.com/dmoonfire/nix-flake-docker:latest
|
||||
commands:
|
||||
- nix develop --command scripts/test.sh
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
#paths:
|
||||
# - ./**/*test-result.xml
|
||||
# - ./coverage/Cobertura.xml
|
||||
# - ./coverage/Summary.*
|
||||
# - ./**/*.nupkg
|
||||
event: [push, pull_request, manual]
|
||||
|
||||
release-main:
|
||||
image: registry.gitlab.com/dmoonfire/nix-flake-docker:latest
|
||||
|
@ -32,5 +26,5 @@ pipeline:
|
|||
secrets:
|
||||
- gitea_token
|
||||
when:
|
||||
event: push
|
||||
event: [push, manual]
|
||||
branch: main
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride;
|
||||
|
@ -43,7 +44,9 @@ public class CopyFilesPipeline : PipelineBase
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IAsyncEnumerable<Entity> RunAsync(IEnumerable<Entity> _)
|
||||
public override IAsyncEnumerable<Entity> RunAsync(
|
||||
IEnumerable<Entity> _,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// We don't care about the incoming entities which means we can
|
||||
// ignore them and use the entities from the ReadFiles operation
|
||||
|
@ -73,8 +76,9 @@ public class CopyFilesPipeline : PipelineBase
|
|||
// read. Coming out of this, we will have one entity that fulfills:
|
||||
//
|
||||
// entity.Get<UPath> == "/output/a.txt"
|
||||
entities = entities.Run(this.removePathPrefix)
|
||||
.Run(this.addPathPrefix);
|
||||
entities = entities
|
||||
.Run(this.removePathPrefix, cancellationToken)
|
||||
.Run(this.addPathPrefix, cancellationToken);
|
||||
|
||||
// Then we write out the files to the output. First we make sure we
|
||||
// clear out the output. This operation performs an action when it
|
||||
|
@ -95,8 +99,9 @@ public class CopyFilesPipeline : PipelineBase
|
|||
// The third way is to use an extension on entities which lets us
|
||||
// chain calls, ala Gulp's pipelines. The below code does this along
|
||||
// with writing the files to the output.
|
||||
entities = entities.Run(this.clearDirectory)
|
||||
.Run(this.writeFiles);
|
||||
entities = entities
|
||||
.Run(this.clearDirectory, cancellationToken)
|
||||
.Run(this.writeFiles, cancellationToken);
|
||||
|
||||
// If we are chaining this pipeline into another, we return the
|
||||
// entities. Otherwise, we can just return an empty list. The
|
||||
|
|
|
@ -27,9 +27,10 @@ public class CopyFilesTest : NitrideTestBase
|
|||
public async Task Run()
|
||||
{
|
||||
// Figure out the paths for this test.
|
||||
DirectoryInfo rootDir =
|
||||
typeof(CopyFilesProgram).GetDirectory()!.FindGitRoot()!
|
||||
.GetDirectory("examples/CopyFiles");
|
||||
DirectoryInfo rootDir = typeof(CopyFilesProgram)
|
||||
.GetDirectory()
|
||||
!.FindGitRoot()!
|
||||
.GetDirectory("examples/CopyFiles");
|
||||
|
||||
DirectoryInfo outputDir = rootDir.GetDirectory("output");
|
||||
FileInfo projectFile = rootDir.GetFile("CopyFiles.csproj");
|
||||
|
|
|
@ -13,6 +13,7 @@ fi
|
|||
# Clean up everything from the previous runs.
|
||||
echo "$(basename $0): cleaning project"
|
||||
dotnet clean
|
||||
rm -f src/*/bin/Debug/*.nupkg
|
||||
|
||||
# Version the file based on the Git repository.
|
||||
echo "$(basename $0): setting project version"
|
||||
|
@ -35,8 +36,8 @@ dotnet pack -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg || exit 1
|
|||
|
||||
echo "$(basename $0): publishing NuGet package"
|
||||
dotnet nuget remove source mfgames.com >& /dev/null
|
||||
dotnet nuget add source --name mfgames.com --username dmoonfire --password $GITEA_TOKEN https://src.mfgames.com/api/packages/mfgames-cil/nuget/index.json --store-password-in-clear-text || exit 1
|
||||
dotnet nuget push --skip-duplicate --source mfgames.com src/*/bin/Debug/*.nupkg || exit 1
|
||||
dotnet nuget add source --name mfgames.com https://src.mfgames.com/api/packages/mfgames-cil/nuget/index.json || exit 1
|
||||
dotnet nuget push --api-key $GITEA_TOKEN --skip-duplicate --source mfgames.com src/*/bin/Debug/*.nupkg || exit 1
|
||||
|
||||
# Tag and push, but only if we don't have a tag.
|
||||
if ! git tag | grep $SEMVER >& /dev/null
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -27,13 +28,13 @@ namespace MfGames.Nitride.Calendar;
|
|||
[WithProperties]
|
||||
public partial class CreateCalender : OperationBase
|
||||
{
|
||||
private readonly Timekeeper clock;
|
||||
private readonly TimeService clock;
|
||||
|
||||
private readonly IValidator<CreateCalender> validator;
|
||||
|
||||
public CreateCalender(
|
||||
IValidator<CreateCalender> validator,
|
||||
Timekeeper clock)
|
||||
TimeService clock)
|
||||
{
|
||||
this.validator = validator;
|
||||
this.clock = clock;
|
||||
|
@ -57,7 +58,9 @@ public partial class CreateCalender : OperationBase
|
|||
public UPath? Path { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -10,16 +10,16 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MfGames.Nitride.IO\MfGames.Nitride.IO.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride.Temporal\MfGames.Nitride.Temporal.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride.IO\MfGames.Nitride.IO.csproj"/>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Temporal\MfGames.Nitride.Temporal.csproj"/>
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Ical.Net" Version="4.2.0" />
|
||||
<PackageReference Include="NodaTime" Version="3.1.2" />
|
||||
<PackageReference Include="Zio" Version="0.15.0" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="Ical.Net" Version="4.2.0"/>
|
||||
<PackageReference Include="NodaTime" Version="3.1.2"/>
|
||||
<PackageReference Include="Zio" Version="0.15.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include the source generator -->
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -93,7 +94,9 @@ public partial class CreateAtomFeed : OperationBase
|
|||
public Func<Entity, Uri>? GetUrl { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -10,15 +10,15 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MfGames.Nitride.IO\MfGames.Nitride.IO.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride.Temporal\MfGames.Nitride.Temporal.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride.IO\MfGames.Nitride.IO.csproj"/>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Temporal\MfGames.Nitride.Temporal.csproj"/>
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="NodaTime" Version="3.1.2" />
|
||||
<PackageReference Include="Zio" Version="0.15.0" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="NodaTime" Version="3.1.2"/>
|
||||
<PackageReference Include="Zio" Version="0.15.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include the source generator -->
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.1" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.3.1"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -42,46 +42,47 @@ public class SingletonComponentSourceGenerator
|
|||
|
||||
// Create the namespace.
|
||||
SyntaxToken cls = cds.Identifier;
|
||||
|
||||
buffer.AppendLine(string.Join(
|
||||
"\n",
|
||||
$"using MfGames.Gallium;",
|
||||
$"",
|
||||
$"namespace {unit.Namespace}",
|
||||
$"{{",
|
||||
$" public partial class {cls}",
|
||||
$" {{",
|
||||
$" static {cls}()",
|
||||
$" {{",
|
||||
$" Instance = new {cls}();",
|
||||
$" }}",
|
||||
$"",
|
||||
$" private {cls}()",
|
||||
$" {{",
|
||||
$" }}",
|
||||
$"",
|
||||
$" public static {cls} Instance {{ get; }}",
|
||||
$" }}",
|
||||
$"",
|
||||
$" public static class {cls}Extensions",
|
||||
$" {{",
|
||||
$" public static bool Has{cls}(this Entity entity)",
|
||||
$" {{",
|
||||
$" return entity.Has<{cls}>();",
|
||||
$" }}",
|
||||
$"",
|
||||
$" public static Entity Remove{cls}(this Entity entity)",
|
||||
$" {{",
|
||||
$" return entity.Remove<{cls}>();",
|
||||
$" }}",
|
||||
$"",
|
||||
$" public static Entity Set{cls}(this Entity entity)",
|
||||
$" {{",
|
||||
$" return entity.Set({cls}.Instance);",
|
||||
$" }}",
|
||||
$" }}",
|
||||
$"}}",
|
||||
""));
|
||||
|
||||
buffer.AppendLine(
|
||||
string.Join(
|
||||
"\n",
|
||||
$"using MfGames.Gallium;",
|
||||
$"",
|
||||
$"namespace {unit.Namespace}",
|
||||
$"{{",
|
||||
$" public partial class {cls}",
|
||||
$" {{",
|
||||
$" static {cls}()",
|
||||
$" {{",
|
||||
$" Instance = new {cls}();",
|
||||
$" }}",
|
||||
$"",
|
||||
$" private {cls}()",
|
||||
$" {{",
|
||||
$" }}",
|
||||
$"",
|
||||
$" public static {cls} Instance {{ get; }}",
|
||||
$" }}",
|
||||
$"",
|
||||
$" public static class {cls}Extensions",
|
||||
$" {{",
|
||||
$" public static bool Has{cls}(this Entity entity)",
|
||||
$" {{",
|
||||
$" return entity.Has<{cls}>();",
|
||||
$" }}",
|
||||
$"",
|
||||
$" public static Entity Remove{cls}(this Entity entity)",
|
||||
$" {{",
|
||||
$" return entity.Remove<{cls}>();",
|
||||
$" }}",
|
||||
$"",
|
||||
$" public static Entity Set{cls}(this Entity entity)",
|
||||
$" {{",
|
||||
$" return entity.Set({cls}.Instance);",
|
||||
$" }}",
|
||||
$" }}",
|
||||
$"}}",
|
||||
""));
|
||||
|
||||
// Create the source text and write out the file.
|
||||
var sourceText = SourceText.From(buffer.ToString(), Encoding.UTF8);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -46,7 +47,9 @@ public partial class ApplyStyleTemplate : OperationBase
|
|||
public IHandlebars? Handlebars { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Make sure we have sane data.
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -27,7 +28,9 @@ public partial class IdentifyHandlebarsFromComponent : IOperation
|
|||
public Func<Entity, bool> HasHandlebarsTest { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
|
@ -12,7 +13,9 @@ namespace MfGames.Nitride.Handlebars;
|
|||
public class IdentifyHandlebarsFromContent : IOperation
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return input.SelectEntity<ITextContent>(this.ScanContent);
|
||||
}
|
||||
|
|
|
@ -10,15 +10,15 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="6.4.0" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Handlebars.Net" Version="2.1.2" />
|
||||
<PackageReference Include="NodaTime.Testing" Version="3.1.2" />
|
||||
<PackageReference Include="Open.Threading" Version="2.2.1" />
|
||||
<PackageReference Include="Autofac" Version="6.4.0"/>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="Handlebars.Net" Version="2.1.2"/>
|
||||
<PackageReference Include="NodaTime.Testing" Version="3.1.2"/>
|
||||
<PackageReference Include="Open.Threading" Version="2.2.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include the source generator -->
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -39,7 +40,9 @@ public partial class RenderContentTemplate : OperationBase
|
|||
public Func<Entity, object>? CreateModelCallback { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
|
@ -13,7 +14,9 @@ namespace MfGames.Nitride.Html;
|
|||
public class ConvertHtmlEntitiesToUnicode : OperationBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return input.SelectEntity<ITextContent>(this.ResolveHtmlEntities);
|
||||
}
|
||||
|
|
78
src/MfGames.Nitride.Html/IdentifyHtml.cs
Normal file
78
src/MfGames.Nitride.Html/IdentifyHtml.cs
Normal file
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Zio;
|
||||
|
||||
namespace MfGames.Nitride.Html;
|
||||
|
||||
/// <summary>
|
||||
/// An operation that identifies Markdown files by their common extensions
|
||||
/// and converts them to text input while also adding the IsMarkdown
|
||||
/// component to identify them.
|
||||
/// </summary>
|
||||
[WithProperties]
|
||||
public partial class IdentifyHtml : IOperation
|
||||
{
|
||||
private readonly IValidator<IdentifyHtml> validator;
|
||||
|
||||
public IdentifyHtml(IValidator<IdentifyHtml> validator)
|
||||
{
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
public Func<Entity, UPath, bool> IsHtmlTest { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
return input.SelectEntity<UPath, ITextContent>(this.MarkTextEntities)
|
||||
.SelectEntity<UPath, IBinaryContent>(this.MarkBinaryEntities);
|
||||
}
|
||||
|
||||
private Entity MarkBinaryEntities(
|
||||
Entity entity,
|
||||
UPath path,
|
||||
IBinaryContent binary)
|
||||
{
|
||||
// If we aren't a Markdown file, then there is nothing we can do about that.
|
||||
if (!this.IsHtmlTest(entity, path))
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
// Convert the file as a binary.
|
||||
if (binary is ITextContentConvertable textConvertable)
|
||||
{
|
||||
entity = entity.SetTextContent(textConvertable.ToTextContent())
|
||||
.SetIsHtml();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Cannot convert a binary content to a text without ITextContentConvertable.");
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private Entity MarkTextEntities(
|
||||
Entity entity,
|
||||
UPath path,
|
||||
ITextContent _)
|
||||
{
|
||||
return this.IsHtmlTest(entity, path)
|
||||
? entity.SetIsHtml()
|
||||
: entity;
|
||||
}
|
||||
}
|
29
src/MfGames.Nitride.Html/IdentifyHtmlFromPath.cs
Normal file
29
src/MfGames.Nitride.Html/IdentifyHtmlFromPath.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using Zio;
|
||||
|
||||
namespace MfGames.Nitride.Html;
|
||||
|
||||
public class IdentifyHtmlFromPath : IdentifyHtml
|
||||
{
|
||||
public IdentifyHtmlFromPath(IValidator<IdentifyHtml> validator)
|
||||
: base(validator)
|
||||
{
|
||||
this.IsHtmlTest = DefaultIsHtml;
|
||||
}
|
||||
|
||||
private static bool DefaultIsHtml(
|
||||
Entity entity,
|
||||
UPath path)
|
||||
{
|
||||
return (path.GetExtensionWithDot() ?? string.Empty).ToLowerInvariant()
|
||||
switch
|
||||
{
|
||||
".htm" => true,
|
||||
".html" => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -8,5 +8,6 @@ public class NitrideHtmlModule : Module
|
|||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterOperators(this);
|
||||
builder.RegisterValidators(this);
|
||||
}
|
||||
}
|
||||
|
|
12
src/MfGames.Nitride.Html/Validators/IdentifyHtmlValidator.cs
Normal file
12
src/MfGames.Nitride.Html/Validators/IdentifyHtmlValidator.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using FluentValidation;
|
||||
|
||||
namespace MfGames.Nitride.Html.Validators;
|
||||
|
||||
public class IdentifyHtmlValidator : AbstractValidator<IdentifyHtml>
|
||||
{
|
||||
public IdentifyHtmlValidator()
|
||||
{
|
||||
this.RuleFor(x => x.IsHtmlTest)
|
||||
.NotNull();
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using DotNet.Globbing;
|
||||
|
||||
|
@ -43,9 +44,10 @@ public partial class ReadFiles : FileSystemOperationBase
|
|||
/// minimatch pattern (as defined by DotNet.Blob).
|
||||
/// </summary>
|
||||
/// <returns>A populated collection of entities.</returns>
|
||||
public IEnumerable<Entity> Run()
|
||||
public IEnumerable<Entity> Run(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.Run(Array.Empty<Entity>());
|
||||
return this.Run(Array.Empty<Entity>(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -53,7 +55,9 @@ public partial class ReadFiles : FileSystemOperationBase
|
|||
/// minimatch pattern (as defined by DotNet.Blob).
|
||||
/// </summary>
|
||||
/// <returns>A populated collection of entities.</returns>
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -63,8 +64,11 @@ public partial class WriteFiles : FileSystemOperationBase, IOperation
|
|||
/// a path and a registered writer will be written.
|
||||
/// </summary>
|
||||
/// <param name="entities">The entities to parse.</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>The same list of entities without changes.</returns>
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> entities)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> entities,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -40,13 +41,16 @@ public partial class ClearDirectory : FileSystemOperationBase, IOperation
|
|||
/// </summary>
|
||||
public UPath? Path { get; set; }
|
||||
|
||||
public IEnumerable<Entity> Run()
|
||||
public IEnumerable<Entity> Run(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.Run(new List<Entity>());
|
||||
return this.Run(new List<Entity>(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
|
@ -16,5 +17,7 @@ public abstract class FileSystemOperationBase : IOperation
|
|||
public IFileSystem FileSystem { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IEnumerable<Entity> Run(IEnumerable<Entity> input);
|
||||
public abstract IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
|
@ -8,17 +8,17 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="6.4.0" />
|
||||
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
||||
<PackageReference Include="FluentValidation" Version="11.2.1" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="MAB.DotIgnore" Version="3.0.2" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Zio" Version="0.15.0" />
|
||||
<PackageReference Include="Autofac" Version="6.4.0"/>
|
||||
<PackageReference Include="DotNet.Glob" Version="3.1.3"/>
|
||||
<PackageReference Include="FluentValidation" Version="11.2.1"/>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="MAB.DotIgnore" Version="3.0.2"/>
|
||||
<PackageReference Include="Serilog" Version="2.11.0"/>
|
||||
<PackageReference Include="Zio" Version="0.15.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include the source generator -->
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -29,7 +30,9 @@ public partial class AddPathPrefix : OperationBase
|
|||
/// </summary>
|
||||
public UPath? PathPrefix { get; set; }
|
||||
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -32,7 +33,9 @@ public partial class ChangePathExtension : IOperation
|
|||
/// </summary>
|
||||
public string? Extension { get; set; }
|
||||
|
||||
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -29,7 +30,9 @@ public partial class LinkDirectChildren : CreateOrUpdateIndex
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.Scanner != null!)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -53,7 +54,9 @@ public partial class MoveToIndexPath : OperationBase
|
|||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -32,7 +33,9 @@ public partial class RemovePathPrefix : IOperation
|
|||
/// </summary>
|
||||
public UPath PathPrefix { get; set; }
|
||||
|
||||
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -36,8 +37,11 @@ public partial class ReplacePath : IOperation
|
|||
/// will be updated, the others will be passed on as-is.
|
||||
/// </summary>
|
||||
/// <param name="input">The list of input entities.</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>The output entities.</returns>
|
||||
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
|
|
@ -10,12 +10,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include the source generator -->
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -28,7 +29,9 @@ public abstract partial class ConvertMarkdownToBase : IOperation
|
|||
public Action<MarkdownPipelineBuilder>? ConfigureMarkdown { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Validate the inputs.
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -29,7 +30,9 @@ public partial class IdentifyMarkdown : IOperation
|
|||
public Func<Entity, UPath, bool> IsMarkdownTest { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
@ -68,16 +71,8 @@ public partial class IdentifyMarkdown : IOperation
|
|||
UPath path,
|
||||
ITextContent _)
|
||||
{
|
||||
// If we aren't a Markdown file, then there is nothing
|
||||
// we can do about that.
|
||||
if (!this.IsMarkdownTest(entity, path))
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
// We are already text, so just mark it as Markdown.
|
||||
entity = entity.Set(IsMarkdown.Instance);
|
||||
|
||||
return entity;
|
||||
return this.IsMarkdownTest(entity, path)
|
||||
? entity.SetIsMarkdown()
|
||||
: entity;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
|
@ -23,7 +24,9 @@ public class MakeSingleLinkListItems : IOperation
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return input
|
||||
.SelectManyEntity<IsMarkdown>(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using FluentValidation;
|
||||
|
||||
namespace MfGames.Nitride.Markdown;
|
||||
namespace MfGames.Nitride.Markdown.Validators;
|
||||
|
||||
public class ConvertMarkdownToBaseValidator
|
||||
: AbstractValidator<ConvertMarkdownToBase>
|
|
@ -1,6 +1,6 @@
|
|||
using FluentValidation;
|
||||
|
||||
namespace MfGames.Nitride.Markdown;
|
||||
namespace MfGames.Nitride.Markdown.Validators;
|
||||
|
||||
public class IdentifyMarkdownValidator : AbstractValidator<IdentifyMarkdown>
|
||||
{
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -8,24 +10,6 @@ using MfGames.Nitride.Generators;
|
|||
|
||||
namespace MfGames.Nitride.Temporal.Schedules;
|
||||
|
||||
public enum SchedulePeriod
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that all the matching schedule periods are instant (zero time).
|
||||
/// </summary>
|
||||
Instant,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the entities will be scheduled a day apart.
|
||||
/// </summary>
|
||||
Day,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the entities will be scheduled seven days apart.
|
||||
/// </summary>
|
||||
Week,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies schedules against the list of entities.
|
||||
/// </summary>
|
||||
|
@ -36,48 +20,75 @@ public partial class ApplySchedules : OperationBase
|
|||
|
||||
public ApplySchedules(
|
||||
IValidator<ApplySchedules> validator,
|
||||
Timekeeper timekeeper)
|
||||
TimeService timeService)
|
||||
{
|
||||
this.Timekeeper = timekeeper;
|
||||
this.TimeService = timeService;
|
||||
this.validator = validator;
|
||||
this.Schedules = new List<ISchedule>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ordered list of schedules to apply to the entities.
|
||||
/// Gets or sets the callback to get the schedules for the entity. This is
|
||||
/// used to allow for per-entity schedules or generic schedules across
|
||||
/// the entire system. If this returns null, then no schedule will be
|
||||
/// applied.
|
||||
/// </summary>
|
||||
public IList<ISchedule> Schedules { get; set; }
|
||||
public Func<Entity, IList<ISchedule>?>? GetSchedules { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timekeeper associated with this operation.
|
||||
/// Gets or sets the time service associated with this operation.
|
||||
/// </summary>
|
||||
public Timekeeper Timekeeper { get; set; }
|
||||
public TimeService TimeService { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
return input.Select(this.Apply);
|
||||
}
|
||||
|
||||
public ApplySchedules WithSchedules<TItem>(IEnumerable<TItem> items)
|
||||
where TItem : ISchedule
|
||||
/// <summary>
|
||||
/// Adds a single schedule into the apply schedules and returns the ApplySchedule
|
||||
/// class.
|
||||
/// </summary>
|
||||
public ApplySchedules WithGetSchedules(Func<Entity, ISchedule> schedule)
|
||||
{
|
||||
this.Schedules = items.OfType<ISchedule>().ToList();
|
||||
return this.WithGetSchedules(entity => new[] { schedule(entity) });
|
||||
}
|
||||
|
||||
public ApplySchedules WithGetSchedules<TType>(
|
||||
Func<Entity, IEnumerable<TType>?> value)
|
||||
where TType : ISchedule
|
||||
{
|
||||
this.GetSchedules = entity => value?
|
||||
.Invoke(entity)
|
||||
?.Cast<ISchedule>()
|
||||
.ToList();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private Entity Apply(Entity entity)
|
||||
{
|
||||
foreach (ISchedule schedule in this.Schedules)
|
||||
// Get the schedule for this entity.
|
||||
IList<ISchedule>? schedules = this.GetSchedules?.Invoke(entity);
|
||||
|
||||
if (schedules == null || schedules.Count == 0)
|
||||
{
|
||||
if (!schedule.CanApply(entity))
|
||||
return entity;
|
||||
}
|
||||
|
||||
// Otherwise, apply the schedules to this entity.
|
||||
foreach (ISchedule schedule in schedules)
|
||||
{
|
||||
if (schedule == null! || !schedule.CanApply(entity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
entity = schedule.Apply(entity, this.Timekeeper);
|
||||
entity = schedule.Apply(entity, this.TimeService);
|
||||
}
|
||||
|
||||
return entity;
|
||||
|
|
|
@ -12,14 +12,14 @@ public interface ISchedule
|
|||
/// Applies the schedule changes to the entity and returns the results.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to update.</param>
|
||||
/// <param name="timekeeper">The timekeeper for apply.</param>
|
||||
/// <param name="timeService">The service to use for time.</param>
|
||||
/// <returns>
|
||||
/// The modified entity, if the changes can be applied, otherwise the same
|
||||
/// entity.
|
||||
/// </returns>
|
||||
Entity Apply(
|
||||
Entity entity,
|
||||
Timekeeper timekeeper);
|
||||
TimeService timeService);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a schedule applies to a given entity.
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
namespace MfGames.Nitride.Temporal.Schedules;
|
||||
|
||||
/// <summary>
|
||||
/// An indexed-based schedule that uses the regular expression to get a
|
||||
/// numerical value and then uses that to determine the schedule.
|
||||
/// </summary>
|
||||
public partial class IndexedPathRegexSchedule<TSchedule> : PathRegexScheduleBase
|
||||
where TSchedule : IndexedSchedule
|
||||
{
|
||||
public IndexedPathRegexSchedule()
|
||||
{
|
||||
this.Indexes = new Dictionary<int, TSchedule>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the dictionary that indexes the various numerical indexes
|
||||
/// to provide the scheduling.
|
||||
/// </summary>
|
||||
public Dictionary<int, TSchedule> Indexes { get; set; }
|
||||
|
||||
public IndexedPathRegexSchedule<TSchedule> WithIndexes(
|
||||
Dictionary<int, TSchedule> value)
|
||||
{
|
||||
this.Indexes = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Entity Apply(
|
||||
Entity entity,
|
||||
int number,
|
||||
TimeService timeService)
|
||||
{
|
||||
// Figure out the entry in the index.
|
||||
var applicableKeys = this.Indexes.Keys
|
||||
.Where(a => a <= number)
|
||||
.ToList();
|
||||
|
||||
if (applicableKeys.Count == 0)
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
int startIndex = applicableKeys.Max(a => a);
|
||||
TSchedule schedule = this.Indexes[startIndex];
|
||||
|
||||
// Pass everything into the schedule to perform the applying.
|
||||
int startOffset = number - startIndex;
|
||||
|
||||
return schedule.Apply(entity, startOffset, timeService);
|
||||
}
|
||||
}
|
79
src/MfGames.Nitride.Temporal.Schedules/IndexedSchedule.cs
Normal file
79
src/MfGames.Nitride.Temporal.Schedules/IndexedSchedule.cs
Normal file
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
namespace MfGames.Nitride.Temporal.Schedules;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a simplified schedule object that contains a start and period
|
||||
/// for the schedule and is designed to work with the numbers provided by
|
||||
/// the <see cref="IndexedPathRegexSchedule" />.
|
||||
/// </summary>
|
||||
public partial class IndexedSchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the period between each matching entity. The period is
|
||||
/// amount of time between each item based on the number. So the first will
|
||||
/// be on the ScheduleDate, the second SchedulePeriod later, the third
|
||||
/// SchedulePeriod after the second, etc. More precisely, the date for
|
||||
/// any item TimeSpan * ((int)CaptureGroup + (int)CaptureOffset).
|
||||
/// </summary>
|
||||
public string? SchedulePeriod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the schedule period as a TimeSpan object. This is converted
|
||||
/// from SchedulePeriod using https://github.com/pengowray/TimeSpanParser.
|
||||
/// If the value is null or blank or "immediate", then this will be instant
|
||||
/// (TimeSpan.Zero).
|
||||
/// </summary>
|
||||
public virtual TimeSpan? SchedulePeriodTimeSpan
|
||||
{
|
||||
get => TimeSpanHelper.Parse(this.SchedulePeriod);
|
||||
set => this.SchedulePeriod = value?.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets when the first item is scheduled.
|
||||
/// </summary>
|
||||
public DateTime? ScheduleStart { get; set; }
|
||||
|
||||
public virtual Entity Apply(
|
||||
Entity entity,
|
||||
int number,
|
||||
TimeService timeService)
|
||||
{
|
||||
// If we have a "never", then we skip it.
|
||||
TimeSpan? period = this.SchedulePeriodTimeSpan;
|
||||
DateTime? start = this.ScheduleStart;
|
||||
|
||||
if (period == null || start == null)
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
// Figure out the time from the start.
|
||||
DateTime when = start.Value + period.Value * number;
|
||||
Instant instant = timeService.CreateInstant(when);
|
||||
|
||||
// If the time hasn't past, then we don't apply it.
|
||||
Instant now = timeService.Clock.GetCurrentInstant();
|
||||
|
||||
return instant > now
|
||||
? entity
|
||||
: this.Apply(entity, number, instant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the schedule to the entity based on the number and instant
|
||||
/// given.
|
||||
/// </summary>
|
||||
protected virtual Entity Apply(
|
||||
Entity entity,
|
||||
int number,
|
||||
Instant instant)
|
||||
{
|
||||
return entity.Set(instant);
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@
|
|||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="TimeSpanParserUtil" Version="1.2.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="12.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using Zio;
|
||||
|
||||
namespace MfGames.Nitride.Temporal.Schedules;
|
||||
|
||||
/// <summary>
|
||||
/// A schedule that uses the `UPath` of the entity to determine an offset from
|
||||
/// a starting point and calculates the information from there.
|
||||
/// </summary>
|
||||
[WithProperties]
|
||||
public partial class NumericalPathSchedule : ISchedule
|
||||
{
|
||||
public NumericalPathSchedule()
|
||||
{
|
||||
this.PathRegex = new Regex(@"^.*(\d+)");
|
||||
this.GetPath = (entity) => entity.Get<UPath>().ToString();
|
||||
this.CaptureGroup = 1;
|
||||
this.CaptureOffset = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the group number of the capture group with 1 being being the
|
||||
/// first group in PathRegex.
|
||||
/// </summary>
|
||||
public int CaptureGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the offset to make the resulting capture group a zero-based
|
||||
/// number.
|
||||
/// </summary>
|
||||
public int CaptureOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the method for retrieving the path for the entity.
|
||||
/// </summary>
|
||||
public Func<Entity, string> GetPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the regular expression to identify a matching path.
|
||||
/// </summary>
|
||||
public Regex? PathRegex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the period between each matching entity. More precisely,
|
||||
/// the schedule will be TimeSpan * (CaptureGroup + CaptureOffset).
|
||||
/// </summary>
|
||||
public SchedulePeriod SchedulePeriod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets when the first item is scheduled.
|
||||
/// </summary>
|
||||
public DateTime? ScheduleStart { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Entity Apply(
|
||||
Entity entity,
|
||||
Timekeeper timekeeper)
|
||||
{
|
||||
// Get the path and match it.
|
||||
string? path = this.GetPath(entity);
|
||||
Match? match = this.PathRegex?.Match(path)
|
||||
?? throw new NullReferenceException(
|
||||
"PathRegex was not configured for the "
|
||||
+ this.GetType().Name
|
||||
+ ".");
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
// Figure out the index/number of this entry.
|
||||
int number = int.Parse(match.Groups[this.CaptureGroup].Value)
|
||||
+ this.CaptureOffset;
|
||||
|
||||
// Figure out the time from the start.
|
||||
TimeSpan span = this.SchedulePeriod switch
|
||||
{
|
||||
SchedulePeriod.Instant => TimeSpan.Zero,
|
||||
SchedulePeriod.Day => TimeSpan.FromDays(1),
|
||||
SchedulePeriod.Week => TimeSpan.FromDays(7),
|
||||
_ => throw new InvalidOperationException(
|
||||
"Cannot parse schedule period from "
|
||||
+ this.SchedulePeriod
|
||||
+ "."),
|
||||
};
|
||||
DateTime start = this.ScheduleStart
|
||||
?? throw new NullReferenceException(
|
||||
"Cannot use a schedule without a start date.");
|
||||
DateTime when = start + span * number;
|
||||
Instant instant = timekeeper.CreateInstant(when);
|
||||
|
||||
// If the time hasn't past, then we don't apply it.
|
||||
Instant now = timekeeper.Clock.GetCurrentInstant();
|
||||
|
||||
return instant > now
|
||||
? entity
|
||||
: this.Apply(entity, number, instant);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanApply(Entity entity)
|
||||
{
|
||||
string? path = this.GetPath(entity);
|
||||
bool match = this.PathRegex?.IsMatch(path) ?? false;
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
protected virtual Entity Apply(
|
||||
Entity entity,
|
||||
int number,
|
||||
Instant instant)
|
||||
{
|
||||
return entity.Set(instant);
|
||||
}
|
||||
}
|
118
src/MfGames.Nitride.Temporal.Schedules/PathRegexScheduleBase.cs
Normal file
118
src/MfGames.Nitride.Temporal.Schedules/PathRegexScheduleBase.cs
Normal file
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Zio;
|
||||
|
||||
namespace MfGames.Nitride.Temporal.Schedules;
|
||||
|
||||
/// <summary>
|
||||
/// A common base for a schedule that is built off the path associated with
|
||||
/// the entity.
|
||||
/// </summary>
|
||||
[WithProperties]
|
||||
public abstract partial class PathRegexScheduleBase : ISchedule
|
||||
{
|
||||
protected PathRegexScheduleBase()
|
||||
{
|
||||
this.PathRegex = @"^.*?(\d+)[^\d]*$";
|
||||
this.GetPath = entity => entity.Get<UPath>().ToString();
|
||||
this.CaptureGroup = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the group number of the capture group with 1 being being the
|
||||
/// first group in PathRegex.
|
||||
/// </summary>
|
||||
public int CaptureGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the offset to make the resulting capture group a zero-based
|
||||
/// number.
|
||||
/// </summary>
|
||||
public int CaptureOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the method for retrieving the path for the entity.
|
||||
/// </summary>
|
||||
public Func<Entity, string> GetPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the regular expression to identify a matching path.
|
||||
/// </summary>
|
||||
public string? PathRegex { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Entity Apply(
|
||||
Entity entity,
|
||||
TimeService timeService)
|
||||
{
|
||||
// Get the path and match it.
|
||||
string path = this.GetPath(entity);
|
||||
Regex? regex = this.GetRegex();
|
||||
Match match = regex?.Match(path)
|
||||
?? throw new NullReferenceException(
|
||||
"PathRegex was not configured for the "
|
||||
+ this.GetType().Name
|
||||
+ ".");
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
if (match.Groups.Count < 2)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"There must be at least one capture group in '"
|
||||
+ this.PathRegex
|
||||
+ "'.");
|
||||
}
|
||||
|
||||
// Figure out the index/number of this entry.
|
||||
string numberValue = match.Groups[this.CaptureGroup].Value;
|
||||
|
||||
if (!int.TryParse(numberValue, out int number))
|
||||
{
|
||||
throw new FormatException(
|
||||
path + ": Cannot parse '" + numberValue + "' as integer.");
|
||||
}
|
||||
|
||||
number += this.CaptureOffset;
|
||||
|
||||
// Pass it onto the extending class.
|
||||
return this.Apply(entity, number, timeService);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanApply(Entity entity)
|
||||
{
|
||||
string path = this.GetPath(entity);
|
||||
Regex? regex = this.GetRegex();
|
||||
Match match = regex?.Match(path)
|
||||
?? throw new NullReferenceException(
|
||||
"PathRegex was not configured for the "
|
||||
+ this.GetType().Name
|
||||
+ ".");
|
||||
|
||||
return match.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the schedule according to the normalized number (after
|
||||
/// CaptureOffset).
|
||||
/// </summary>
|
||||
protected abstract Entity Apply(
|
||||
Entity entity,
|
||||
int number,
|
||||
TimeService timeService);
|
||||
|
||||
private Regex? GetRegex()
|
||||
{
|
||||
return this.PathRegex == null
|
||||
? null
|
||||
: new Regex(this.PathRegex);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
namespace MfGames.Nitride.Temporal.Schedules;
|
||||
|
||||
/// <summary>
|
||||
/// A schedule that uses the `UPath` of the entity to determine an offset from
|
||||
/// a starting point and calculates the information from there.
|
||||
/// </summary>
|
||||
[WithProperties]
|
||||
public partial class PeriodicPathRegexSchedule : PathRegexScheduleBase
|
||||
{
|
||||
public PeriodicPathRegexSchedule()
|
||||
{
|
||||
this.CaptureOffset = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the period between each matching entity. The period is
|
||||
/// amount of time between each item based on the number. So the first will
|
||||
/// be on the ScheduleDate, the second SchedulePeriod later, the third
|
||||
/// SchedulePeriod after the second, etc. More precisely, the date for
|
||||
/// any item TimeSpan * ((int)CaptureGroup + (int)CaptureOffset).
|
||||
/// </summary>
|
||||
public string? SchedulePeriod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the schedule period as a TimeSpan object. This is converted
|
||||
/// from SchedulePeriod using https://github.com/pengowray/TimeSpanParser.
|
||||
/// If the value is null or blank or "immediate", then this will be instant
|
||||
/// (TimeSpan.Zero).
|
||||
/// </summary>
|
||||
public virtual TimeSpan? SchedulePeriodTimeSpan
|
||||
{
|
||||
get => TimeSpanHelper.Parse(this.SchedulePeriod);
|
||||
set => this.SchedulePeriod = value?.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets when the first item is scheduled.
|
||||
/// </summary>
|
||||
public DateTime? ScheduleStart { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Entity Apply(
|
||||
Entity entity,
|
||||
int number,
|
||||
TimeService timeService)
|
||||
{
|
||||
// If we have a "never", then we skip it.
|
||||
TimeSpan? period = this.SchedulePeriodTimeSpan;
|
||||
DateTime? start = this.ScheduleStart;
|
||||
|
||||
if (period == null || start == null)
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
// Figure out the time from the start.
|
||||
DateTime when = start.Value + period.Value * number;
|
||||
Instant instant = timeService.CreateInstant(when);
|
||||
|
||||
// If the time hasn't past, then we don't apply it.
|
||||
Instant now = timeService.Clock.GetCurrentInstant();
|
||||
|
||||
return instant > now
|
||||
? entity
|
||||
: this.Apply(entity, number, instant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the schedule to the entity based on the number and instant
|
||||
/// given.
|
||||
/// </summary>
|
||||
protected virtual Entity Apply(
|
||||
Entity entity,
|
||||
int number,
|
||||
Instant instant)
|
||||
{
|
||||
return entity.Set(instant);
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ date (as provided by the `MfGames.Nitride.Temporal` package).
|
|||
A schedule is not needed when the date is in the path or inside a component. Using
|
||||
`MfGames.Nitride.Temporal.SetFromComponent` or `MfGames.Nitride.Temporal.SetFromPath`
|
||||
would be more than sufficient. This is for dynamically changing entities based on
|
||||
the date based on `Timekeeper`.
|
||||
the date based on `TimeService`.
|
||||
|
||||
## Configuring
|
||||
|
||||
|
@ -28,10 +28,10 @@ builder.UseTemporalSchedules();
|
|||
A schedule is a class that implements `ISchedule` which has the following methods:
|
||||
|
||||
- CanApply(Entity) ⟶ bool
|
||||
- This returns true if the schedule can apply to the given entity.
|
||||
- This returns true if the schedule can apply to the given entity.
|
||||
- Apply(Entity) ⟶ Entity
|
||||
- This makes the changes for the schedule on the entity and returns the results.
|
||||
- If the entity doesn't apply, then it should return the entity without changes.
|
||||
- This makes the changes for the schedule on the entity and returns the results.
|
||||
- If the entity doesn't apply, then it should return the entity without changes.
|
||||
|
||||
## ApplySchedules
|
||||
|
||||
|
@ -53,18 +53,32 @@ The following properties are available (along with their corresponding `With*` m
|
|||
- `IList<ISchedule> Schedules`
|
||||
- Contains the list of schedules to apply against entities.
|
||||
- Defaults to an empty list.
|
||||
- `Timekeeper Timekeeper`
|
||||
- `TimeService TimeService`
|
||||
- Used to determine when the site is being generated.
|
||||
- Defaults to the one provided by `MfGames.Nitride.Temporal`.
|
||||
|
||||
### Numerical Path-Based Schedules
|
||||
## Provided Schedules
|
||||
|
||||
A number of schedules are provided as part of this library.
|
||||
|
||||
### Schedule Periods
|
||||
|
||||
In all cases, the `SchedulePeriod` is a string that parses into a `TimeSpan` object
|
||||
that determines the amount of time between two successive entities of the same
|
||||
schedule. It is parsed using [TimeSpanParser](https://github.com/pengowray/TimeSpanParser)
|
||||
and allow for `TimeSpan` formatting (such as "1.00:00:00"), descriptions such as
|
||||
"4 days" or "1 month". In addition, it also handles `instant` (non-case sensitive)
|
||||
for a zero length time or effectively all at once or `never` (also not case sensitive)
|
||||
to never apply to it.
|
||||
|
||||
### Periodic Path Regex Schedules
|
||||
|
||||
A common pattern for using schedules is to dole out a numerical series of posts over
|
||||
a period of time. For example, a weekly posting of chapters or comic strips. In these
|
||||
cases, there is usually a number in the path such as `chapter-01` or `comic-0244`. The
|
||||
schedule starts at a certain point and then continues every day or week as needed.
|
||||
|
||||
The `NumericalPathSchedule` encapsulates this pattern. It uses the `UPath` component
|
||||
The `PeriodicPathRegexSchedule` encapsulates this pattern. It uses the `UPath` component
|
||||
of the entity and compares it against a regular expression that captures the numerical
|
||||
part of the path. If it matches, then the schedule sets the date equal to the starting
|
||||
point plus the "period" for every one past the first.
|
||||
|
@ -81,11 +95,12 @@ var entities = new List<Entity>
|
|||
};
|
||||
var schedules = new List<ISchedule>
|
||||
{
|
||||
new NumericalPathSchedule
|
||||
new PeriodicPathRegexSchedule
|
||||
{
|
||||
PathRegex = "chapter-(\d+),
|
||||
ScheduleStart = DateTime.Parse("2023-01-01"),
|
||||
SchedulePeriod = SchedulePeriod.Week,
|
||||
SchedulePeriod = "1 week",
|
||||
// Alternatively, SchedulePeriodTimeSpan = TimeSpan.FromDays(7),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -95,22 +110,22 @@ return entities.Run(op.WithSchedules(schedules));
|
|||
This will have chapter-01 have an `Instant` component set to 2023-01-01, the second
|
||||
chapter will be set to 2023-01-08, and the third at 2023-01-15.
|
||||
|
||||
`NumericalPathSchedule` has the following properties:
|
||||
`PeriodicPathRegexSchedule` has the following properties:
|
||||
|
||||
- `Func<Entity, string> GetPath`
|
||||
- An override to allow retrieving a different function.
|
||||
- Defaults to `entity.Get<UPath>().ToString()`
|
||||
- `Regex PathRegex`
|
||||
- The regular expression that retrieves the number.
|
||||
- Defaults to `^.*(\d+)` which grabs the last number found.
|
||||
- Defaults to `^.*?(\d+)[^\d]*$` which grabs the last number found.
|
||||
- `DateTime ScheduleStart`
|
||||
- The date that the schedule starts.
|
||||
- No default.
|
||||
- `SchedulePeriod SchedulePeriod`
|
||||
- Values:
|
||||
- Instant (meaning everything at once, default)
|
||||
- Week
|
||||
- Day
|
||||
- `string SchedulePeriod`
|
||||
- Parsed using https://github.com/pengowray/TimeSpanParser
|
||||
- Supports any `TimeSpan` value, also "2 weeks" and Humanizer formatted values
|
||||
- `TimeSpan SchedulePeriodTimeSpan`
|
||||
- Parsed from `SchedulePeriod`
|
||||
- `int CaptureGroup`
|
||||
- The numerical index of the capture group.
|
||||
- Defaults to `1` because the first match in Regex is 1.
|
||||
|
@ -126,7 +141,7 @@ There is also a virtual method for applying the schedule.
|
|||
|
||||
#### Overriding Logic
|
||||
|
||||
The default operation of a `NumericalPathSchedule` is to only set the `Instant`
|
||||
The default operation of a `PeriodicPathRegexSchedule` is to only set the `Instant`
|
||||
component of the entity. The class can be extended to have more site-specific
|
||||
entries including adding more properties to the schedule and applying them.
|
||||
|
||||
|
@ -167,14 +182,54 @@ Commonly, this schedule will be put into a JSON or YAML file.
|
|||
```yaml
|
||||
schedules:
|
||||
# Patron and Ko-Fi subscribers get it all at once
|
||||
- pathRegex: chapters/chapter-\d+
|
||||
scheduleDate: 2025-01-01
|
||||
- pathRegex: chapters/chapter-(\d+)
|
||||
scheduleStart: 2025-01-01
|
||||
schedulePeriod: instant # Because we overrode the default to be weekly.
|
||||
access: subscribers
|
||||
# The first fifteen chapters (01-15) were released at the rate of one
|
||||
# per week starting in 2024. This will replace all the schedules above
|
||||
# it.
|
||||
- path: chapters/chapter-(0\d|1[1-5])
|
||||
scheduleDate: 2030-01-01
|
||||
scheduleStart: 2030-01-01
|
||||
access: public
|
||||
```
|
||||
|
||||
### Indexed Path Regex Schedules
|
||||
|
||||
A variant on the periodic schedule is the `IndexedPathRegexSchedule`. This evolved
|
||||
from the periodic one in order to simplify the regular expressions at the expense
|
||||
of having a more complex structure. It uses the same regular expression as
|
||||
`PeriodicPathRegexSchedule` including `CaptureGroup` (still defaults to 1) and
|
||||
`CaptureOffset` (which defaults to 0) but instead of having the schedule date
|
||||
and period, those are relegated to a `Dictionary<int, IndexedSchedule>` (or a
|
||||
class that extends `IndexedSchedule`).
|
||||
|
||||
Using the below example:
|
||||
|
||||
```yaml
|
||||
schedules:
|
||||
pathRegex: chapters/chapter-(\d+)
|
||||
indexes:
|
||||
1:
|
||||
scheduleStart: 2025-01-01
|
||||
schedulePeriod: instant
|
||||
access: subscribers
|
||||
10:
|
||||
scheduleStart: 2030-01-01
|
||||
schedulePeriod: 1 week
|
||||
access: subscribers
|
||||
30:
|
||||
schedulePeriod: never
|
||||
```
|
||||
|
||||
For a given calculated number (such as `chapter-01` being `1`), the schedule picks the
|
||||
highest index that is not replaced by a higher number. So, chapters 1-9 would use the 2025
|
||||
date and be instantly available on that date while chapters 10 and higher would use the
|
||||
2030 date and be doled out a week at a time.
|
||||
|
||||
While this example doesn't use the top level as a sequence, it could be used to handle
|
||||
multiple overlapping schedules (like the first where two changes), but it is more
|
||||
verbose while describing it.
|
||||
|
||||
For the inner schedule, the number is normalized to be 0-based automatically for each one
|
||||
so the above example would have 1, 10, and 30 all pass in `0` into the `Apply` function.
|
||||
|
|
51
src/MfGames.Nitride.Temporal.Schedules/SimplePathSchedule.cs
Normal file
51
src/MfGames.Nitride.Temporal.Schedules/SimplePathSchedule.cs
Normal file
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
namespace MfGames.Nitride.Temporal.Schedules;
|
||||
|
||||
/// <summary>
|
||||
/// A schedule that goes against all entities it is applied to.
|
||||
/// </summary>
|
||||
[WithProperties]
|
||||
public partial class SimplePathSchedule : ISchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets when the first item is scheduled.
|
||||
/// </summary>
|
||||
public DateTime? ScheduleStart { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Entity Apply(
|
||||
Entity entity,
|
||||
TimeService timeService)
|
||||
{
|
||||
DateTime start = this.ScheduleStart
|
||||
?? throw new NullReferenceException(
|
||||
"Cannot use a schedule without a start date.");
|
||||
Instant instant = timeService.CreateInstant(start);
|
||||
|
||||
// If the time hasn't past, then we don't apply it.
|
||||
Instant now = timeService.Clock.GetCurrentInstant();
|
||||
|
||||
return instant > now
|
||||
? entity
|
||||
: this.Apply(entity, instant);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanApply(Entity entity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual Entity Apply(
|
||||
Entity entity,
|
||||
Instant instant)
|
||||
{
|
||||
return entity.Set(instant);
|
||||
}
|
||||
}
|
35
src/MfGames.Nitride.Temporal.Schedules/TimeSpanHelper.cs
Normal file
35
src/MfGames.Nitride.Temporal.Schedules/TimeSpanHelper.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
|
||||
using TimeSpanParserUtil;
|
||||
|
||||
namespace MfGames.Nitride.Temporal.Schedules;
|
||||
|
||||
public static class TimeSpanHelper
|
||||
{
|
||||
public static TimeSpan? Parse(string? input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.Equals(
|
||||
"immediate",
|
||||
StringComparison.InvariantCultureIgnoreCase)
|
||||
|| input.Equals(
|
||||
"instant",
|
||||
StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
if (input.Equals(
|
||||
"never",
|
||||
StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return TimeSpanParser.Parse(input);
|
||||
}
|
||||
}
|
|
@ -6,10 +6,10 @@ public class ApplySchedulesValidator : AbstractValidator<ApplySchedules>
|
|||
{
|
||||
public ApplySchedulesValidator()
|
||||
{
|
||||
this.RuleFor(x => x.Schedules)
|
||||
.NotEmpty();
|
||||
this.RuleFor(x => x.GetSchedules)
|
||||
.NotNull();
|
||||
|
||||
this.RuleFor(x => x.Timekeeper)
|
||||
this.RuleFor(x => x.TimeService)
|
||||
.NotNull();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,14 +20,14 @@ public class DatePipelineCommandOption : IPipelineCommandOption
|
|||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
private readonly Timekeeper timekeeper;
|
||||
private readonly TimeService timeService;
|
||||
|
||||
public DatePipelineCommandOption(
|
||||
ILogger logger,
|
||||
Timekeeper timekeeper)
|
||||
TimeService timeService)
|
||||
{
|
||||
this.logger = logger.ForContext<Instant>();
|
||||
this.timekeeper = timekeeper;
|
||||
this.timeService = timeService;
|
||||
|
||||
this.Option = new Option<DateTime>("--date")
|
||||
{
|
||||
|
@ -53,15 +53,15 @@ public class DatePipelineCommandOption : IPipelineCommandOption
|
|||
// date for the entire run.
|
||||
var local = LocalDateTime.FromDateTime(value.Value);
|
||||
ZonedDateTime zoned =
|
||||
local.InZoneStrictly(this.timekeeper.DateTimeZone);
|
||||
local.InZoneStrictly(this.timeService.DateTimeZone);
|
||||
var instant = zoned.ToInstant();
|
||||
|
||||
this.timekeeper.Clock = new FakeClock(instant);
|
||||
this.timeService.Clock = new FakeClock(instant);
|
||||
}
|
||||
|
||||
// Report the date we are processing.
|
||||
Instant now = this.timekeeper.Clock.GetCurrentInstant();
|
||||
ZonedDateTime dateTime = now.InZone(this.timekeeper.DateTimeZone);
|
||||
Instant now = this.timeService.Clock.GetCurrentInstant();
|
||||
ZonedDateTime dateTime = now.InZone(this.timeService.DateTimeZone);
|
||||
string formatted = dateTime.ToString("G", CultureInfo.InvariantCulture);
|
||||
|
||||
this.logger.Information("Setting date/time to {When:l}", formatted);
|
||||
|
|
|
@ -18,13 +18,13 @@ namespace MfGames.Nitride.Temporal.Cli;
|
|||
/// </summary>
|
||||
public class ExpiresPipelineCommandOption : IPipelineCommandOption
|
||||
{
|
||||
private readonly Timekeeper clock;
|
||||
private readonly TimeService clock;
|
||||
|
||||
private readonly ILogger logger;
|
||||
|
||||
public ExpiresPipelineCommandOption(
|
||||
ILogger logger,
|
||||
Timekeeper clock,
|
||||
TimeService clock,
|
||||
string? defaultValue = null)
|
||||
{
|
||||
this.logger = logger.ForContext<Instant>();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -24,10 +25,10 @@ public partial class CreateDateIndexes : OperationBase, IResolvingOperation
|
|||
|
||||
public CreateDateIndexes(
|
||||
IValidator<CreateDateIndexes> validator,
|
||||
Timekeeper timekeeper)
|
||||
TimeService timeService)
|
||||
{
|
||||
this.validator = validator;
|
||||
this.Timekeeper = timekeeper;
|
||||
this.TimeService = timeService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -50,10 +51,12 @@ public partial class CreateDateIndexes : OperationBase, IResolvingOperation
|
|||
/// </summary>
|
||||
public int LessThanEqualCollapse { get; set; }
|
||||
|
||||
public Timekeeper Timekeeper { get; }
|
||||
public TimeService TimeService { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Validate our input.
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
@ -133,7 +136,7 @@ public partial class CreateDateIndexes : OperationBase, IResolvingOperation
|
|||
|
||||
Entity? first = pair.Value[0];
|
||||
|
||||
string? nextKey = this.Timekeeper
|
||||
string? nextKey = this.TimeService
|
||||
.ToDateTime(first.Get<Instant>())
|
||||
.ToString(this.Formats[i + 1]);
|
||||
|
||||
|
@ -163,7 +166,7 @@ public partial class CreateDateIndexes : OperationBase, IResolvingOperation
|
|||
List<Dictionary<string, List<Entity>>> grouped,
|
||||
Entity entity)
|
||||
{
|
||||
var dateTime = this.Timekeeper.ToDateTime(instant);
|
||||
var dateTime = this.TimeService.ToDateTime(instant);
|
||||
|
||||
for (int i = 0; i < this.Formats.Count; i++)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -19,20 +20,22 @@ public partial class FilterOutExpiredInstant : OperationBase
|
|||
|
||||
public FilterOutExpiredInstant(
|
||||
IValidator<FilterOutExpiredInstant> validator,
|
||||
Timekeeper clock)
|
||||
TimeService clock)
|
||||
{
|
||||
this.validator = validator;
|
||||
this.Timekeeper = clock;
|
||||
this.TimeService = clock;
|
||||
}
|
||||
|
||||
public Timekeeper Timekeeper { get; set; }
|
||||
public TimeService TimeService { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
if (!this.Timekeeper.Expiration.HasValue)
|
||||
if (!this.TimeService.Expiration.HasValue)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
@ -45,7 +48,7 @@ public partial class FilterOutExpiredInstant : OperationBase
|
|||
Instant instant,
|
||||
CanExpire _)
|
||||
{
|
||||
Instant expiration = this.Timekeeper.Expiration!.Value;
|
||||
Instant expiration = this.TimeService.Expiration!.Value;
|
||||
bool isExpired = instant.CompareTo(expiration) < 0;
|
||||
|
||||
return isExpired ? null : entity;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
@ -14,17 +15,19 @@ namespace MfGames.Nitride.Temporal;
|
|||
[WithProperties]
|
||||
public partial class FilterOutFutureInstant : OperationBase
|
||||
{
|
||||
public FilterOutFutureInstant(Timekeeper timekeeper)
|
||||
public FilterOutFutureInstant(TimeService timeService)
|
||||
{
|
||||
this.Timekeeper = timekeeper;
|
||||
this.TimeService = timeService;
|
||||
}
|
||||
|
||||
public Timekeeper Timekeeper { get; set; }
|
||||
public TimeService TimeService { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Instant now = this.Timekeeper.Clock.GetCurrentInstant();
|
||||
Instant now = this.TimeService.Clock.GetCurrentInstant();
|
||||
|
||||
return input
|
||||
.SelectEntity<Instant>(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -16,11 +17,11 @@ namespace MfGames.Nitride.Temporal;
|
|||
/// </summary>
|
||||
public class SetInstantFromComponent<TComponent> : OperationBase
|
||||
{
|
||||
private readonly Timekeeper clock;
|
||||
private readonly TimeService clock;
|
||||
|
||||
private readonly IValidator<SetInstantFromComponent<TComponent>> validator;
|
||||
|
||||
public SetInstantFromComponent(Timekeeper clock)
|
||||
public SetInstantFromComponent(TimeService clock)
|
||||
{
|
||||
// TODO: Figure out why Autofac won't let us register IValidator of generic classes.
|
||||
this.validator = new SetInstantFromComponentValidator<TComponent>();
|
||||
|
@ -39,7 +40,9 @@ public class SetInstantFromComponent<TComponent> : OperationBase
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
@ -70,6 +73,7 @@ public class SetInstantFromComponent<TComponent> : OperationBase
|
|||
{
|
||||
case null:
|
||||
return entity;
|
||||
|
||||
case Instant direct:
|
||||
instant = direct;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -22,13 +23,13 @@ namespace MfGames.Nitride.Temporal;
|
|||
[WithProperties]
|
||||
public partial class SetInstantFromPath : OperationBase
|
||||
{
|
||||
private readonly Timekeeper clock;
|
||||
private readonly TimeService clock;
|
||||
|
||||
private readonly IValidator<SetInstantFromPath> validator;
|
||||
|
||||
public SetInstantFromPath(
|
||||
IValidator<SetInstantFromPath> validator,
|
||||
Timekeeper clock)
|
||||
TimeService clock)
|
||||
{
|
||||
this.validator = validator;
|
||||
this.clock = clock;
|
||||
|
@ -43,7 +44,9 @@ public partial class SetInstantFromPath : OperationBase
|
|||
public Regex? PathRegex { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
|
@ -64,15 +67,9 @@ public partial class SetInstantFromPath : OperationBase
|
|||
|
||||
// Create an Instant from this.
|
||||
Instant instant = this.clock.CreateInstant(
|
||||
Convert.ToInt32(
|
||||
match.Groups["year"]
|
||||
.Value),
|
||||
Convert.ToInt32(
|
||||
match.Groups["month"]
|
||||
.Value),
|
||||
Convert.ToInt32(
|
||||
match.Groups["day"]
|
||||
.Value));
|
||||
Convert.ToInt32(match.Groups["year"].Value),
|
||||
Convert.ToInt32(match.Groups["month"].Value),
|
||||
Convert.ToInt32(match.Groups["day"].Value));
|
||||
|
||||
return entity.Set(instant);
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@ public static class NitrideTemporalBuilderExtensions
|
|||
context =>
|
||||
{
|
||||
ILogger logger = context.Resolve<ILogger>();
|
||||
Timekeeper
|
||||
clock = context.Resolve<Timekeeper>();
|
||||
TimeService
|
||||
clock = context.Resolve<TimeService>();
|
||||
|
||||
return new ExpiresPipelineCommandOption(
|
||||
logger,
|
||||
|
@ -65,12 +65,12 @@ public static class NitrideTemporalBuilderExtensions
|
|||
scope) =>
|
||||
{
|
||||
ILogger logger = scope.Resolve<ILogger>();
|
||||
Timekeeper timekeeper = scope.Resolve<Timekeeper>();
|
||||
TimeService timeService = scope.Resolve<TimeService>();
|
||||
|
||||
timekeeper.DateTimeZone = config.DateTimeZone;
|
||||
timeService.DateTimeZone = config.DateTimeZone;
|
||||
logger.Verbose(
|
||||
"Setting time zone to {Zone:l}",
|
||||
timekeeper.DateTimeZone);
|
||||
timeService.DateTimeZone);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ public class NitrideTemporalModule : Module
|
|||
builder.RegisterOperators(this);
|
||||
builder.RegisterValidators(this);
|
||||
|
||||
builder.RegisterType<Timekeeper>()
|
||||
builder.RegisterType<TimeService>()
|
||||
.AsSelf()
|
||||
.SingleInstance();
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ namespace MfGames.Nitride.Temporal;
|
|||
/// the desire time zone along with various methods for processing parsed
|
||||
/// DateTime objects into NodaTime.Instant.
|
||||
/// </summary>
|
||||
public class Timekeeper
|
||||
public class TimeService
|
||||
{
|
||||
public Timekeeper()
|
||||
public TimeService()
|
||||
{
|
||||
// We use FakeClock because we don't want time to advance in the
|
||||
// middle of running, just in case we are just a few seconds before
|
||||
|
@ -28,6 +28,16 @@ public class Timekeeper
|
|||
/// </summary>
|
||||
public IClock Clock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current date time.
|
||||
/// </summary>
|
||||
public DateTime CurrentDateTime => this.ToDateTime(this.CurrentInstant);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current instant.
|
||||
/// </summary>
|
||||
public Instant CurrentInstant => this.Clock.GetCurrentInstant();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date time zone used for processing.
|
||||
/// </summary>
|
|
@ -6,7 +6,7 @@ public class CreateDateIndexesValidator : AbstractValidator<CreateDateIndexes>
|
|||
{
|
||||
public CreateDateIndexesValidator()
|
||||
{
|
||||
this.RuleFor(a => a.Timekeeper)
|
||||
this.RuleFor(a => a.TimeService)
|
||||
.NotNull();
|
||||
|
||||
this.RuleFor(a => a.CreateIndex)
|
||||
|
|
|
@ -7,7 +7,7 @@ public class FilterOutExpiredInstantValidator
|
|||
{
|
||||
public FilterOutExpiredInstantValidator()
|
||||
{
|
||||
this.RuleFor(x => x.Timekeeper)
|
||||
this.RuleFor(x => x.TimeService)
|
||||
.NotNull();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ public class FilterOutFutureInstantValidator
|
|||
{
|
||||
public FilterOutFutureInstantValidator()
|
||||
{
|
||||
this.RuleFor(x => x.Timekeeper)
|
||||
this.RuleFor(x => x.TimeService)
|
||||
.NotNull();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="12.0.0" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="YamlDotNet" Version="12.0.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include the source generator -->
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
|
@ -44,7 +45,9 @@ public class ParseYamlHeader<TModel> : OperationBase
|
|||
private bool RemoveHeader { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set up the YAML parsing.
|
||||
DeserializerBuilder builder =
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Invocation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using MfGames.Nitride.Pipelines;
|
||||
|
@ -48,6 +49,9 @@ public class BuildCommand : Command, ICommandHandler
|
|||
/// <inheritdoc />
|
||||
public async Task<int> InvokeAsync(InvocationContext context)
|
||||
{
|
||||
// Get the cancellation token so we can be interrupted.
|
||||
CancellationToken cancellationToken = context.GetCancellationToken();
|
||||
|
||||
// Process any injected options.
|
||||
this.logger.Debug(
|
||||
"Processing {Count:N0} pipeline options",
|
||||
|
@ -63,7 +67,7 @@ public class BuildCommand : Command, ICommandHandler
|
|||
// all the pipelines once and then quits when it finishes.
|
||||
this.logger.Information("Running pipelines");
|
||||
|
||||
int pipelinesResults = await this.pipelines.RunAsync();
|
||||
int pipelinesResults = await this.pipelines.RunAsync(cancellationToken);
|
||||
|
||||
return pipelinesResults;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -67,7 +68,9 @@ public partial class CreateOrUpdateIndex : OperationBase
|
|||
} = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Make sure we have sane data.
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -92,7 +93,9 @@ public partial class EntityScanner : OperationBase
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Make sure we have sane data.
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
|
@ -46,7 +47,9 @@ public partial class LinkEntitySequence : OperationBase, IResolvingOperation
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Make sure everything is good.
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
|
@ -10,6 +11,9 @@ public interface IOperation
|
|||
/// Runs the input entities through the operation and returns the results.
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<Entity> Run(IEnumerable<Entity> input);
|
||||
IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
|
@ -15,22 +15,22 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="6.4.0" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="FluentValidation" Version="11.2.1" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="MfGames.ToolBuilder" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Autofac.DependencyInjection" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageReference Include="Zio" Version="0.15.0" />
|
||||
<PackageReference Include="Autofac" Version="6.4.0"/>
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0"/>
|
||||
<PackageReference Include="FluentValidation" Version="11.2.1"/>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
|
||||
<PackageReference Include="MfGames.ToolBuilder" Version="1.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0"/>
|
||||
<PackageReference Include="Serilog" Version="2.11.0"/>
|
||||
<PackageReference Include="Serilog.Extensions.Autofac.DependencyInjection" Version="5.0.0"/>
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1"/>
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0"/>
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0"/>
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1"/>
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1"/>
|
||||
<PackageReference Include="Zio" Version="0.15.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include the source generator -->
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
|
@ -15,11 +16,13 @@ public static class NitrideOperationExtensions
|
|||
/// </summary>
|
||||
/// <param name="input">The entities to perform the operation against.</param>
|
||||
/// <param name="operation">The operation to run.</param>
|
||||
/// <param name="cancellationToken">The cancellation token of the request.</param>
|
||||
/// <returns>The results of the operation.</returns>
|
||||
public static IEnumerable<Entity> Run(
|
||||
this IEnumerable<Entity> input,
|
||||
IOperation operation)
|
||||
IOperation operation,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return operation.Run(input);
|
||||
return operation.Run(input, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
|
@ -10,5 +11,7 @@ namespace MfGames.Nitride;
|
|||
public abstract class OperationBase : IOperation
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public abstract IEnumerable<Entity> Run(IEnumerable<Entity> input);
|
||||
public abstract IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
|
@ -22,6 +23,9 @@ public interface IPipeline
|
|||
/// entities.
|
||||
/// </summary>
|
||||
/// <param name="entities">The entities to process.</param>
|
||||
/// <param name="cancellationToken">The token for cancelling processing.</param>
|
||||
/// <returns>The resulting entities after the process runs.</returns>
|
||||
IAsyncEnumerable<Entity> RunAsync(IEnumerable<Entity> entities);
|
||||
IAsyncEnumerable<Entity> RunAsync(
|
||||
IEnumerable<Entity> entities,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
|
@ -41,12 +42,12 @@ public abstract class PipelineBase : IPipeline
|
|||
|
||||
/// <inheritdoc />
|
||||
public abstract IAsyncEnumerable<Entity> RunAsync(
|
||||
IEnumerable<Entity> entities);
|
||||
IEnumerable<Entity> entities,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return this.GetType()
|
||||
.Name;
|
||||
return this.GetType().Name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Humanizer;
|
||||
|
@ -48,8 +49,9 @@ public class PipelineManager
|
|||
/// Runs all of the pipelines in the appropriate order while running
|
||||
/// across multiple threads.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The token for cancelling processing.</param>
|
||||
/// <returns>A task with zero for success or otherwise an error code.</returns>
|
||||
public Task<int> RunAsync()
|
||||
public Task<int> RunAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Make sure everything is setup.
|
||||
DateTime started = DateTime.UtcNow;
|
||||
|
@ -66,14 +68,18 @@ public class PipelineManager
|
|||
"pipeline".ToQuantity(this.pipelines.Count));
|
||||
|
||||
Task[] tasks = this.entries
|
||||
.Select(x => Task.Run(async () => await x.RunAsync()))
|
||||
.Select(
|
||||
x => Task.Run(
|
||||
async () => await x.RunAsync(cancellationToken),
|
||||
cancellationToken))
|
||||
.ToArray();
|
||||
|
||||
var report = TimeSpan.FromSeconds(15);
|
||||
|
||||
while (!Task.WaitAll(tasks, report))
|
||||
{
|
||||
var waiting = this.entries.Where(x => !x.IsFinished)
|
||||
var waiting = this.entries
|
||||
.Where(x => !x.IsFinished)
|
||||
.ToList();
|
||||
|
||||
this.logger.Debug(
|
||||
|
@ -81,14 +87,15 @@ public class PipelineManager
|
|||
"pipeline".ToQuantity(waiting.Count));
|
||||
|
||||
IOrderedEnumerable<IGrouping<PipelineRunnerState, PipelineRunner>>
|
||||
states =
|
||||
waiting.GroupBy(x => x.State, x => x)
|
||||
.OrderBy(x => (int)x.Key);
|
||||
states = waiting
|
||||
.GroupBy(x => x.State, x => x)
|
||||
.OrderBy(x => (int)x.Key);
|
||||
|
||||
foreach (IGrouping<PipelineRunnerState, PipelineRunner>? state in
|
||||
states)
|
||||
{
|
||||
var statePipelines = state.OrderBy(x => x.Pipeline.ToString())
|
||||
var statePipelines = state
|
||||
.OrderBy(x => x.Pipeline.ToString())
|
||||
.ToList();
|
||||
|
||||
this.logger.Verbose(
|
||||
|
@ -106,8 +113,8 @@ public class PipelineManager
|
|||
}
|
||||
|
||||
// Figure out our return code.
|
||||
bool hasErrors =
|
||||
this.entries.Any(x => x.State == PipelineRunnerState.Errored);
|
||||
bool hasErrors = this.entries
|
||||
.Any(x => x.State == PipelineRunnerState.Errored);
|
||||
|
||||
this.logger.Information(
|
||||
"Completed in {Elapsed}",
|
||||
|
@ -144,20 +151,22 @@ public class PipelineManager
|
|||
|
||||
// Wrap all the pipelines into entries. We do this before the next
|
||||
// step so we can have the entries depend on the entries.
|
||||
this.entries = this.pipelines.Select(x => this.createEntry(x))
|
||||
this.entries = this.pipelines
|
||||
.Select(x => this.createEntry(x))
|
||||
.ToList();
|
||||
|
||||
// Go through and connect the pipelines together.
|
||||
foreach (PipelineRunner? entry in this.entries)
|
||||
{
|
||||
var dependencies = entry.Pipeline.GetDependencies()
|
||||
var dependencies = entry.Pipeline
|
||||
.GetDependencies()
|
||||
.ToList();
|
||||
|
||||
foreach (IPipeline? dependency in dependencies)
|
||||
{
|
||||
// Get the entry for the dependency.
|
||||
PipelineRunner dependencyPipeline =
|
||||
this.entries.Single(x => x.Pipeline == dependency);
|
||||
PipelineRunner dependencyPipeline = this.entries
|
||||
.Single(x => x.Pipeline == dependency);
|
||||
|
||||
// Set up the bi-directional connection.
|
||||
entry.Incoming.Add(dependencyPipeline);
|
||||
|
|
|
@ -149,7 +149,7 @@ public class PipelineRunner
|
|||
/// Executes the pipeline, including waiting for any or all
|
||||
/// dependencies.
|
||||
/// </summary>
|
||||
public async Task RunAsync()
|
||||
public async Task RunAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -194,7 +194,7 @@ public class PipelineRunner
|
|||
|
||||
// Run the pipeline. This may not be resolved until we gather
|
||||
// the output below.
|
||||
await this.RunPipeline(input);
|
||||
await this.RunPipeline(input, cancellationToken);
|
||||
|
||||
// At this point, we are completely done with our inputs, so signal
|
||||
// to them in case they have to clean up any of their structures.
|
||||
|
@ -274,12 +274,14 @@ public class PipelineRunner
|
|||
return input;
|
||||
}
|
||||
|
||||
private async Task RunPipeline(List<Entity> input)
|
||||
private async Task RunPipeline(
|
||||
List<Entity> input,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Get the sequence of data, but this doesn't drain the enumeration.
|
||||
List<Entity> output = await this.Pipeline
|
||||
.RunAsync(input)
|
||||
.ToListAsync();
|
||||
.RunAsync(input, cancellationToken)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// Gather all the output.
|
||||
this.logger.Verbose("{Pipeline:l}: Gathering output", this.Pipeline);
|
||||
|
|
|
@ -6,22 +6,22 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.IO\MfGames.Nitride.IO.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.IO\MfGames.Nitride.IO.csproj"/>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CompareNETObjects" Version="4.78.0" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="CompareNETObjects" Version="4.78.0"/>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1"/>
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114"/>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Zio" Version="0.15.0" />
|
||||
<PackageReference Include="Zio" Version="0.15.0"/>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1"/>
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@ -21,9 +21,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.Json\MfGames.Nitride.Json.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj"/>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.Json\MfGames.Nitride.Json.csproj"/>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride\MfGames.Nitride.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -6,18 +6,18 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.Markdown\MfGames.Nitride.Markdown.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.Markdown\MfGames.Nitride.Markdown.csproj"/>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Markdig" Version="0.30.4" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="MfGames.Markdown.Gemtext" Version="1.2.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114" />
|
||||
<PackageReference Include="Slugify.Core" Version="3.0.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="Markdig" Version="0.30.4"/>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="MfGames.Markdown.Gemtext" Version="1.2.2"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1"/>
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114"/>
|
||||
<PackageReference Include="Slugify.Core" Version="3.0.0"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
|
|
@ -6,21 +6,21 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.IO\MfGames.Nitride.IO.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.Slugs\MfGames.Nitride.Slugs.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.IO\MfGames.Nitride.IO.csproj"/>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.Slugs\MfGames.Nitride.Slugs.csproj"/>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1"/>
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Zio" Version="0.15.0" />
|
||||
<PackageReference Include="Zio" Version="0.15.0"/>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Tests;
|
||||
|
||||
using NodaTime;
|
||||
using NodaTime.Testing;
|
||||
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
using Zio;
|
||||
|
||||
namespace MfGames.Nitride.Temporal.Schedules.Tests;
|
||||
|
||||
public class IndexedPathRegexScheduleTest : TemporalSchedulesTestBase
|
||||
{
|
||||
public IndexedPathRegexScheduleTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeserializedSetupWorks()
|
||||
{
|
||||
using TemporalSchedulesTestContext context = this.CreateContext();
|
||||
|
||||
// Create a numerical series of entities.
|
||||
var input = new List<Entity>
|
||||
{
|
||||
new Entity().SetAll((UPath)"/chapter-01.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-02.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-03.md", new TestModel()),
|
||||
};
|
||||
|
||||
TestModel model = new DeserializerBuilder()
|
||||
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||
.Build()
|
||||
.Deserialize<TestModel>(
|
||||
string.Join(
|
||||
"\n",
|
||||
"---",
|
||||
"access: custom",
|
||||
"schedules:",
|
||||
" pathRegex: chapter-(\\d+)",
|
||||
" indexes:",
|
||||
" 1:",
|
||||
" scheduleStart: 2020-01-01",
|
||||
" schedulePeriod: instant",
|
||||
" access: t-1",
|
||||
" 2:",
|
||||
" scheduleStart: 2023-01-02",
|
||||
" schedulePeriod: 1 week",
|
||||
" access: t-2",
|
||||
""));
|
||||
var schedules = model.Schedules!;
|
||||
|
||||
// Create the operation and run it, but treat it as being set after the
|
||||
// second but before the third item.
|
||||
TimeService time = context.Resolve<TimeService>();
|
||||
ApplySchedules op = context.Resolve<ApplySchedules>()
|
||||
.WithGetSchedules(_ => new ISchedule[] { schedules });
|
||||
var now = Instant.FromUtc(2023, 1, 3, 0, 0);
|
||||
|
||||
time.Clock = new FakeClock(now);
|
||||
|
||||
var actual = op
|
||||
.Run(input)
|
||||
.Select(
|
||||
a => string.Format(
|
||||
"{0} -- {1} -- {2}",
|
||||
a.Get<UPath>().ToString(),
|
||||
a.Has<Instant>()
|
||||
? time
|
||||
.ToDateTime(a.Get<Instant>())
|
||||
.ToString("yyyy-MM-dd")
|
||||
: "none",
|
||||
a.Get<TestModel>().Access))
|
||||
.ToList();
|
||||
|
||||
var expected = new List<string>
|
||||
{
|
||||
"/chapter-01.md -- 2020-01-01 -- t-1",
|
||||
"/chapter-02.md -- 2023-01-02 -- t-2",
|
||||
"/chapter-03.md -- none -- private",
|
||||
};
|
||||
|
||||
TestHelper.CompareObjects(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManualSetupWorks()
|
||||
{
|
||||
using TemporalSchedulesTestContext context = this.CreateContext();
|
||||
|
||||
// Create a numerical series of entities.
|
||||
var input = new List<Entity>
|
||||
{
|
||||
new Entity().SetAll((UPath)"/chapter-01.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-02.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-03.md", new TestModel()),
|
||||
};
|
||||
|
||||
var schedules = new TestRegexSchedule
|
||||
{
|
||||
Indexes = new Dictionary<int, TestSchedule>
|
||||
{
|
||||
[1] = new TestSchedule
|
||||
{
|
||||
ScheduleStart = DateTime.Parse("2023-01-01"),
|
||||
Access = "public",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Create the operation and run it, but treat it as being set after the
|
||||
// second but before the third item.
|
||||
TimeService time = context.Resolve<TimeService>();
|
||||
ApplySchedules op = context.Resolve<ApplySchedules>()
|
||||
.WithGetSchedules(_ => new ISchedule[] { schedules });
|
||||
var now = Instant.FromUtc(2023, 1, 9, 0, 0);
|
||||
|
||||
time.Clock = new FakeClock(now);
|
||||
|
||||
var actual = op
|
||||
.Run(input)
|
||||
.Select(
|
||||
a => string.Format(
|
||||
"{0} -- {1} -- {2}",
|
||||
a.Get<UPath>().ToString(),
|
||||
a.Has<Instant>()
|
||||
? time
|
||||
.ToDateTime(a.Get<Instant>())
|
||||
.ToString("yyyy-MM-dd")
|
||||
: "none",
|
||||
a.Get<TestModel>().Access))
|
||||
.ToList();
|
||||
|
||||
var expected = new List<string>
|
||||
{
|
||||
"/chapter-01.md -- 2023-01-01 -- public",
|
||||
"/chapter-02.md -- 2023-01-08 -- public",
|
||||
"/chapter-03.md -- none -- private",
|
||||
};
|
||||
|
||||
TestHelper.CompareObjects(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SequencedScheduleWorks()
|
||||
{
|
||||
using TemporalSchedulesTestContext context = this.CreateContext();
|
||||
|
||||
// Create a numerical series of entities.
|
||||
var input = new List<Entity>
|
||||
{
|
||||
new Entity().SetAll((UPath)"/chapter-01.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-02.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-03.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-04.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-05.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-06.md", new TestModel()),
|
||||
};
|
||||
|
||||
var schedules = new TestRegexSchedule()
|
||||
{
|
||||
Indexes = new Dictionary<int, TestSchedule>
|
||||
{
|
||||
[1] = new()
|
||||
{
|
||||
ScheduleStart = DateTime.Parse("2020-01-01"),
|
||||
Access = "subscriber",
|
||||
SchedulePeriodTimeSpan = TimeSpan.FromDays(7),
|
||||
},
|
||||
[3] = new()
|
||||
{
|
||||
ScheduleStart = DateTime.Parse("2023-01-07"),
|
||||
Access = "public",
|
||||
SchedulePeriodTimeSpan = TimeSpan.Zero,
|
||||
},
|
||||
[5] = new()
|
||||
{
|
||||
SchedulePeriod = "never",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Create the operation and run it, but treat it as being set after the
|
||||
// second but before the third item.
|
||||
TimeService time = context.Resolve<TimeService>();
|
||||
ApplySchedules op = context.Resolve<ApplySchedules>()
|
||||
.WithGetSchedules(_ => new ISchedule[] { schedules });
|
||||
var now = Instant.FromUtc(2023, 1, 9, 0, 0);
|
||||
|
||||
time.Clock = new FakeClock(now);
|
||||
|
||||
var actual = op
|
||||
.Run(input)
|
||||
.Select(
|
||||
a => string.Format(
|
||||
"{0} -- {1} -- {2}",
|
||||
a.Get<UPath>().ToString(),
|
||||
a.Has<Instant>()
|
||||
? time
|
||||
.ToDateTime(a.Get<Instant>())
|
||||
.ToString("yyyy-MM-dd")
|
||||
: "none",
|
||||
a.Get<TestModel>().Access))
|
||||
.ToList();
|
||||
|
||||
var expected = new List<string>
|
||||
{
|
||||
"/chapter-01.md -- 2020-01-01 -- subscriber",
|
||||
"/chapter-02.md -- 2020-01-08 -- subscriber",
|
||||
"/chapter-03.md -- 2023-01-07 -- public",
|
||||
"/chapter-04.md -- 2023-01-07 -- public",
|
||||
"/chapter-05.md -- none -- private",
|
||||
"/chapter-06.md -- none -- private",
|
||||
};
|
||||
|
||||
TestHelper.CompareObjects(expected, actual);
|
||||
}
|
||||
|
||||
public class TestModel
|
||||
{
|
||||
public string? Access { get; set; } = "private";
|
||||
|
||||
public TestRegexSchedule? Schedules { get; set; }
|
||||
}
|
||||
|
||||
public class TestRegexSchedule : IndexedPathRegexSchedule<TestSchedule>
|
||||
{
|
||||
}
|
||||
|
||||
public class TestSchedule : IndexedSchedule
|
||||
{
|
||||
public TestSchedule()
|
||||
{
|
||||
this.SchedulePeriod = "1 week";
|
||||
}
|
||||
|
||||
public string? Access { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Entity Apply(
|
||||
Entity entity,
|
||||
int number,
|
||||
Instant instant)
|
||||
{
|
||||
TestModel model = entity.Get<TestModel>();
|
||||
model.Access = this.Access;
|
||||
return entity.SetAll(instant, model);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,9 +18,9 @@ using Zio;
|
|||
|
||||
namespace MfGames.Nitride.Temporal.Schedules.Tests;
|
||||
|
||||
public class NumericalPathScheduleTests : TemporalSchedulesTestBase
|
||||
public class PeriodicPathRegexScheduleTest : TemporalSchedulesTestBase
|
||||
{
|
||||
public NumericalPathScheduleTests(ITestOutputHelper output)
|
||||
public PeriodicPathRegexScheduleTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
@ -47,16 +47,18 @@ public class NumericalPathScheduleTests : TemporalSchedulesTestBase
|
|||
"---",
|
||||
"access: custom",
|
||||
"schedules:",
|
||||
" - scheduleStart: 2023-01-01",
|
||||
" - pathRegex: chapter-(\\d+)",
|
||||
" scheduleStart: 2023-01-01",
|
||||
" schedulePeriod: 1 week",
|
||||
" access: public",
|
||||
""));
|
||||
List<TestSchedule>? schedules = model.Schedules!;
|
||||
List<TestRegexSchedule>? schedules = model.Schedules!;
|
||||
|
||||
// Create the operation and run it, but treat it as being set after the
|
||||
// second but before the third item.
|
||||
Timekeeper time = context.Resolve<Timekeeper>();
|
||||
TimeService time = context.Resolve<TimeService>();
|
||||
ApplySchedules op = context.Resolve<ApplySchedules>()
|
||||
.WithSchedules(schedules);
|
||||
.WithGetSchedules(_ => schedules);
|
||||
var now = Instant.FromUtc(2023, 1, 9, 0, 0);
|
||||
|
||||
time.Clock = new FakeClock(now);
|
||||
|
@ -98,7 +100,7 @@ public class NumericalPathScheduleTests : TemporalSchedulesTestBase
|
|||
new Entity().SetAll((UPath)"/chapter-03.md", new TestModel()),
|
||||
};
|
||||
|
||||
var schedules = new List<TestSchedule>
|
||||
var schedules = new List<TestRegexSchedule>
|
||||
{
|
||||
new()
|
||||
{
|
||||
|
@ -109,9 +111,9 @@ public class NumericalPathScheduleTests : TemporalSchedulesTestBase
|
|||
|
||||
// Create the operation and run it, but treat it as being set after the
|
||||
// second but before the third item.
|
||||
Timekeeper time = context.Resolve<Timekeeper>();
|
||||
TimeService time = context.Resolve<TimeService>();
|
||||
ApplySchedules op = context.Resolve<ApplySchedules>()
|
||||
.WithSchedules(schedules);
|
||||
.WithGetSchedules(_ => schedules);
|
||||
var now = Instant.FromUtc(2023, 1, 9, 0, 0);
|
||||
|
||||
time.Clock = new FakeClock(now);
|
||||
|
@ -140,6 +142,62 @@ public class NumericalPathScheduleTests : TemporalSchedulesTestBase
|
|||
TestHelper.CompareObjects(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScheduleOffsetWorks()
|
||||
{
|
||||
using TemporalSchedulesTestContext context = this.CreateContext();
|
||||
|
||||
// Create a numerical series of entities.
|
||||
var input = new List<Entity>
|
||||
{
|
||||
new Entity().SetAll((UPath)"/chapter-11.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-12.md", new TestModel()),
|
||||
new Entity().SetAll((UPath)"/chapter-13.md", new TestModel()),
|
||||
};
|
||||
|
||||
var schedules = new List<TestRegexSchedule>
|
||||
{
|
||||
new()
|
||||
{
|
||||
ScheduleStart = DateTime.Parse("2023-01-01"),
|
||||
CaptureOffset = -11,
|
||||
Access = "public",
|
||||
},
|
||||
};
|
||||
|
||||
// Create the operation and run it, but treat it as being set after the
|
||||
// second but before the third item.
|
||||
TimeService time = context.Resolve<TimeService>();
|
||||
ApplySchedules op = context.Resolve<ApplySchedules>()
|
||||
.WithGetSchedules(_ => schedules);
|
||||
var now = Instant.FromUtc(2023, 1, 9, 0, 0);
|
||||
|
||||
time.Clock = new FakeClock(now);
|
||||
|
||||
var actual = op
|
||||
.Run(input)
|
||||
.Select(
|
||||
a => string.Format(
|
||||
"{0} -- {1} -- {2}",
|
||||
a.Get<UPath>().ToString(),
|
||||
a.Has<Instant>()
|
||||
? time
|
||||
.ToDateTime(a.Get<Instant>())
|
||||
.ToString("yyyy-MM-dd")
|
||||
: "none",
|
||||
a.Get<TestModel>().Access))
|
||||
.ToList();
|
||||
|
||||
var expected = new List<string>
|
||||
{
|
||||
"/chapter-11.md -- 2023-01-01 -- public",
|
||||
"/chapter-12.md -- 2023-01-08 -- public",
|
||||
"/chapter-13.md -- none -- private",
|
||||
};
|
||||
|
||||
TestHelper.CompareObjects(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SequencedScheduleWorks()
|
||||
{
|
||||
|
@ -153,7 +211,7 @@ public class NumericalPathScheduleTests : TemporalSchedulesTestBase
|
|||
new Entity().SetAll((UPath)"/chapter-03.md", new TestModel()),
|
||||
};
|
||||
|
||||
var schedules = new List<TestSchedule>
|
||||
var schedules = new List<TestRegexSchedule>
|
||||
{
|
||||
new()
|
||||
{
|
||||
|
@ -169,9 +227,9 @@ public class NumericalPathScheduleTests : TemporalSchedulesTestBase
|
|||
|
||||
// Create the operation and run it, but treat it as being set after the
|
||||
// second but before the third item.
|
||||
Timekeeper time = context.Resolve<Timekeeper>();
|
||||
TimeService time = context.Resolve<TimeService>();
|
||||
ApplySchedules op = context.Resolve<ApplySchedules>()
|
||||
.WithSchedules(schedules);
|
||||
.WithGetSchedules(_ => schedules);
|
||||
var now = Instant.FromUtc(2023, 1, 9, 0, 0);
|
||||
|
||||
time.Clock = new FakeClock(now);
|
||||
|
@ -204,14 +262,14 @@ public class NumericalPathScheduleTests : TemporalSchedulesTestBase
|
|||
{
|
||||
public string? Access { get; set; } = "private";
|
||||
|
||||
public List<TestSchedule>? Schedules { get; set; }
|
||||
public List<TestRegexSchedule>? Schedules { get; set; }
|
||||
}
|
||||
|
||||
public class TestSchedule : NumericalPathSchedule
|
||||
public class TestRegexSchedule : PeriodicPathRegexSchedule
|
||||
{
|
||||
public TestSchedule()
|
||||
public TestRegexSchedule()
|
||||
{
|
||||
this.SchedulePeriod = SchedulePeriod.Week;
|
||||
this.SchedulePeriod = "1 week";
|
||||
}
|
||||
|
||||
public string? Access { get; set; }
|
|
@ -21,7 +21,7 @@ public class CreateDateIndexesTests : TemporalTestBase
|
|||
public void MonthOnlyIndexes()
|
||||
{
|
||||
using TemporalTestContext context = this.CreateContext();
|
||||
Timekeeper timekeeper = context.Resolve<Timekeeper>();
|
||||
TimeService timeService = context.Resolve<TimeService>();
|
||||
|
||||
CreateDateIndexes op = context.Resolve<CreateDateIndexes>()
|
||||
.WithFormats("yyyy-MM")
|
||||
|
@ -30,11 +30,11 @@ public class CreateDateIndexesTests : TemporalTestBase
|
|||
List<Entity> input = new()
|
||||
{
|
||||
new Entity().Add("page1")
|
||||
.Add(timekeeper.CreateInstant(2021, 1, 2)),
|
||||
.Add(timeService.CreateInstant(2021, 1, 2)),
|
||||
new Entity().Add("page2")
|
||||
.Add(timekeeper.CreateInstant(2021, 2, 2)),
|
||||
.Add(timeService.CreateInstant(2021, 2, 2)),
|
||||
new Entity().Add("page3")
|
||||
.Add(timekeeper.CreateInstant(2022, 1, 2)),
|
||||
.Add(timeService.CreateInstant(2022, 1, 2)),
|
||||
};
|
||||
|
||||
List<Tuple<string, List<string>?, List<string>?>> actual =
|
||||
|
@ -66,7 +66,7 @@ public class CreateDateIndexesTests : TemporalTestBase
|
|||
public void YearMonthDayIndexes()
|
||||
{
|
||||
using TemporalTestContext context = this.CreateContext();
|
||||
Timekeeper timekeeper = context.Resolve<Timekeeper>();
|
||||
TimeService timeService = context.Resolve<TimeService>();
|
||||
|
||||
CreateDateIndexes op = context.Resolve<CreateDateIndexes>()
|
||||
.WithFormats("yyyy/MM/dd", "yyyy/MM", "yyyy")
|
||||
|
@ -75,11 +75,11 @@ public class CreateDateIndexesTests : TemporalTestBase
|
|||
List<Entity> input = new()
|
||||
{
|
||||
new Entity().Add("page1")
|
||||
.Add(timekeeper.CreateInstant(2021, 1, 2)),
|
||||
.Add(timeService.CreateInstant(2021, 1, 2)),
|
||||
new Entity().Add("page2")
|
||||
.Add(timekeeper.CreateInstant(2021, 2, 2)),
|
||||
.Add(timeService.CreateInstant(2021, 2, 2)),
|
||||
new Entity().Add("page3")
|
||||
.Add(timekeeper.CreateInstant(2022, 1, 2)),
|
||||
.Add(timeService.CreateInstant(2022, 1, 2)),
|
||||
};
|
||||
|
||||
List<Tuple<string, List<string>?, List<string>?>> actual =
|
||||
|
@ -131,7 +131,7 @@ public class CreateDateIndexesTests : TemporalTestBase
|
|||
public void YearMonthDayIndexesThreshold1()
|
||||
{
|
||||
using TemporalTestContext context = this.CreateContext();
|
||||
Timekeeper timekeeper = context.Resolve<Timekeeper>();
|
||||
TimeService timeService = context.Resolve<TimeService>();
|
||||
|
||||
CreateDateIndexes op = context.Resolve<CreateDateIndexes>()
|
||||
.WithFormats("yyyy/MM/dd", "yyyy/MM", "yyyy")
|
||||
|
@ -141,11 +141,11 @@ public class CreateDateIndexesTests : TemporalTestBase
|
|||
List<Entity> input = new()
|
||||
{
|
||||
new Entity().Add("page1")
|
||||
.Add(timekeeper.CreateInstant(2021, 1, 2)),
|
||||
.Add(timeService.CreateInstant(2021, 1, 2)),
|
||||
new Entity().Add("page2")
|
||||
.Add(timekeeper.CreateInstant(2021, 2, 2)),
|
||||
.Add(timeService.CreateInstant(2021, 2, 2)),
|
||||
new Entity().Add("page3")
|
||||
.Add(timekeeper.CreateInstant(2022, 1, 2)),
|
||||
.Add(timeService.CreateInstant(2022, 1, 2)),
|
||||
};
|
||||
|
||||
List<Tuple<string, List<string>?, List<string>?>> actual =
|
||||
|
@ -197,7 +197,7 @@ public class CreateDateIndexesTests : TemporalTestBase
|
|||
public void YearMonthIndexes()
|
||||
{
|
||||
using TemporalTestContext context = this.CreateContext();
|
||||
Timekeeper timekeeper = context.Resolve<Timekeeper>();
|
||||
TimeService timeService = context.Resolve<TimeService>();
|
||||
|
||||
CreateDateIndexes op = context.Resolve<CreateDateIndexes>()
|
||||
.WithFormats("yyyy-MM", "yyyy")
|
||||
|
@ -206,11 +206,11 @@ public class CreateDateIndexesTests : TemporalTestBase
|
|||
List<Entity> input = new()
|
||||
{
|
||||
new Entity().Add("page1")
|
||||
.Add(timekeeper.CreateInstant(2021, 1, 2)),
|
||||
.Add(timeService.CreateInstant(2021, 1, 2)),
|
||||
new Entity().Add("page2")
|
||||
.Add(timekeeper.CreateInstant(2021, 2, 2)),
|
||||
.Add(timeService.CreateInstant(2021, 2, 2)),
|
||||
new Entity().Add("page3")
|
||||
.Add(timekeeper.CreateInstant(2022, 1, 2)),
|
||||
.Add(timeService.CreateInstant(2022, 1, 2)),
|
||||
};
|
||||
|
||||
List<Tuple<string, List<string>?, List<string>?>> actual =
|
||||
|
@ -250,7 +250,7 @@ public class CreateDateIndexesTests : TemporalTestBase
|
|||
public void YearOnlyIndexes()
|
||||
{
|
||||
using TemporalTestContext context = this.CreateContext();
|
||||
Timekeeper timekeeper = context.Resolve<Timekeeper>();
|
||||
TimeService timeService = context.Resolve<TimeService>();
|
||||
|
||||
CreateDateIndexes op = context.Resolve<CreateDateIndexes>()
|
||||
.WithFormats("yyyy")
|
||||
|
@ -259,11 +259,11 @@ public class CreateDateIndexesTests : TemporalTestBase
|
|||
List<Entity> input = new()
|
||||
{
|
||||
new Entity().Add("page1")
|
||||
.Add(timekeeper.CreateInstant(2021, 1, 2)),
|
||||
.Add(timeService.CreateInstant(2021, 1, 2)),
|
||||
new Entity().Add("page2")
|
||||
.Add(timekeeper.CreateInstant(2021, 2, 2)),
|
||||
.Add(timeService.CreateInstant(2021, 2, 2)),
|
||||
new Entity().Add("page3")
|
||||
.Add(timekeeper.CreateInstant(2022, 1, 2)),
|
||||
.Add(timeService.CreateInstant(2022, 1, 2)),
|
||||
};
|
||||
|
||||
List<Tuple<string, List<string>?, List<string>?>> actual =
|
||||
|
|
|
@ -22,10 +22,10 @@ public class FilterOutFutureInstantTests : TemporalTestBase
|
|||
{
|
||||
// Create the context and set the timestamp to a constant value.
|
||||
using TemporalTestContext context = this.CreateContext();
|
||||
Timekeeper timekeeper = context.Resolve<Timekeeper>();
|
||||
TimeService timeService = context.Resolve<TimeService>();
|
||||
var now = Instant.FromUtc(2000, 6, 1, 0, 0);
|
||||
|
||||
timekeeper.Clock = new FakeClock(now);
|
||||
timeService.Clock = new FakeClock(now);
|
||||
|
||||
// Create the entities.
|
||||
List<Entity> input = new()
|
||||
|
@ -52,10 +52,10 @@ public class FilterOutFutureInstantTests : TemporalTestBase
|
|||
{
|
||||
// Create the context and set the timestamp to a constant value.
|
||||
using TemporalTestContext context = this.CreateContext();
|
||||
Timekeeper timekeeper = context.Resolve<Timekeeper>();
|
||||
TimeService timeService = context.Resolve<TimeService>();
|
||||
var now = Instant.FromUtc(2000, 6, 1, 0, 0);
|
||||
|
||||
timekeeper.Clock = new FakeClock(now);
|
||||
timeService.Clock = new FakeClock(now);
|
||||
|
||||
// Create the entities.
|
||||
List<Entity> input = new()
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.Temporal\MfGames.Nitride.Temporal.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.Temporal\MfGames.Nitride.Temporal.csproj"/>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CompareNETObjects" Version="4.78.0" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="CompareNETObjects" Version="4.78.0"/>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1"/>
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114"/>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CompareNETObjects" Version="4.78.0" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="MfGames.TestSetup" Version="1.0.6" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="Serilog.Sinks.XUnit" Version="3.0.3" />
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="CompareNETObjects" Version="4.78.0"/>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="MfGames.TestSetup" Version="1.0.6"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1"/>
|
||||
<PackageReference Include="Serilog.Sinks.XUnit" Version="3.0.3"/>
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -26,7 +26,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride\MfGames.Nitride.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1"/>
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@ -21,9 +21,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.Yaml\MfGames.Nitride.Yaml.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj"/>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.Yaml\MfGames.Nitride.Yaml.csproj"/>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride\MfGames.Nitride.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Reference in a new issue