Compare commits
27 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 | ||
D. Moonfire | fc1ab22a0e | ||
D. Moonfire | d5b975c179 | ||
D. Moonfire | c73805ae93 | ||
D. Moonfire | edda9a2773 | ||
D. Moonfire | e02c56e77e | ||
D. Moonfire | a5694d0cee | ||
D. Moonfire | 0a36d70fb0 | ||
D. Moonfire | c5c9b8bf9c | ||
D. Moonfire | a8c6d0e582 | ||
D. Moonfire | 084aa7e812 | ||
D. Moonfire | 7d388b09c2 | ||
D. Moonfire | 9e93eb6ce6 | ||
D. Moonfire | 6a397f5284 | ||
D. Moonfire | 0011dc715b |
3
.envrc
3
.envrc
|
@ -1,2 +1,3 @@
|
|||
export PATH=$PWD/scripts:$PATH
|
||||
use flake || use nix
|
||||
export PATH=$PWD/scripts:$PATH
|
||||
export DOTNET_ROOT=$(dirname $(dirname $(which dotnet)))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -47,6 +47,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Nitride.Temporal.Te
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Nitride.Markdown.Tests", "tests\MfGames.Nitride.Markdown.Tests\MfGames.Nitride.Markdown.Tests.csproj", "{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Nitride.Json", "src\MfGames.Nitride.Json\MfGames.Nitride.Json.csproj", "{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Nitride.Json.Tests", "tests\MfGames.Nitride.Json.Tests\MfGames.Nitride.Json.Tests.csproj", "{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Nitride.Temporal.Schedules", "src\MfGames.Nitride.Temporal.Schedules\MfGames.Nitride.Temporal.Schedules.csproj", "{6AC8F985-B11B-44F4-A000-DFEAFEF59754}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Nitride.Temporal.Schedules.Tests", "tests\MfGames.Nitride.Temporal.Schedules.Tests\MfGames.Nitride.Temporal.Schedules.Tests.csproj", "{CA009524-E64A-4380-874E-C9D19D868572}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -288,6 +296,54 @@ Global
|
|||
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Release|x64.Build.0 = Release|Any CPU
|
||||
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Release|x86.Build.0 = Release|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Release|x64.Build.0 = Release|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754}.Release|x86.Build.0 = Release|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CA009524-E64A-4380-874E-C9D19D868572}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{D480943C-764D-4A8A-B546-642ED10586BB} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||
|
@ -309,5 +365,9 @@ Global
|
|||
{C49E07D0-CD32-4332-90FA-07494195CAC4} = {251D9C68-34EB-439D-B167-688BCC47DA17}
|
||||
{0B74A4DE-4F92-44EE-8273-E5A15EAB4266} = {251D9C68-34EB-439D-B167-688BCC47DA17}
|
||||
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65} = {251D9C68-34EB-439D-B167-688BCC47DA17}
|
||||
{9A0D2BEE-859A-4E74-8CA7-5E0FB7C2B113} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||
{7CCC3A82-D5FE-4D54-9751-5E7985DE1F26} = {251D9C68-34EB-439D-B167-688BCC47DA17}
|
||||
{6AC8F985-B11B-44F4-A000-DFEAFEF59754} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||
{CA009524-E64A-4380-874E-C9D19D868572} = {251D9C68-34EB-439D-B167-688BCC47DA17}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
<add key="mfgames.com" value="https://src.mfgames.com/api/packages/mfgames-cil/nuget/index.json" protocolVersion="3" />
|
||||
<add key="mfgames.com" value="https://src.mfgames.com/api/packages/mfgames-cil/nuget/index.json" />
|
||||
</packageSources>
|
||||
<packageSourceMapping>
|
||||
<packageSource key="nuget.org">
|
||||
|
|
|
@ -7,20 +7,20 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.IO\MfGames.Nitride.IO.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
<ProjectReference Include="..\..\tests\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj" />
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride.IO\MfGames.Nitride.IO.csproj"/>
|
||||
<ProjectReference Include="..\..\src\MfGames.Nitride\MfGames.Nitride.csproj"/>
|
||||
<ProjectReference Include="..\..\tests\MfGames.Nitride.Tests\MfGames.Nitride.Tests.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliWrap" Version="3.5.0" />
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="CliWrap" Version="3.5.0"/>
|
||||
<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="MfGames.IO" Version="1.2.7" />
|
||||
<PackageReference Include="MfGames.IO" Version="1.2.7"/>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using MfGames.Nitride;
|
||||
using MfGames.Nitride.IO.Contents;
|
||||
using MfGames.Nitride.IO.Directories;
|
||||
|
@ -45,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
|
||||
|
@ -75,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
|
||||
|
@ -97,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
|
||||
|
|
|
@ -4,7 +4,6 @@ using System.Threading.Tasks;
|
|||
using Autofac;
|
||||
|
||||
using MfGames.IO.Extensions;
|
||||
|
||||
using MfGames.Nitride;
|
||||
using MfGames.Nitride.IO;
|
||||
|
||||
|
@ -27,7 +26,8 @@ public static class CopyFilesProgram
|
|||
// system. At the moment, we set the "root" directory which will
|
||||
// contains all the paths, both input and output.
|
||||
DirectoryInfo rootDir =
|
||||
typeof(CopyFilesProgram).GetDirectory()!.FindGitRoot()!.GetDirectory("examples/CopyFiles");
|
||||
typeof(CopyFilesProgram).GetDirectory()!.FindGitRoot()!
|
||||
.GetDirectory("examples/CopyFiles");
|
||||
|
||||
builder.WithRootDirectory(rootDir);
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using CliWrap;
|
||||
|
||||
using MfGames.IO.Extensions;
|
||||
|
||||
using MfGames.Nitride.Tests;
|
||||
|
||||
using Xunit;
|
||||
|
@ -26,8 +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");
|
||||
|
@ -41,14 +44,31 @@ public class CopyFilesTest : NitrideTestBase
|
|||
}
|
||||
|
||||
// Execute the generator. This will throw if there is an exception.
|
||||
await Cli.Wrap("dotnet")
|
||||
.WithArguments(
|
||||
x => x.Add("run")
|
||||
.Add("--project")
|
||||
.Add(projectFile.FullName)
|
||||
.Add("--")
|
||||
.Add("build"))
|
||||
.ExecuteAsync();
|
||||
StringBuilder output = new();
|
||||
|
||||
try
|
||||
{
|
||||
await Cli
|
||||
.Wrap("dotnet")
|
||||
.WithWorkingDirectory(projectFile.DirectoryName!)
|
||||
.WithArguments(
|
||||
argumentsBuilder => argumentsBuilder
|
||||
.Add("run")
|
||||
.Add("--no-build")
|
||||
.Add("--")
|
||||
.Add("build"))
|
||||
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(output))
|
||||
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(output))
|
||||
.ExecuteAsync();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
this.Logger.Fatal(
|
||||
exception,
|
||||
"There was an exception running the command:\n\n{Log:l}",
|
||||
output);
|
||||
throw;
|
||||
}
|
||||
|
||||
// Make sure we have our output.
|
||||
FileInfo aFile = outputDir.GetFile("a.txt");
|
||||
|
|
12
flake.lock
12
flake.lock
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -17,11 +17,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1662019588,
|
||||
"narHash": "sha256-oPEjHKGGVbBXqwwL+UjsveJzghWiWV0n9ogo1X6l4cw=",
|
||||
"lastModified": 1673631141,
|
||||
"narHash": "sha256-AprpYQ5JvLS4wQG/ghm2UriZ9QZXvAwh1HlgA/6ZEVQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2da64a81275b68fdad38af669afeda43d401e94b",
|
||||
"rev": "befc83905c965adfd33e5cae49acb0351f6e0404",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
pkgs.dotnet-sdk
|
||||
pkgs.lefthook
|
||||
pkgs.convco
|
||||
pkgs.nodePackages.prettier
|
||||
pkgs.nixfmt
|
||||
pkgs.jq
|
||||
];
|
||||
|
|
11
lefthook.yml
11
lefthook.yml
|
@ -1,14 +1,3 @@
|
|||
pre-commit:
|
||||
parallel: true
|
||||
commands:
|
||||
dotnet-format:
|
||||
glob: "*.cs"
|
||||
run: dotnet format
|
||||
prettier:
|
||||
run: prettier . --write --loglevel warn
|
||||
nixfmt:
|
||||
run: nixfmt flake.nix
|
||||
|
||||
commit-msg:
|
||||
commands:
|
||||
commit-check:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
cd $(dirname $0)/..
|
||||
|
||||
# Make sure we have the needed executables installed.
|
||||
for e in dotnet lefthook prettier nixfmt
|
||||
for e in dotnet lefthook nixfmt
|
||||
do
|
||||
if ! which $e >& /dev/null
|
||||
then
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
mode: ContinuousDelivery
|
||||
increment: Inherit
|
||||
continuous-delivery-fallback-tag: ci
|
||||
|
||||
major-version-bump-message: "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?(!:|:.*\\n\\n((.+\\n)+\\n)?BREAKING CHANGE:\\s.+)"
|
||||
minor-version-bump-message: "^(feat)(\\([\\w\\s-]*\\))?:"
|
||||
patch-version-bump-message: "^(build|chore|ci|docs|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?:"
|
||||
|
||||
assembly-versioning-scheme: MajorMinorPatch
|
||||
assembly-file-versioning-scheme: MajorMinorPatch
|
||||
assembly-informational-format: "{InformationalVersion}"
|
||||
|
||||
tag-prefix: "[vV]"
|
|
@ -1,16 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using Ical.Net.CalendarComponents;
|
||||
using Ical.Net.DataTypes;
|
||||
using Ical.Net.Serialization;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Generators;
|
||||
using MfGames.Nitride.Temporal;
|
||||
|
||||
using NodaTime;
|
||||
|
@ -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,17 +58,21 @@ 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);
|
||||
|
||||
SplitEntityEnumerations split = input.SplitEntity<Instant>();
|
||||
IEnumerable<Entity> datedAndCalendars = this.CreateCalendarEntity(split.HasAll);
|
||||
IEnumerable<Entity> datedAndCalendars =
|
||||
this.CreateCalendarEntity(split.HasAll);
|
||||
|
||||
return datedAndCalendars.Union(split.NotHasAll);
|
||||
}
|
||||
|
||||
private IEnumerable<Entity> CreateCalendarEntity(IEnumerable<Entity> entities)
|
||||
private IEnumerable<Entity> CreateCalendarEntity(
|
||||
IEnumerable<Entity> entities)
|
||||
{
|
||||
// Create the calendar in the same time zone as the rest of the system.
|
||||
var calendar = new Ical.Net.Calendar();
|
||||
|
@ -76,7 +81,8 @@ public partial class CreateCalender : OperationBase
|
|||
|
||||
// Go through the events and add all of them.
|
||||
var input = entities.ToList();
|
||||
IEnumerable<CalendarEvent> events = input.Select(this.CreateCalendarEvent);
|
||||
IEnumerable<CalendarEvent> events =
|
||||
input.Select(this.CreateCalendarEvent);
|
||||
|
||||
calendar.Events.AddRange(events);
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Calendar;
|
||||
|
||||
/// <summary>
|
||||
/// A marker component for identifying an entity that represents a calendar.
|
||||
/// </summary>
|
||||
public record IsCalendar
|
||||
[SingletonComponent]
|
||||
public partial class IsCalendar
|
||||
{
|
||||
public static IsCalendar Instance { get; } = new();
|
||||
}
|
||||
|
|
|
@ -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.3.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,6 @@
|
|||
using Autofac;
|
||||
|
||||
using MfGames.Nitride.Temporal;
|
||||
using MfGames.Nitride.Temporal.Setup;
|
||||
|
||||
namespace MfGames.Nitride.Calendar;
|
||||
|
||||
|
@ -8,7 +8,8 @@ public static class NitrideCalendarBuilderExtensions
|
|||
{
|
||||
public static NitrideBuilder UseCalendar(this NitrideBuilder builder)
|
||||
{
|
||||
return builder.UseTemporal()
|
||||
return builder
|
||||
.UseTemporal()
|
||||
.ConfigureContainer(x => x.RegisterModule<NitrideCalendarModule>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Feeds.Structure;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Feeds;
|
||||
|
||||
/// <summary>
|
||||
/// A marker component that indicates this page is a feed.
|
||||
/// </summary>
|
||||
public class IsFeed
|
||||
[SingletonComponent]
|
||||
public partial class IsFeed
|
||||
{
|
||||
public IsFeed()
|
||||
{
|
||||
}
|
||||
|
||||
public static IsFeed Instance { get; } = new();
|
||||
}
|
||||
|
|
|
@ -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.3.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 -->
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Autofac;
|
||||
|
||||
using MfGames.Nitride.Temporal;
|
||||
using MfGames.Nitride.Temporal.Setup;
|
||||
|
||||
namespace MfGames.Nitride.Feeds;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.Xml.Linq;
|
||||
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Feeds.Structure;
|
||||
|
||||
/// <summary>
|
||||
|
@ -29,7 +31,10 @@ public partial class AtomAuthor
|
|||
|
||||
if (!string.IsNullOrEmpty(this.Name))
|
||||
{
|
||||
author.Add(new XElement(XmlConstants.AtomNamespace + "name", new XText(this.Name)));
|
||||
author.Add(
|
||||
new XElement(
|
||||
XmlConstants.AtomNamespace + "name",
|
||||
new XText(this.Name)));
|
||||
}
|
||||
|
||||
return author;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Feeds.Structure;
|
||||
|
||||
/// <summary>
|
||||
|
@ -36,7 +38,9 @@ public partial class AtomCategory
|
|||
throw new NullReferenceException("Category term cannot be null.");
|
||||
}
|
||||
|
||||
var elem = new XElement(XmlConstants.AtomNamespace + "category", new XAttribute("term", this.Term));
|
||||
var elem = new XElement(
|
||||
XmlConstants.AtomNamespace + "category",
|
||||
new XAttribute("term", this.Term));
|
||||
|
||||
if (this.Scheme != null)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using static MfGames.Nitride.Feeds.Structure.XmlConstants;
|
||||
|
|
|
@ -24,7 +24,10 @@ public static class AtomHelper
|
|||
{
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
elem.Add(new XElement(XmlConstants.AtomNamespace + name, new XText(text)));
|
||||
elem.Add(
|
||||
new XElement(
|
||||
XmlConstants.AtomNamespace + name,
|
||||
new XText(text)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,12 @@ public static class XmlConstants
|
|||
/// <summary>
|
||||
/// The XML namespace for Atom feeds.
|
||||
/// </summary>
|
||||
public static readonly XNamespace AtomNamespace = "http://www.w3.org/2005/Atom";
|
||||
public static readonly XNamespace AtomNamespace =
|
||||
"http://www.w3.org/2005/Atom";
|
||||
|
||||
/// <summary>
|
||||
/// The XML namespace for media.
|
||||
/// </summary>
|
||||
public static readonly XNamespace MediaNamespace = "http://search.yahoo.com/mrss/";
|
||||
public static readonly XNamespace MediaNamespace =
|
||||
"http://search.yahoo.com/mrss/";
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Gemtext;
|
||||
|
||||
/// <summary>
|
||||
/// A marker component for indicating that an entity is Gemtext, the format
|
||||
/// for text files using the Gemini protocol.
|
||||
/// </summary>
|
||||
public record IsGemtext
|
||||
[SingletonComponent]
|
||||
public partial class IsGemtext
|
||||
{
|
||||
public static IsGemtext Instance { get; } = new();
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ public static class NitrideGemtextBuilderExtensions
|
|||
{
|
||||
public static NitrideBuilder UseGemtext(this NitrideBuilder builder)
|
||||
{
|
||||
return builder.ConfigureContainer(x => x.RegisterModule<NitrideGemtextModule>());
|
||||
return builder.ConfigureContainer(
|
||||
x => x.RegisterModule<NitrideGemtextModule>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace MfGames.Nitride.Generators;
|
|||
|
||||
/// <summary>
|
||||
/// Internal class that consolidates all of the information needed to generate a
|
||||
/// file.
|
||||
/// class for adding With* properties.
|
||||
/// </summary>
|
||||
internal class WithPropertyClass
|
||||
public class ClassAttributeReference
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the syntax for the class declaration.
|
|
@ -0,0 +1,66 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace MfGames.Nitride.Generators;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for classes marked with an attribute.
|
||||
/// </summary>
|
||||
public abstract class ClassAttributeSourceGeneratorBase<TSyntaxReceiver>
|
||||
: ISourceGenerator
|
||||
where TSyntaxReceiver : ClassAttributeSyntaxReceiverBase
|
||||
{
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
// Get the generator infrastructure will create a receiver and
|
||||
// populate it we can retrieve the populated instance via the
|
||||
// context.
|
||||
if (context.SyntaxReceiver is not TSyntaxReceiver syntaxReceiver)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Report any messages.
|
||||
foreach (string? message in syntaxReceiver.Messages)
|
||||
{
|
||||
context.Warning(
|
||||
MessageCode.Debug,
|
||||
Location.Create(
|
||||
"Temporary.g.cs",
|
||||
TextSpan.FromBounds(0, 0),
|
||||
new LinePositionSpan(
|
||||
new LinePosition(0, 0),
|
||||
new LinePosition(0, 0))),
|
||||
"{0}: Syntax Message: {1}",
|
||||
this.GetType().Name,
|
||||
message);
|
||||
}
|
||||
|
||||
// If we didn't find anything, then there is nothing to do.
|
||||
if (syntaxReceiver.ReferenceList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Go through each one.
|
||||
foreach (ClassAttributeReference reference in syntaxReceiver
|
||||
.ReferenceList)
|
||||
{
|
||||
this.GenerateClassFile(context, reference);
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
// Register a factory that can create our custom syntax receiver
|
||||
context.RegisterForSyntaxNotifications(
|
||||
() => this.CreateSyntaxReceiver(context));
|
||||
}
|
||||
|
||||
protected abstract TSyntaxReceiver CreateSyntaxReceiver(
|
||||
GeneratorInitializationContext context);
|
||||
|
||||
protected abstract void GenerateClassFile(
|
||||
GeneratorExecutionContext context,
|
||||
ClassAttributeReference reference);
|
||||
}
|
|
@ -6,18 +6,26 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|||
|
||||
namespace MfGames.Nitride.Generators;
|
||||
|
||||
internal class WithPropertySyntaxReceiver : ISyntaxReceiver
|
||||
public abstract class ClassAttributeSyntaxReceiverBase : ISyntaxReceiver
|
||||
{
|
||||
private readonly string attributeName;
|
||||
|
||||
private readonly GeneratorInitializationContext context;
|
||||
|
||||
public WithPropertySyntaxReceiver(GeneratorInitializationContext context)
|
||||
public ClassAttributeSyntaxReceiverBase(
|
||||
GeneratorInitializationContext context,
|
||||
string attributeName)
|
||||
{
|
||||
this.context = context;
|
||||
this.ClassList = new List<WithPropertyClass>();
|
||||
this.attributeName = attributeName;
|
||||
this.ReferenceList = new List<ClassAttributeReference>();
|
||||
this.Messages = new List<string>();
|
||||
}
|
||||
|
||||
public List<WithPropertyClass> ClassList { get; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether we should debug parsing attributes.
|
||||
/// </summary>
|
||||
public bool DebugAttributes { get; set; }
|
||||
|
||||
public List<string> Messages { get; }
|
||||
|
||||
|
@ -26,6 +34,8 @@ internal class WithPropertySyntaxReceiver : ISyntaxReceiver
|
|||
/// </summary>
|
||||
public string? Namespace { get; private set; }
|
||||
|
||||
public List<ClassAttributeReference> ReferenceList { get; }
|
||||
|
||||
public List<UsingDirectiveSyntax> UsingDirectiveList { get; set; } = new();
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
|
@ -64,21 +74,30 @@ internal class WithPropertySyntaxReceiver : ISyntaxReceiver
|
|||
}
|
||||
|
||||
// See if the class has our set properties attribute.
|
||||
bool found = cds.AttributeLists.AsEnumerable()
|
||||
var attributes = cds.AttributeLists
|
||||
.AsEnumerable()
|
||||
.SelectMany(x => x.Attributes)
|
||||
.Select(x => x.Name.ToString())
|
||||
.ToList();
|
||||
bool found = attributes
|
||||
.Any(
|
||||
x => x switch
|
||||
{
|
||||
"WithProperties" => true,
|
||||
"WithPropertiesAttribute" => true,
|
||||
_ => false,
|
||||
});
|
||||
x => x == this.attributeName
|
||||
|| x == $"{this.attributeName}Attribute");
|
||||
|
||||
if (this.DebugAttributes)
|
||||
{
|
||||
this.Messages.Add(
|
||||
string.Format(
|
||||
"Parsing {0} found? {1} from attributes [{2}]",
|
||||
cds.Identifier,
|
||||
found,
|
||||
string.Join(", ", attributes)));
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
this.ClassList.Add(
|
||||
new WithPropertyClass
|
||||
this.ReferenceList.Add(
|
||||
new ClassAttributeReference
|
||||
{
|
||||
Namespace = this.Namespace!,
|
||||
UsingDirectiveList = this.UsingDirectiveList,
|
|
@ -18,7 +18,7 @@ public static class CodeAnalysisExtensions
|
|||
this GeneratorExecutionContext context,
|
||||
MessageCode messageCode,
|
||||
string format,
|
||||
params object[] parameters)
|
||||
params object?[] parameters)
|
||||
{
|
||||
Error(context, messageCode, null, format, parameters);
|
||||
}
|
||||
|
@ -36,9 +36,14 @@ public static class CodeAnalysisExtensions
|
|||
MessageCode messageCode,
|
||||
Location? location,
|
||||
string format,
|
||||
params object[] parameters)
|
||||
params object?[] parameters)
|
||||
{
|
||||
context.Message(messageCode, location, DiagnosticSeverity.Error, format, parameters);
|
||||
context.Message(
|
||||
messageCode,
|
||||
location,
|
||||
DiagnosticSeverity.Error,
|
||||
format,
|
||||
parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -52,7 +57,7 @@ public static class CodeAnalysisExtensions
|
|||
this GeneratorExecutionContext context,
|
||||
MessageCode messageCode,
|
||||
string format,
|
||||
params object[] parameters)
|
||||
params object?[] parameters)
|
||||
{
|
||||
Information(context, messageCode, null, format, parameters);
|
||||
}
|
||||
|
@ -70,9 +75,14 @@ public static class CodeAnalysisExtensions
|
|||
MessageCode messageCode,
|
||||
Location? location,
|
||||
string format,
|
||||
params object[] parameters)
|
||||
params object?[] parameters)
|
||||
{
|
||||
context.Message(messageCode, location, DiagnosticSeverity.Info, format, parameters);
|
||||
context.Message(
|
||||
messageCode,
|
||||
location,
|
||||
DiagnosticSeverity.Info,
|
||||
format,
|
||||
parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -86,7 +96,7 @@ public static class CodeAnalysisExtensions
|
|||
this GeneratorExecutionContext context,
|
||||
MessageCode messageCode,
|
||||
string format,
|
||||
params object[] parameters)
|
||||
params object?[] parameters)
|
||||
{
|
||||
Warning(context, messageCode, null, format, parameters);
|
||||
}
|
||||
|
@ -104,9 +114,14 @@ public static class CodeAnalysisExtensions
|
|||
MessageCode messageCode,
|
||||
Location? location,
|
||||
string format,
|
||||
params object[] parameters)
|
||||
params object?[] parameters)
|
||||
{
|
||||
context.Message(messageCode, location, DiagnosticSeverity.Warning, format, parameters);
|
||||
context.Message(
|
||||
messageCode,
|
||||
location,
|
||||
DiagnosticSeverity.Warning,
|
||||
format,
|
||||
parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -124,7 +139,7 @@ public static class CodeAnalysisExtensions
|
|||
Location? location,
|
||||
DiagnosticSeverity severity,
|
||||
string format,
|
||||
params object[] parameters)
|
||||
params object?[] parameters)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
|
@ -134,7 +149,10 @@ public static class CodeAnalysisExtensions
|
|||
severity,
|
||||
severity,
|
||||
true,
|
||||
severity is DiagnosticSeverity.Warning or DiagnosticSeverity.Info ? 4 : 0,
|
||||
severity is DiagnosticSeverity.Warning
|
||||
or DiagnosticSeverity.Info
|
||||
? 4
|
||||
: 0,
|
||||
location: location));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.3.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>
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
using System.Text;
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace MfGames.Nitride.Generators;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a source generator that creates the additional properties
|
||||
/// and methods for a singleton component including the constructor and
|
||||
/// instance methods, along with extension methods for adding them to entities.
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class SingletonComponentSourceGenerator
|
||||
: ClassAttributeSourceGeneratorBase<SingletonComponentSyntaxReceiver>
|
||||
{
|
||||
protected override SingletonComponentSyntaxReceiver CreateSyntaxReceiver(
|
||||
GeneratorInitializationContext context)
|
||||
{
|
||||
return new SingletonComponentSyntaxReceiver(context);
|
||||
}
|
||||
|
||||
protected override void GenerateClassFile(
|
||||
GeneratorExecutionContext context,
|
||||
ClassAttributeReference unit)
|
||||
{
|
||||
// Pull out some fields.
|
||||
ClassDeclarationSyntax cds = unit.ClassDeclaration;
|
||||
|
||||
// Create the partial class.
|
||||
StringBuilder buffer = new();
|
||||
buffer.AppendLine("#nullable enable");
|
||||
|
||||
// Copy the using statements from the file.
|
||||
foreach (UsingDirectiveSyntax? uds in unit.UsingDirectiveList)
|
||||
{
|
||||
buffer.AppendLine(uds.ToString());
|
||||
}
|
||||
|
||||
buffer.AppendLine();
|
||||
|
||||
// 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);",
|
||||
$" }}",
|
||||
$" }}",
|
||||
$"}}",
|
||||
""));
|
||||
|
||||
// Create the source text and write out the file.
|
||||
var sourceText = SourceText.From(buffer.ToString(), Encoding.UTF8);
|
||||
context.AddSource(cls + ".Generated.cs", sourceText);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace MfGames.Nitride.Generators;
|
||||
|
||||
public class SingletonComponentSyntaxReceiver : ClassAttributeSyntaxReceiverBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public SingletonComponentSyntaxReceiver(
|
||||
GeneratorInitializationContext context)
|
||||
: base(context, "SingletonComponent")
|
||||
{
|
||||
}
|
||||
}
|
|
@ -15,58 +15,22 @@ namespace MfGames.Nitride.Generators;
|
|||
/// together calls.
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class WithPropertySourceGenerator : ISourceGenerator
|
||||
public class WithPropertiesSourceGenerator
|
||||
: ClassAttributeSourceGeneratorBase<WithPropertiesSyntaxReceiver>
|
||||
{
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
/// <inheritdoc />
|
||||
protected override WithPropertiesSyntaxReceiver CreateSyntaxReceiver(
|
||||
GeneratorInitializationContext context)
|
||||
{
|
||||
// Get the generator infrastructure will create a receiver and
|
||||
// populate it we can retrieve the populated instance via the
|
||||
// context.
|
||||
var syntaxReceiver = (WithPropertySyntaxReceiver?)context.SyntaxReceiver;
|
||||
|
||||
if (syntaxReceiver == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Report any messages.
|
||||
foreach (string? message in syntaxReceiver.Messages)
|
||||
{
|
||||
context.Information(
|
||||
MessageCode.Debug,
|
||||
Location.Create(
|
||||
"Temporary.g.cs",
|
||||
TextSpan.FromBounds(0, 0),
|
||||
new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0))),
|
||||
"Generating additional identifier code: {0}",
|
||||
message);
|
||||
}
|
||||
|
||||
// If we didn't find anything, then there is nothing to do.
|
||||
if (syntaxReceiver.ClassList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Go through each one.
|
||||
foreach (WithPropertyClass classInfo in syntaxReceiver.ClassList)
|
||||
{
|
||||
this.GenerateClassFile(context, classInfo);
|
||||
}
|
||||
return new WithPropertiesSyntaxReceiver(context);
|
||||
}
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
// Register a factory that can create our custom syntax receiver
|
||||
context.RegisterForSyntaxNotifications(() => new WithPropertySyntaxReceiver(context));
|
||||
}
|
||||
|
||||
private void GenerateClassFile(
|
||||
protected override void GenerateClassFile(
|
||||
GeneratorExecutionContext context,
|
||||
WithPropertyClass unit)
|
||||
ClassAttributeReference unit)
|
||||
{
|
||||
// Pull out some fields.
|
||||
ClassDeclarationSyntax? cds = unit.ClassDeclaration;
|
||||
ClassDeclarationSyntax cds = unit.ClassDeclaration;
|
||||
|
||||
// Create the partial class.
|
||||
StringBuilder buffer = new();
|
||||
|
@ -96,7 +60,9 @@ public class WithPropertySourceGenerator : ISourceGenerator
|
|||
foreach (PropertyDeclarationSyntax pds in properties)
|
||||
{
|
||||
// See if we have a setter.
|
||||
bool found = pds.AccessorList?.Accessors.Any(x => x.Keyword.ToString() == "set") ?? false;
|
||||
bool found = pds.AccessorList?.Accessors
|
||||
.Any(x => x.Keyword.ToString() == "set")
|
||||
?? false;
|
||||
|
||||
if (!found)
|
||||
{
|
||||
|
@ -132,7 +98,8 @@ public class WithPropertySourceGenerator : ISourceGenerator
|
|||
pds.Type));
|
||||
|
||||
buffer.AppendLine(" {");
|
||||
buffer.AppendLine(string.Format(" this.{0} = value;", pds.Identifier));
|
||||
buffer.AppendLine(
|
||||
string.Format(" this.{0} = value;", pds.Identifier));
|
||||
buffer.AppendLine(" return this;");
|
||||
buffer.AppendLine(" }");
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace MfGames.Nitride.Generators;
|
||||
|
||||
public class WithPropertiesSyntaxReceiver : ClassAttributeSyntaxReceiverBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public WithPropertiesSyntaxReceiver(GeneratorInitializationContext context)
|
||||
: base(context, "WithProperties")
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using HandlebarsDotNet;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Handlebars;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -61,7 +64,8 @@ public partial class ApplyStyleTemplate : OperationBase
|
|||
{
|
||||
object model = this.CreateModelCallback!(entity);
|
||||
string name = this.GetTemplateName!(entity);
|
||||
HandlebarsTemplate<object, object> template = this.cache.GetNamedTemplate(name);
|
||||
HandlebarsTemplate<object, object> template =
|
||||
this.cache.GetNamedTemplate(name);
|
||||
string result = template(model!);
|
||||
|
||||
return entity.SetTextContent(result);
|
||||
|
|
|
@ -23,12 +23,18 @@ public class ForEachHandlebarsBlock<TModel> : HandlebarsBlockBase
|
|||
/// <summary>
|
||||
/// Gets or sets the callback that is called when nothing is found.
|
||||
/// </summary>
|
||||
public Action<EncodedTextWriter, BlockHelperOptions, Context, Arguments>? NothingFound { get; set; }
|
||||
public Action<EncodedTextWriter, BlockHelperOptions, Context, Arguments>?
|
||||
NothingFound
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string HelperName { get; }
|
||||
|
||||
public ForEachHandlebarsBlock<TModel> WithGetList(Func<TModel, IEnumerable<object>>? callback)
|
||||
public ForEachHandlebarsBlock<TModel> WithGetList(
|
||||
Func<TModel, IEnumerable<object>>? callback)
|
||||
{
|
||||
this.GetList = callback;
|
||||
|
||||
|
@ -36,7 +42,8 @@ public class ForEachHandlebarsBlock<TModel> : HandlebarsBlockBase
|
|||
}
|
||||
|
||||
public ForEachHandlebarsBlock<TModel> WithNothingFoundText(
|
||||
Action<EncodedTextWriter, BlockHelperOptions, Context, Arguments>? callback)
|
||||
Action<EncodedTextWriter, BlockHelperOptions, Context, Arguments>?
|
||||
callback)
|
||||
{
|
||||
this.NothingFound = callback;
|
||||
|
||||
|
|
|
@ -16,13 +16,15 @@ public class HandlebarsTemplateCache
|
|||
|
||||
private readonly ModificationSynchronizer locker;
|
||||
|
||||
private readonly Dictionary<string, HandlebarsTemplate<object, object>> templates;
|
||||
private readonly Dictionary<string, HandlebarsTemplate<object, object>>
|
||||
templates;
|
||||
|
||||
public HandlebarsTemplateCache(IHandlebars handlebars)
|
||||
{
|
||||
this.handlebars = handlebars;
|
||||
this.locker = new ModificationSynchronizer();
|
||||
this.templates = new Dictionary<string, HandlebarsTemplate<object, object>>();
|
||||
this.templates =
|
||||
new Dictionary<string, HandlebarsTemplate<object, object>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -39,7 +41,8 @@ public class HandlebarsTemplateCache
|
|||
() => !this.templates.ContainsKey(literal),
|
||||
() =>
|
||||
{
|
||||
HandlebarsTemplate<object, object> template = this.handlebars!.Compile(literal);
|
||||
HandlebarsTemplate<object, object> template =
|
||||
this.handlebars!.Compile(literal);
|
||||
|
||||
this.templates[literal] = template;
|
||||
|
||||
|
@ -56,7 +59,8 @@ public class HandlebarsTemplateCache
|
|||
/// </summary>
|
||||
/// <param name="templateName"></param>
|
||||
/// <returns></returns>
|
||||
public HandlebarsTemplate<object, object> GetNamedTemplate(string templateName)
|
||||
public HandlebarsTemplate<object, object> GetNamedTemplate(
|
||||
string templateName)
|
||||
{
|
||||
string template = $"{{{{> {templateName}}}}}";
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Handlebars;
|
||||
|
||||
|
@ -17,7 +19,8 @@ public partial class IdentifyHandlebarsFromComponent : IOperation
|
|||
{
|
||||
private readonly IValidator<IdentifyHandlebarsFromComponent> validator;
|
||||
|
||||
public IdentifyHandlebarsFromComponent(IValidator<IdentifyHandlebarsFromComponent> validator)
|
||||
public IdentifyHandlebarsFromComponent(
|
||||
IValidator<IdentifyHandlebarsFromComponent> validator)
|
||||
{
|
||||
this.validator = validator;
|
||||
}
|
||||
|
@ -25,11 +28,15 @@ 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);
|
||||
|
||||
return input.Select(
|
||||
(entity) => this.HasHandlebarsTest.Invoke(entity) ? entity.Set(HasHandlebarsTemplate.Instance) : entity);
|
||||
(entity) => this.HasHandlebarsTest.Invoke(entity)
|
||||
? entity.Set(HasHandlebarsTemplate.Instance)
|
||||
: entity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ using FluentValidation;
|
|||
|
||||
namespace MfGames.Nitride.Handlebars;
|
||||
|
||||
public class IdentifyHandlebarsFromComponentValidator : AbstractValidator<IdentifyHandlebarsFromComponent>
|
||||
public class IdentifyHandlebarsFromComponentValidator
|
||||
: AbstractValidator<IdentifyHandlebarsFromComponent>
|
||||
{
|
||||
public IdentifyHandlebarsFromComponentValidator()
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using MfGames.Nitride.Contents;
|
||||
|
||||
namespace MfGames.Nitride.Handlebars;
|
||||
|
@ -13,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);
|
||||
}
|
||||
|
@ -22,7 +24,7 @@ public class IdentifyHandlebarsFromContent : IOperation
|
|||
Entity entity,
|
||||
ITextContent content)
|
||||
{
|
||||
string text = content.GetText();
|
||||
string text = content.GetTextContentString();
|
||||
|
||||
if (text.Contains("{{") && text.Contains("}}"))
|
||||
{
|
||||
|
|
|
@ -10,15 +10,15 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="6.4.0" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.3.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 -->
|
||||
|
|
|
@ -12,7 +12,8 @@ public static class NitrideHandlebarsBuilderExtensions
|
|||
public static NitrideBuilder UseHandlebars(this NitrideBuilder builder)
|
||||
{
|
||||
return builder
|
||||
.ConfigureContainer(x => x.RegisterModule<NitrideHandlebarsModule>());
|
||||
.ConfigureContainer(
|
||||
x => x.RegisterModule<NitrideHandlebarsModule>());
|
||||
}
|
||||
|
||||
public static NitrideBuilder UseHandlebars(
|
||||
|
|
|
@ -23,8 +23,10 @@ public class NitrideHandlebarsModule : Module
|
|||
builder.Register(
|
||||
(context) =>
|
||||
{
|
||||
IHandlebars handlebars = HandlebarsDotNet.Handlebars.Create();
|
||||
IEnumerable<IHandlebarsLoader> helpers = context.Resolve<IEnumerable<IHandlebarsLoader>>();
|
||||
IHandlebars handlebars =
|
||||
HandlebarsDotNet.Handlebars.Create();
|
||||
IEnumerable<IHandlebarsLoader> helpers =
|
||||
context.Resolve<IEnumerable<IHandlebarsLoader>>();
|
||||
|
||||
foreach (IHandlebarsLoader helper in helpers)
|
||||
{
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using HandlebarsDotNet;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Handlebars;
|
||||
|
||||
|
@ -39,11 +40,14 @@ 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);
|
||||
|
||||
return input.SelectEntity<HasHandlebarsTemplate, ITextContent>(this.Apply);
|
||||
return input.SelectEntity<HasHandlebarsTemplate, ITextContent>(
|
||||
this.Apply);
|
||||
}
|
||||
|
||||
private Entity Apply(
|
||||
|
@ -51,8 +55,9 @@ public partial class RenderContentTemplate : OperationBase
|
|||
HasHandlebarsTemplate _,
|
||||
ITextContent content)
|
||||
{
|
||||
string text = content.GetText();
|
||||
HandlebarsTemplate<object, object> template = this.cache.GetLiteralTemplate(text);
|
||||
string text = content.GetTextContentString();
|
||||
HandlebarsTemplate<object, object> template =
|
||||
this.cache.GetLiteralTemplate(text);
|
||||
object model = this.CreateModelCallback!(entity);
|
||||
string result = template(model!);
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ using FluentValidation;
|
|||
|
||||
namespace MfGames.Nitride.Handlebars;
|
||||
|
||||
public class RenderContentTemplateValidator : AbstractValidator<RenderContentTemplate>
|
||||
public class RenderContentTemplateValidator
|
||||
: AbstractValidator<RenderContentTemplate>
|
||||
{
|
||||
public RenderContentTemplateValidator()
|
||||
{
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using MfGames.Nitride.Contents;
|
||||
|
||||
namespace MfGames.Nitride.Html;
|
||||
|
@ -14,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);
|
||||
}
|
||||
|
@ -23,7 +25,7 @@ public class ConvertHtmlEntitiesToUnicode : OperationBase
|
|||
Entity entity,
|
||||
ITextContent content)
|
||||
{
|
||||
string text = content.GetText();
|
||||
string text = content.GetTextContentString();
|
||||
string resolved = WebUtility.HtmlDecode(text);
|
||||
|
||||
return entity.SetTextContent(resolved);
|
||||
|
|
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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Html;
|
||||
|
||||
/// <summary>
|
||||
/// A marker component that indicates that the entity is an HTML file.
|
||||
/// </summary>
|
||||
public record IsHtml
|
||||
[SingletonComponent]
|
||||
public partial class IsHtml
|
||||
{
|
||||
public static IsHtml Instance { get; } = new();
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.3.0" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -6,6 +6,7 @@ public static class NitrideHtmlBuilderExtensions
|
|||
{
|
||||
public static NitrideBuilder UseHtml(this NitrideBuilder builder)
|
||||
{
|
||||
return builder.ConfigureContainer(x => x.RegisterModule<NitrideHtmlModule>());
|
||||
return builder.ConfigureContainer(
|
||||
x => x.RegisterModule<NitrideHtmlModule>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -22,7 +22,9 @@ public class FileEntryTextContent : ITextContent, IBinaryContentConvertable
|
|||
/// <inheritdoc />
|
||||
public TextReader GetReader()
|
||||
{
|
||||
return new StreamReader(this.entry.Open(FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8);
|
||||
return new StreamReader(
|
||||
this.entry.Open(FileMode.Open, FileAccess.Read, FileShare.Read),
|
||||
Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -2,14 +2,15 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using DotNet.Globbing;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Zio;
|
||||
|
||||
|
@ -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,13 +55,16 @@ 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);
|
||||
|
||||
var glob = Glob.Parse(this.Pattern);
|
||||
|
||||
IEnumerable<FileEntry> files = this.FileSystem.EnumerateFileEntries("/", "*", SearchOption.AllDirectories)
|
||||
IEnumerable<FileEntry> files = this.FileSystem
|
||||
.EnumerateFileEntries("/", "*", SearchOption.AllDirectories)
|
||||
.Where(x => glob.IsMatch(x.Path.ToString()));
|
||||
|
||||
IEnumerable<Entity> entities = files.Select(this.ToEntity);
|
||||
|
|
|
@ -3,12 +3,13 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Serilog;
|
||||
|
||||
|
@ -48,7 +49,8 @@ public partial class WriteFiles : FileSystemOperationBase, IOperation
|
|||
public Dictionary<Type, Func<IContent, Stream>> StreamFactories
|
||||
{
|
||||
get => this.factories;
|
||||
set => this.factories = value ?? throw new ArgumentNullException(nameof(value));
|
||||
set => this.factories =
|
||||
value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -62,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);
|
||||
|
||||
|
@ -102,7 +107,9 @@ public partial class WriteFiles : FileSystemOperationBase, IOperation
|
|||
.ReadToEnd();
|
||||
|
||||
var stream = new MemoryStream();
|
||||
var writer = new StreamWriter(stream, this.TextEncoding ?? Encoding.UTF8);
|
||||
var writer = new StreamWriter(
|
||||
stream,
|
||||
this.TextEncoding ?? Encoding.UTF8);
|
||||
|
||||
writer.Write(text);
|
||||
writer.Flush();
|
||||
|
@ -133,7 +140,9 @@ public partial class WriteFiles : FileSystemOperationBase, IOperation
|
|||
// First see if we have a factory for the exact type of content.
|
||||
IContent content = entity.GetContent();
|
||||
|
||||
if (this.factories.TryGetValue(content.GetType(), out Func<IContent, Stream>? getStream))
|
||||
if (this.factories.TryGetValue(
|
||||
content.GetType(),
|
||||
out Func<IContent, Stream>? getStream))
|
||||
{
|
||||
Stream stream = getStream(content);
|
||||
|
||||
|
@ -143,7 +152,9 @@ public partial class WriteFiles : FileSystemOperationBase, IOperation
|
|||
// If we have an easy conversion, then use that so we don't have to
|
||||
// walk up the tree looking for one we do have.
|
||||
if (content is IBinaryContentConvertable binaryConvertable
|
||||
&& this.factories.TryGetValue(typeof(IBinaryContent), out Func<IContent, Stream>? binaryContent))
|
||||
&& this.factories.TryGetValue(
|
||||
typeof(IBinaryContent),
|
||||
out Func<IContent, Stream>? binaryContent))
|
||||
{
|
||||
Stream stream = binaryContent(binaryConvertable.ToBinaryContent());
|
||||
|
||||
|
@ -151,7 +162,9 @@ public partial class WriteFiles : FileSystemOperationBase, IOperation
|
|||
}
|
||||
|
||||
if (content is ITextContentConvertable textConvertable
|
||||
&& this.factories.TryGetValue(typeof(ITextContent), out Func<IContent, Stream>? textContent))
|
||||
&& this.factories.TryGetValue(
|
||||
typeof(ITextContent),
|
||||
out Func<IContent, Stream>? textContent))
|
||||
{
|
||||
Stream stream = textContent(textConvertable.ToTextContent());
|
||||
|
||||
|
@ -167,7 +180,12 @@ public partial class WriteFiles : FileSystemOperationBase, IOperation
|
|||
{
|
||||
// Check to see if we have any of these types.
|
||||
Func<IContent, Stream>? found = types
|
||||
.Select(x => this.factories.TryGetValue(x, out Func<IContent, Stream>? factory) ? factory : null)
|
||||
.Select(
|
||||
x => this.factories.TryGetValue(
|
||||
x,
|
||||
out Func<IContent, Stream>? factory)
|
||||
? factory
|
||||
: null)
|
||||
.FirstOrDefault(x => x != null);
|
||||
|
||||
if (found != null)
|
||||
|
@ -179,7 +197,8 @@ public partial class WriteFiles : FileSystemOperationBase, IOperation
|
|||
|
||||
// We didn't find one, so add all the parent types and try
|
||||
// again with the new list.
|
||||
types = types.SelectMany(x => new[] { x.BaseType }.Union(x.GetInterfaces()))
|
||||
types = types
|
||||
.SelectMany(x => new[] { x.BaseType }.Union(x.GetInterfaces()))
|
||||
.Where(x => x != null)
|
||||
.Select(x => x!)
|
||||
.ToList();
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Serilog;
|
||||
|
||||
|
@ -39,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);
|
||||
|
||||
|
@ -71,7 +76,8 @@ public partial class ClearDirectory : FileSystemOperationBase, IOperation
|
|||
|
||||
// Clear out the contents.
|
||||
IEnumerable<UPath> files = this.FileSystem.EnumerateFiles(path);
|
||||
IEnumerable<UPath> directories = this.FileSystem.EnumerateDirectories(path);
|
||||
IEnumerable<UPath> directories =
|
||||
this.FileSystem.EnumerateDirectories(path);
|
||||
|
||||
foreach (UPath file in files)
|
||||
{
|
||||
|
|
|
@ -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.3.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 -->
|
||||
|
|
|
@ -6,6 +6,7 @@ public static class NitrideIOBuilderExtensions
|
|||
{
|
||||
public static NitrideBuilder UseIO(this NitrideBuilder builder)
|
||||
{
|
||||
return builder.ConfigureContainer(x => x.RegisterModule<NitrideIOModule>());
|
||||
return builder.ConfigureContainer(
|
||||
x => x.RegisterModule<NitrideIOModule>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using MAB.DotIgnore;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using Zio;
|
||||
|
||||
namespace MfGames.Nitride.IO;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Zio;
|
||||
|
||||
|
@ -28,11 +30,14 @@ 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);
|
||||
|
||||
return this.replacePath.WithReplacement(this.RunReplacement)
|
||||
return this.replacePath
|
||||
.WithReplacement(this.RunReplacement)
|
||||
.Run(input);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Zio;
|
||||
|
||||
|
@ -31,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);
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ using FluentValidation;
|
|||
|
||||
namespace MfGames.Nitride.IO.Paths;
|
||||
|
||||
public class ChangePathExtensionValidator : AbstractValidator<ChangePathExtension>
|
||||
public class ChangePathExtensionValidator
|
||||
: AbstractValidator<ChangePathExtension>
|
||||
{
|
||||
public ChangePathExtensionValidator()
|
||||
{
|
||||
|
|
|
@ -3,8 +3,8 @@ using System.Collections.Generic;
|
|||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using MfGames.Nitride.Entities;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Zio;
|
||||
|
||||
|
@ -41,6 +41,8 @@ public partial class DirectChildPathScanner : EntityScanner
|
|||
|
||||
// If we are using directory indexes, skip when we have an index root.
|
||||
// Otherwise, get the parent and use that as the key.
|
||||
return path.GetDirectoryIndexPath() == "/" ? null : new[] { path.GetParentDirectoryIndexPath() };
|
||||
return path.GetDirectoryIndexPath() == "/"
|
||||
? null
|
||||
: new[] { path.GetParentDirectoryIndexPath() };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using MfGames.Nitride.Entities;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Serilog;
|
||||
|
||||
|
@ -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,9 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Zio;
|
||||
|
||||
|
@ -52,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,8 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Zio;
|
||||
|
||||
|
@ -31,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,9 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Zio;
|
||||
|
||||
|
@ -35,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);
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ This assembly contains the primary system for reading and writing from the disk,
|
|||
along with various processes to manipulate paths. It contains three primary
|
||||
components:
|
||||
|
||||
- File System I/O
|
||||
- Path Normalization
|
||||
- Disk-Based Content
|
||||
- File System I/O
|
||||
- Path Normalization
|
||||
- Disk-Based Content
|
||||
|
||||
## File System I/O
|
||||
|
||||
|
|
11
src/MfGames.Nitride.Json/IsJson.cs
Normal file
11
src/MfGames.Nitride.Json/IsJson.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Json;
|
||||
|
||||
/// <summary>
|
||||
/// A marker class that indicates that the entity is JSON.
|
||||
/// </summary>
|
||||
[SingletonComponent]
|
||||
public partial class IsJson
|
||||
{
|
||||
}
|
33
src/MfGames.Nitride.Json/MfGames.Nitride.Json.csproj
Normal file
33
src/MfGames.Nitride.Json/MfGames.Nitride.Json.csproj
Normal file
|
@ -0,0 +1,33 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>An extension to Nitride static site generator to parse JSON content.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include the source generator -->
|
||||
<PropertyGroup>
|
||||
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Generators\MfGames.Nitride.Generators.csproj">
|
||||
<OutputItemType>Analyzer</OutputItemType>
|
||||
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
12
src/MfGames.Nitride.Json/NitrideJsonBuilderExtensions.cs
Normal file
12
src/MfGames.Nitride.Json/NitrideJsonBuilderExtensions.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Autofac;
|
||||
|
||||
namespace MfGames.Nitride.Json;
|
||||
|
||||
public static class NitrideJsonBuilderExtensions
|
||||
{
|
||||
public static NitrideBuilder UseJson(this NitrideBuilder builder)
|
||||
{
|
||||
return builder.ConfigureContainer(
|
||||
x => x.RegisterModule<NitrideJsonModule>());
|
||||
}
|
||||
}
|
77
src/MfGames.Nitride.Json/NitrideJsonEntityExtensions.cs
Normal file
77
src/MfGames.Nitride.Json/NitrideJsonEntityExtensions.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace MfGames.Nitride.Json;
|
||||
|
||||
public static class NitrideJsonEntityExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the entity text as a JSON file and returns the results.
|
||||
/// </summary>
|
||||
public static TType? GetTextContentJson<TType>(
|
||||
this Entity entity,
|
||||
Action<JsonSerializerSettings> configure)
|
||||
{
|
||||
JsonSerializerSettings settings = new();
|
||||
|
||||
configure.Invoke(settings);
|
||||
|
||||
return entity.GetTextContentJson<TType>(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the entity text as a JSON file and returns the results.
|
||||
/// </summary>
|
||||
public static TType? GetTextContentJson<TType>(
|
||||
this Entity entity,
|
||||
JsonSerializerSettings? settings = null)
|
||||
{
|
||||
string? text = entity.GetTextContentString();
|
||||
|
||||
return text != null
|
||||
? JsonConvert.DeserializeObject<TType>(text, settings)
|
||||
: default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the text content to the serialized value. If this is null, then
|
||||
/// the text content is removed. This uses the default serializer which
|
||||
/// may be configured.
|
||||
/// </summary>
|
||||
/// <returns>The same entity for chaining methods.</returns>
|
||||
public static Entity SetTextContentJson<TType>(
|
||||
this Entity entity,
|
||||
TType? value,
|
||||
Action<JsonSerializerSettings> configure)
|
||||
{
|
||||
JsonSerializerSettings settings = new();
|
||||
|
||||
configure.Invoke(settings);
|
||||
|
||||
return SetTextContentJson(entity, value, settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the text content to the serialized value using the serializer
|
||||
/// provided. If the value is null, then the text content is removed.
|
||||
/// </summary>
|
||||
/// <returns>The same entity for chaining methods.</returns>
|
||||
public static Entity SetTextContentJson<TType>(
|
||||
this Entity entity,
|
||||
TType? value,
|
||||
JsonSerializerSettings? settings = null)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return entity.Remove<ITextContent>();
|
||||
}
|
||||
|
||||
string json = JsonConvert.SerializeObject(value, settings);
|
||||
|
||||
return entity.SetTextContent(json);
|
||||
}
|
||||
}
|
13
src/MfGames.Nitride.Json/NitrideJsonModule.cs
Normal file
13
src/MfGames.Nitride.Json/NitrideJsonModule.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Autofac;
|
||||
|
||||
namespace MfGames.Nitride.Json;
|
||||
|
||||
public class NitrideJsonModule : Module
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterOperators(this);
|
||||
builder.RegisterValidators(this);
|
||||
}
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using Markdig;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Markdown;
|
||||
|
||||
|
@ -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);
|
||||
|
|
|
@ -2,12 +2,10 @@ using System;
|
|||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using Markdig;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Markdown.Gemtext;
|
||||
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Gemtext;
|
||||
|
||||
|
@ -26,7 +24,8 @@ public class ConvertMarkdownToGemtext : ConvertMarkdownToBase
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ConvertMarkdownToGemtext WithConfigureMarkdown(Action<MarkdownPipelineBuilder>? value)
|
||||
public override ConvertMarkdownToGemtext WithConfigureMarkdown(
|
||||
Action<MarkdownPipelineBuilder>? value)
|
||||
{
|
||||
base.WithConfigureMarkdown(value);
|
||||
|
||||
|
@ -45,7 +44,7 @@ public class ConvertMarkdownToGemtext : ConvertMarkdownToBase
|
|||
ITextContent markdownContent,
|
||||
MarkdownPipeline options)
|
||||
{
|
||||
string markdown = markdownContent.GetText();
|
||||
string markdown = markdownContent.GetTextContentString();
|
||||
string gemtext = MarkdownGemtext.ToGemtext(markdown, options);
|
||||
var content = new StringTextContent(gemtext);
|
||||
|
||||
|
|
|
@ -2,10 +2,9 @@ using System;
|
|||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using Markdig;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Html;
|
||||
|
||||
|
@ -23,7 +22,8 @@ public class ConvertMarkdownToHtml : ConvertMarkdownToBase
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ConvertMarkdownToHtml WithConfigureMarkdown(Action<MarkdownPipelineBuilder>? value)
|
||||
public override ConvertMarkdownToHtml WithConfigureMarkdown(
|
||||
Action<MarkdownPipelineBuilder>? value)
|
||||
{
|
||||
base.WithConfigureMarkdown(value);
|
||||
|
||||
|
@ -43,7 +43,7 @@ public class ConvertMarkdownToHtml : ConvertMarkdownToBase
|
|||
MarkdownPipeline options)
|
||||
{
|
||||
// Convert the entity to Html.
|
||||
string markdown = markdownContent.GetText();
|
||||
string markdown = markdownContent.GetTextContentString();
|
||||
string html = Markdig.Markdown.ToHtml(markdown, options);
|
||||
var htmlContent = new StringTextContent(html);
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
using Zio;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,12 @@ public class IdentifyMarkdownFromPath : IdentifyMarkdown
|
|||
Entity entity,
|
||||
UPath path)
|
||||
{
|
||||
return (path.GetExtensionWithDot() ?? string.Empty).ToLowerInvariant() switch
|
||||
{
|
||||
".md" => true,
|
||||
".markdown" => true,
|
||||
_ => false,
|
||||
};
|
||||
return (path.GetExtensionWithDot() ?? string.Empty).ToLowerInvariant()
|
||||
switch
|
||||
{
|
||||
".md" => true,
|
||||
".markdown" => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// A marker class that indicates that the file is a Markdown file.
|
||||
/// </summary>
|
||||
public record IsMarkdown
|
||||
[SingletonComponent]
|
||||
public partial class IsMarkdown
|
||||
{
|
||||
public static IsMarkdown Instance { get; } = new();
|
||||
}
|
||||
|
|
|
@ -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>(
|
||||
|
@ -38,7 +41,7 @@ public class MakeSingleLinkListItems : IOperation
|
|||
/// </summary>
|
||||
private Entity MakeSingleLinkLists(Entity entity)
|
||||
{
|
||||
string content = entity.GetText()!;
|
||||
string content = entity.GetTextContentString()!;
|
||||
|
||||
string output = Regex.Replace(
|
||||
content,
|
||||
|
@ -85,9 +88,6 @@ public class MakeSingleLinkListItems : IOperation
|
|||
match =>
|
||||
{
|
||||
string link1 = match.Groups["label"].ToString();
|
||||
string path1 = string.Format(
|
||||
"/{0}/",
|
||||
this.slugs.ToSlug(link1.Split('|').First()));
|
||||
string label1 = link1.Split('|').Last();
|
||||
|
||||
return label1;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.3.0" />
|
||||
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
|
||||
<PackageReference Include="Markdig" Version="0.30.3" />
|
||||
<PackageReference Include="MfGames.Markdown.Gemtext" Version="1.2.2" />
|
||||
<PackageReference Include="Zio" Version="0.15.0" />
|
||||
|
|
|
@ -6,6 +6,7 @@ public static class NitrideMarkdownBuilderExtensions
|
|||
{
|
||||
public static NitrideBuilder UseMarkdown(this NitrideBuilder builder)
|
||||
{
|
||||
return builder.ConfigureContainer(x => x.RegisterModule<NitrideMarkdownModule>());
|
||||
return builder.ConfigureContainer(
|
||||
x => x.RegisterModule<NitrideMarkdownModule>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
using FluentValidation;
|
||||
|
||||
namespace MfGames.Nitride.Markdown;
|
||||
namespace MfGames.Nitride.Markdown.Validators;
|
||||
|
||||
public class ConvertMarkdownToBaseValidator : AbstractValidator<ConvertMarkdownToBase>
|
||||
public class ConvertMarkdownToBaseValidator
|
||||
: AbstractValidator<ConvertMarkdownToBase>
|
||||
{
|
||||
public ConvertMarkdownToBaseValidator()
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using FluentValidation;
|
||||
|
||||
namespace MfGames.Nitride.Markdown;
|
||||
namespace MfGames.Nitride.Markdown.Validators;
|
||||
|
||||
public class IdentifyMarkdownValidator : AbstractValidator<IdentifyMarkdown>
|
||||
{
|
|
@ -21,7 +21,8 @@ public class SimpleSlugConverter : ISlugConverter, IEnumerable<string>
|
|||
|
||||
public SimpleSlugConverter(
|
||||
IDictionary<string, string> replacements,
|
||||
StringComparison comparison = StringComparison.InvariantCultureIgnoreCase)
|
||||
StringComparison comparison =
|
||||
StringComparison.InvariantCultureIgnoreCase)
|
||||
: this()
|
||||
{
|
||||
foreach (KeyValuePair<string, string> pair in replacements)
|
||||
|
@ -44,7 +45,8 @@ public class SimpleSlugConverter : ISlugConverter, IEnumerable<string>
|
|||
public void Add(
|
||||
string search,
|
||||
string replace,
|
||||
StringComparison comparison = StringComparison.InvariantCultureIgnoreCase)
|
||||
StringComparison comparison =
|
||||
StringComparison.InvariantCultureIgnoreCase)
|
||||
{
|
||||
this.replacements.Add((search, replace, comparison));
|
||||
}
|
||||
|
@ -62,12 +64,15 @@ public class SimpleSlugConverter : ISlugConverter, IEnumerable<string>
|
|||
// If we have null or whitespace, we have a problem.
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
throw new ArgumentException("Cannot have a blank or null input", nameof(input));
|
||||
throw new ArgumentException(
|
||||
"Cannot have a blank or null input",
|
||||
nameof(input));
|
||||
}
|
||||
|
||||
// We need to do the replacements before we slugify.
|
||||
// Perform any additional replacements.
|
||||
foreach ((string search, string replace, StringComparison comparison) in this.replacements)
|
||||
foreach ((string search, string replace, StringComparison comparison) in
|
||||
this.replacements)
|
||||
{
|
||||
input = input.Replace(search, replace, comparison);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ public class UnicodeNormalizingSlugConverter : SimpleSlugConverter
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public UnicodeNormalizingSlugConverter(IDictionary<string, string> replacements)
|
||||
public UnicodeNormalizingSlugConverter(
|
||||
IDictionary<string, string> replacements)
|
||||
: base(replacements)
|
||||
{
|
||||
}
|
||||
|
@ -29,7 +30,9 @@ public class UnicodeNormalizingSlugConverter : SimpleSlugConverter
|
|||
// If we have null or whitespace, we have a problem.
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
throw new ArgumentException("Cannot have a blank or null input", nameof(input));
|
||||
throw new ArgumentException(
|
||||
"Cannot have a blank or null input",
|
||||
nameof(input));
|
||||
}
|
||||
|
||||
// Normalize the Unicode objects.
|
||||
|
@ -38,7 +41,8 @@ public class UnicodeNormalizingSlugConverter : SimpleSlugConverter
|
|||
.Where(this.IsNonSpacingMark)
|
||||
.ToArray();
|
||||
|
||||
string normalized = new string(chars).Normalize(NormalizationForm.FormC);
|
||||
string normalized =
|
||||
new string(chars).Normalize(NormalizationForm.FormC);
|
||||
|
||||
// Return the base implementation.
|
||||
return base.ToSlug(normalized);
|
||||
|
|
96
src/MfGames.Nitride.Temporal.Schedules/ApplySchedules.cs
Normal file
96
src/MfGames.Nitride.Temporal.Schedules/ApplySchedules.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using FluentValidation;
|
||||
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Temporal.Schedules;
|
||||
|
||||
/// <summary>
|
||||
/// Applies schedules against the list of entities.
|
||||
/// </summary>
|
||||
[WithProperties]
|
||||
public partial class ApplySchedules : OperationBase
|
||||
{
|
||||
private readonly IValidator<ApplySchedules> validator;
|
||||
|
||||
public ApplySchedules(
|
||||
IValidator<ApplySchedules> validator,
|
||||
TimeService timeService)
|
||||
{
|
||||
this.TimeService = timeService;
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 Func<Entity, IList<ISchedule>?>? GetSchedules { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time service associated with this operation.
|
||||
/// </summary>
|
||||
public TimeService TimeService { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
return input.Select(this.Apply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single schedule into the apply schedules and returns the ApplySchedule
|
||||
/// class.
|
||||
/// </summary>
|
||||
public ApplySchedules WithGetSchedules(Func<Entity, ISchedule> schedule)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// Get the schedule for this entity.
|
||||
IList<ISchedule>? schedules = this.GetSchedules?.Invoke(entity);
|
||||
|
||||
if (schedules == null || schedules.Count == 0)
|
||||
{
|
||||
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.TimeService);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
30
src/MfGames.Nitride.Temporal.Schedules/ISchedule.cs
Normal file
30
src/MfGames.Nitride.Temporal.Schedules/ISchedule.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using MfGames.Gallium;
|
||||
|
||||
namespace MfGames.Nitride.Temporal.Schedules;
|
||||
|
||||
/// <summary>
|
||||
/// A schedule is a specific schedule that can be applied to an entity to make
|
||||
/// changes based on components.
|
||||
/// </summary>
|
||||
public interface ISchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies the schedule changes to the entity and returns the results.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to update.</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,
|
||||
TimeService timeService);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a schedule applies to a given entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to test.</param>
|
||||
/// <returns>True if the schedule applies to the given entity, otherwise false.</returns>
|
||||
bool CanApply(Entity 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>An extension to Nitride static site generator to add a scheduling system for posts.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="6.4.0" />
|
||||
<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>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Temporal\MfGames.Nitride.Temporal.csproj" />
|
||||
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include the source generator -->
|
||||
<PropertyGroup>
|
||||
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MfGames.Nitride.Generators\MfGames.Nitride.Generators.csproj">
|
||||
<OutputItemType>Analyzer</OutputItemType>
|
||||
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
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);
|
||||
}
|
||||
}
|
235
src/MfGames.Nitride.Temporal.Schedules/README.md
Normal file
235
src/MfGames.Nitride.Temporal.Schedules/README.md
Normal file
|
@ -0,0 +1,235 @@
|
|||
# Schedules
|
||||
|
||||
Scheduling posts and entries is fairly common process with blob posts but the
|
||||
methods for creating those schedules can vary drastically because it requires
|
||||
site- or project-specific elements such as which components or models to change
|
||||
and how. This package provides one approach to scheduling that "applies"
|
||||
changes (as described in JSON or YAML) to a given object based on the current
|
||||
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 `TimeService`.
|
||||
|
||||
## Configuring
|
||||
|
||||
To use the modules, either the `NitrideTemporalSchedulesModule` can be added or
|
||||
the builder extension method can be used.
|
||||
|
||||
```csharp
|
||||
NitrideBuilder builder;
|
||||
|
||||
builder.UseTemporalSchedules();
|
||||
```
|
||||
|
||||
## ISchedule
|
||||
|
||||
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.
|
||||
- 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.
|
||||
|
||||
## ApplySchedules
|
||||
|
||||
The primary operation is `ApplySchedules` which take a sequence of schedule objects
|
||||
and applies each one in turn against every entity given to the operation.
|
||||
|
||||
```csharp
|
||||
IEnumerable<Entity> entities;
|
||||
IList<ISchedule> schedules;
|
||||
ApplySchedules op;
|
||||
|
||||
return op
|
||||
.WithSchedules(schedules)
|
||||
.Run(entities);
|
||||
```
|
||||
|
||||
The following properties are available (along with their corresponding `With*` methods):
|
||||
|
||||
- `IList<ISchedule> Schedules`
|
||||
- Contains the list of schedules to apply against entities.
|
||||
- Defaults to an empty list.
|
||||
- `TimeService TimeService`
|
||||
- Used to determine when the site is being generated.
|
||||
- Defaults to the one provided by `MfGames.Nitride.Temporal`.
|
||||
|
||||
## 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 `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.
|
||||
|
||||
In this example:
|
||||
|
||||
```csharp
|
||||
ApplySchedule op;
|
||||
var entities = new List<Entity>
|
||||
{
|
||||
new Entity().Set((UPath) "chapter-01.md"),
|
||||
new Entity().Set((UPath) "chapter-02.md"),
|
||||
new Entity().Set((UPath) "chapter-03.md"),
|
||||
};
|
||||
var schedules = new List<ISchedule>
|
||||
{
|
||||
new PeriodicPathRegexSchedule
|
||||
{
|
||||
PathRegex = "chapter-(\d+),
|
||||
ScheduleStart = DateTime.Parse("2023-01-01"),
|
||||
SchedulePeriod = "1 week",
|
||||
// Alternatively, SchedulePeriodTimeSpan = TimeSpan.FromDays(7),
|
||||
},
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
`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+)[^\d]*$` which grabs the last number found.
|
||||
- `DateTime ScheduleStart`
|
||||
- The date that the schedule starts.
|
||||
- No default.
|
||||
- `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.
|
||||
- `int CaptureOffset`
|
||||
- The offset to make the capture group a zero-based number.
|
||||
- Defaults to `-1` which makes `chapter-01` the first entry.
|
||||
|
||||
There is also a virtual method for applying the schedule.
|
||||
|
||||
- `protected Entity ApplySchedule(Entity entity, int number, Instant instant)`
|
||||
- The callback method for applying the schedule to the entity.
|
||||
- This defaults to `return entity.Set(instant)`.
|
||||
|
||||
#### Overriding Logic
|
||||
|
||||
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.
|
||||
|
||||
```csharp
|
||||
/// <summary>A model for the YAML front matter on a page.</summary>
|
||||
public class PageModel
|
||||
{
|
||||
/// <summary>Gets or sets the access key for the page.</summary>
|
||||
public string? Access { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the optional schedule for this page.</summary>
|
||||
List<PageSchedule>? Schedules { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>A schedule specific to this project.</summary>
|
||||
public class PageSchedule : NumericalPathSchedule
|
||||
{
|
||||
public PageSchedule()
|
||||
{
|
||||
// Set the default to weekly.
|
||||
this.SchedulePeriod = SchedulePeriod.Week;
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the access key for the page.</summary>
|
||||
public Access { get; set; }
|
||||
|
||||
protected override Entity Apply(Entity entity, int number, Instant instant)
|
||||
{
|
||||
var model = entity.Get<PageModel>();
|
||||
model.Access = this.Access;
|
||||
return entity.Set(instant, model);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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+)
|
||||
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])
|
||||
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.
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue