Compare commits

...

30 commits
v0.2.0 ... main

Author SHA1 Message Date
D. Moonfire 5a993e85cb fix(html): added validatiors to Autofac module
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-21 21:49:41 -06:00
D. Moonfire aac4b4373d feat(html): added identify operations
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-21 21:14:36 -06:00
D. Moonfire 22ddae11f8 fix(temporal): added CurrentInstant and CurrentDateTime to TimeService
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-21 17:48:39 -06:00
D. Moonfire b32ca7582c fix(schedules): protect against null schedules 2023-01-21 12:13:09 -06:00
D. Moonfire 07eb12414a refactor(temporal)!: renamed Timekeeper to TimeService
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-21 01:52:52 -06:00
D. Moonfire 82e1bc3c28 feat(schedules)!: reworked schedule names and added a new style
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-20 23:28:41 -06:00
D. Moonfire 070cf2bfb8 fix(scheduler): added better error messages and regular expression for numerical paths
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-18 23:51:37 -06:00
D. Moonfire 185980b5c4 fix(schedules): corrected serialization of pathRegex
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-18 22:32:54 -06:00
D. Moonfire 2e93ebdb7c ci: add manual to the release process
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-18 15:52:39 -06:00
D. Moonfire bca501d4e5 ci: updating to not create 1.0.0 packages
Some checks failed
ci/woodpecker/manual/woodpecker Pipeline failed
2023-01-18 15:49:51 -06:00
D. Moonfire 189273692c feat(schedules): switched how periods were parsed to allow for "2 weeks"
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-18 14:29:11 -06:00
D. Moonfire 2892ec3445 feat!: added cancellation token support to pipelines and operations
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-17 19:24:09 -06:00
D. Moonfire 08aafb144c feat(schedules): changed ApplySchedules.Schedules to a getter GetSchedules 2023-01-17 18:42:31 -06:00
D. Moonfire fc1ab22a0e build: no prettier left
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-16 22:13:58 -06:00
D. Moonfire d5b975c179 feat(schedules): implemented the basic schedule
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-01-16 22:10:24 -06:00
D. Moonfire c73805ae93 chore: fixed a reference to Gallium 2023-01-16 22:09:06 -06:00
D. Moonfire edda9a2773 refactor(temporal)!: cleaned up organization 2023-01-16 13:45:59 -06:00
D. Moonfire e02c56e77e feat: implemented SingletonComponent to wrap most of the Is* components 2023-01-16 12:38:29 -06:00
D. Moonfire a5694d0cee refactor!: moving generator attributes into Nitride.Generators namespace 2023-01-15 14:08:58 -06:00
D. Moonfire 0a36d70fb0 feat: implemented a JSON project as a variant of YAML 2023-01-15 13:57:43 -06:00
D. Moonfire c5c9b8bf9c refactor!: renamed GetText to GetTextContentString 2023-01-15 12:43:42 -06:00
D. Moonfire a8c6d0e582 feat: made PipelineBase.AddDependency be a params 2023-01-15 12:20:41 -06:00
D. Moonfire 084aa7e812 build: removed prettier and formatting in lefthook 2023-01-15 12:20:13 -06:00
D. Moonfire 7d388b09c2 chore: lowered verbosity on some pipeline operations
closes #2
2023-01-14 18:30:22 -06:00
D. Moonfire 9e93eb6ce6 refactor!: fixed missed namespaces
- reformatted code and cleaned up references
2023-01-14 18:19:42 -06:00
D. Moonfire 6a397f5284 build: removed old git versions
Some checks failed
ci/woodpecker/tag/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline failed
2022-12-24 00:47:57 -06:00
D. Moonfire 0011dc715b refactor: cleaning up file 2022-12-24 00:41:34 -06:00
D. Moonfire 982e3b38c3 feat(markdown): implemented expanding single link list items
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline failed
2022-12-24 00:38:24 -06:00
D. Moonfire 16a888fe9e chore: adding a markdown test project 2022-11-02 20:46:15 -05:00
D. Moonfire 8a5967a05a feat: updating package dependencies 2022-09-06 17:50:34 -05:00
191 changed files with 3684 additions and 756 deletions

3
.envrc
View file

@ -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)))

View file

@ -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

View file

@ -45,6 +45,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Nitride.Slugs.Tests
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Nitride.Temporal.Tests", "tests\MfGames.Nitride.Temporal.Tests\MfGames.Nitride.Temporal.Tests.csproj", "{0B74A4DE-4F92-44EE-8273-E5A15EAB4266}"
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
@ -274,6 +284,66 @@ Global
{0B74A4DE-4F92-44EE-8273-E5A15EAB4266}.Release|x64.Build.0 = Release|Any CPU
{0B74A4DE-4F92-44EE-8273-E5A15EAB4266}.Release|x86.ActiveCfg = Release|Any CPU
{0B74A4DE-4F92-44EE-8273-E5A15EAB4266}.Release|x86.Build.0 = Release|Any CPU
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Debug|x64.ActiveCfg = Debug|Any CPU
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Debug|x64.Build.0 = Debug|Any CPU
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Debug|x86.ActiveCfg = Debug|Any CPU
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Debug|x86.Build.0 = Debug|Any CPU
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Release|Any CPU.Build.0 = Release|Any CPU
{2AAE2B69-A93D-4045-B7E6-A32ED08D0D65}.Release|x64.ActiveCfg = Release|Any CPU
{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}
@ -294,5 +364,10 @@ Global
{2C92A626-7A14-4FDB-906B-E7FA5FF18CC1} = {47461A29-E502-4B0E-AAF5-D87C4B93AB6D}
{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

View file

@ -1371,5 +1371,7 @@ using(DataAccessAdapter dataAccessAdapter = new DataAccessAdapter(ConnectionStri
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F87CBA43E9CDCC41A45B39A2A2A25764/Scope/=2C285F182AC98D44B0B4F29D4D2149EC/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F87CBA43E9CDCC41A45B39A2A2A25764/Scope/=2C285F182AC98D44B0B4F29D4D2149EC/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F87CBA43E9CDCC41A45B39A2A2A25764/Scope/=2C285F182AC98D44B0B4F29D4D2149EC/Type/@EntryValue">InCSharpStatement</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gemtext/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tocks/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

View file

@ -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">

View file

@ -13,14 +13,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.4.4"/>
<PackageReference Include="CliWrap" Version="3.5.0"/>
<PackageReference Include="JunitXml.TestLogger" Version="3.0.114"/>
<PackageReference Include="xunit" Version="2.4.1"/>
<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.3"/>
<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>

View file

@ -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

View file

@ -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);

View file

@ -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");

View file

@ -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": {

View file

@ -15,7 +15,6 @@
pkgs.dotnet-sdk
pkgs.lefthook
pkgs.convco
pkgs.nodePackages.prettier
pkgs.nixfmt
pkgs.jq
];

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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]"

View file

@ -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);

View file

@ -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();
}

View file

@ -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.2.0" />
<PackageReference Include="Ical.Net" Version="4.2.0" />
<PackageReference Include="NodaTime" Version="3.1.0" />
<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 -->

View file

@ -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>());
}
}

View file

@ -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);

View file

@ -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();
}

View file

@ -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.2.0" />
<PackageReference Include="NodaTime" Version="3.1.0" />
<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 -->

View file

@ -1,6 +1,6 @@
using Autofac;
using MfGames.Nitride.Temporal;
using MfGames.Nitride.Temporal.Setup;
namespace MfGames.Nitride.Feeds;

View file

@ -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;

View file

@ -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)
{

View file

@ -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;

View file

@ -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)));
}
}
}

View file

@ -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/";
}

View file

@ -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();
}

View file

@ -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>());
}
}

View file

@ -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.

View file

@ -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);
}

View file

@ -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,

View file

@ -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));
}
}

View file

@ -10,10 +10,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MfGames.Gallium" Version="0.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.1.0" />
<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>

View file

@ -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);
}
}

View file

@ -0,0 +1,13 @@
using Microsoft.CodeAnalysis;
namespace MfGames.Nitride.Generators;
public class SingletonComponentSyntaxReceiver : ClassAttributeSyntaxReceiverBase
{
/// <inheritdoc />
public SingletonComponentSyntaxReceiver(
GeneratorInitializationContext context)
: base(context, "SingletonComponent")
{
}
}

View file

@ -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(" }");
}

View file

@ -0,0 +1,12 @@
using Microsoft.CodeAnalysis;
namespace MfGames.Nitride.Generators;
public class WithPropertiesSyntaxReceiver : ClassAttributeSyntaxReceiverBase
{
/// <inheritdoc />
public WithPropertiesSyntaxReceiver(GeneratorInitializationContext context)
: base(context, "WithProperties")
{
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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}}}}}";

View file

@ -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);
}
}

View file

@ -2,7 +2,8 @@ using FluentValidation;
namespace MfGames.Nitride.Handlebars;
public class IdentifyHandlebarsFromComponentValidator : AbstractValidator<IdentifyHandlebarsFromComponent>
public class IdentifyHandlebarsFromComponentValidator
: AbstractValidator<IdentifyHandlebarsFromComponent>
{
public IdentifyHandlebarsFromComponentValidator()
{

View file

@ -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("}}"))
{

View file

@ -10,15 +10,15 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="6.4.0" />
<PackageReference Include="MfGames.Gallium" Version="0.2.0" />
<PackageReference Include="Handlebars.Net" Version="2.1.2" />
<PackageReference Include="NodaTime.Testing" Version="3.1.0" />
<PackageReference Include="Open.Threading" Version="2.2.0" />
<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 -->

View file

@ -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(

View file

@ -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)
{

View file

@ -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!);

View file

@ -2,7 +2,8 @@ using FluentValidation;
namespace MfGames.Nitride.Handlebars;
public class RenderContentTemplateValidator : AbstractValidator<RenderContentTemplate>
public class RenderContentTemplateValidator
: AbstractValidator<RenderContentTemplate>
{
public RenderContentTemplateValidator()
{

View file

@ -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);

View 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;
}
}

View 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,
};
}
}

View file

@ -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();
}

View file

@ -25,7 +25,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MfGames.Gallium" Version="0.2.0" />
<PackageReference Include="MfGames.Gallium" Version="0.4.0" />
</ItemGroup>
</Project>

View file

@ -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>());
}
}

View file

@ -8,5 +8,6 @@ public class NitrideHtmlModule : Module
protected override void Load(ContainerBuilder builder)
{
builder.RegisterOperators(this);
builder.RegisterValidators(this);
}
}

View 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();
}
}

View file

@ -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 />

View file

@ -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);

View file

@ -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();

View file

@ -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)
{

View file

@ -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);
}

View file

@ -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.1.0" />
<PackageReference Include="MfGames.Gallium" Version="0.2.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 -->

View file

@ -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>());
}
}

View file

@ -1,9 +1,9 @@
using System.Collections.Generic;
using MfGames.Gallium;
using MAB.DotIgnore;
using MfGames.Gallium;
using Zio;
namespace MfGames.Nitride.IO;

View file

@ -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);
}

View file

@ -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);

View file

@ -2,7 +2,8 @@ using FluentValidation;
namespace MfGames.Nitride.IO.Paths;
public class ChangePathExtensionValidator : AbstractValidator<ChangePathExtension>
public class ChangePathExtensionValidator
: AbstractValidator<ChangePathExtension>
{
public ChangePathExtensionValidator()
{

View file

@ -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() };
}
}

View file

@ -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!)
{

View file

@ -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);

View file

@ -1,8 +1,10 @@
using System.Collections.Generic;
using System.Threading;
using FluentValidation;
using MfGames.Gallium;
using MfGames.Nitride.Generators;
using Zio;
@ -31,11 +33,14 @@ 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);
return this.replacePath.WithReplacement(this.RunReplacement)
return this.replacePath
.WithReplacement(this.RunReplacement)
.Run(input);
}

View file

@ -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);

View file

@ -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

View 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
{
}

View 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>

View 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>());
}
}

View 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);
}
}

View 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);
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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,
};
}
}

View file

@ -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();
}

View file

@ -0,0 +1,103 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using MfGames.Gallium;
using MfGames.Nitride.Contents;
using MfGames.Nitride.Slugs;
namespace MfGames.Nitride.Markdown;
/// <summary>
/// An operation that turns list items that starts with a link into a single
/// link for the entire list item, even if there are additional links in the
/// list.
/// </summary>
public class MakeSingleLinkListItems : IOperation
{
private readonly ISlugConverter slugs;
public MakeSingleLinkListItems(ISlugConverter slugs)
{
this.slugs = slugs;
}
/// <inheritdoc />
public IEnumerable<Entity> Run(
IEnumerable<Entity> input,
CancellationToken cancellationToken = default)
{
return input
.SelectManyEntity<IsMarkdown>(
x => x
.Select(this.MakeSingleLinkLists));
}
/// <summary>
/// This turns all links that start with a link into a single link while
/// removing all trailing links within the line. This is to simplify the
/// rendering of the link on page.
/// </summary>
private Entity MakeSingleLinkLists(Entity entity)
{
string content = entity.GetTextContentString()!;
string output = Regex.Replace(
content,
@"- \[\[(?<label>[^\]]+?)\]\](?<post>[^\n]+)\n",
match =>
{
string wiki = match.Groups["label"].ToString();
string path = string.Format(
"/{0}/",
this.slugs.ToSlug(wiki.Split('|').First()));
string label = wiki.Split('|').Last();
string after = match.Groups["post"].ToString();
string post = this.RemoveLinks(after);
string value = $"- [{label}{post}]({path})\n";
return value;
});
output = Regex.Replace(
output,
@"- \[(?<label>[^\]]+?)\]\((?<path>[^\)]+?)\)(?<post>[^\n]+)\n",
match =>
{
string label = match.Groups["label"].ToString();
string path = match.Groups["path"].ToString();
string after = match.Groups["post"].ToString();
string post = this.RemoveLinks(after);
string value = $"- [{label}{post}]({path})\n";
return value;
});
return entity.SetTextContent(output);
}
/// <summary>
/// Removes a normal link and replaces it with the text.
/// </summary>
private string RemoveLinks(string input)
{
string output = Regex.Replace(
input,
@"\[\[(?<label>[^\]]+)\]\]",
match =>
{
string link1 = match.Groups["label"].ToString();
string label1 = link1.Split('|').Last();
return label1;
});
output = Regex.Replace(
output,
@"\[(?<label>[^\]]+)\]\([^)]+\)",
match => match.Groups["label"].ToString());
return output;
}
}

View file

@ -12,13 +12,14 @@
<ItemGroup>
<ProjectReference Include="..\MfGames.Nitride.Gemtext\MfGames.Nitride.Gemtext.csproj" />
<ProjectReference Include="..\MfGames.Nitride.Html\MfGames.Nitride.Html.csproj" />
<ProjectReference Include="..\MfGames.Nitride.Slugs\MfGames.Nitride.Slugs.csproj" />
<ProjectReference Include="..\MfGames.Nitride\MfGames.Nitride.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MfGames.Gallium" Version="0.2.0" />
<PackageReference Include="Markdig" Version="0.30.2" />
<PackageReference Include="MfGames.Markdown.Gemtext" Version="1.2.1" />
<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" />
</ItemGroup>

View file

@ -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>());
}
}

View file

@ -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()
{

View file

@ -1,6 +1,6 @@
using FluentValidation;
namespace MfGames.Nitride.Markdown;
namespace MfGames.Nitride.Markdown.Validators;
public class IdentifyMarkdownValidator : AbstractValidator<IdentifyMarkdown>
{

View file

@ -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);
}

View file

@ -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);

View 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;
}
}

View 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);
}

View file

@ -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);
}
}

View 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);
}
}

View file

@ -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>

View 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);
}
}

View file

@ -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);
}
}

Some files were not shown because too many files have changed in this diff Show more