feat: initial release
This commit is contained in:
commit
78054ee2a7
164 changed files with 22055 additions and 0 deletions
122
.editorconfig
Normal file
122
.editorconfig
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset=utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline=true
|
||||||
|
indent_style=space
|
||||||
|
indent_size=4
|
||||||
|
|
||||||
|
# Microsoft .NET properties
|
||||||
|
csharp_new_line_before_members_in_object_initializers=false
|
||||||
|
csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
|
||||||
|
csharp_space_after_cast=false
|
||||||
|
csharp_style_var_elsewhere=false:hint
|
||||||
|
csharp_style_var_for_built_in_types=false:hint
|
||||||
|
csharp_style_var_when_type_is_apparent=true:hint
|
||||||
|
csharp_preserve_single_line_statements=false
|
||||||
|
csharp_preserve_single_line_blocks=true
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members=true:hint
|
||||||
|
dotnet_style_predefined_type_for_member_access=true:hint
|
||||||
|
dotnet_style_qualification_for_event=true:hint
|
||||||
|
dotnet_style_qualification_for_field=true:hint
|
||||||
|
dotnet_style_qualification_for_method=true:hint
|
||||||
|
dotnet_style_qualification_for_property=true:hint
|
||||||
|
dotnet_style_require_accessibility_modifiers=for_non_interface_members:hint
|
||||||
|
|
||||||
|
# ReSharper properties
|
||||||
|
resharper_alignment_tab_fill_style=optimal_fill
|
||||||
|
resharper_apply_on_completion=true
|
||||||
|
resharper_blank_lines_after_control_transfer_statements=1
|
||||||
|
resharper_blank_lines_around_single_line_auto_property=1
|
||||||
|
resharper_blank_lines_around_single_line_property=1
|
||||||
|
resharper_blank_lines_before_single_line_comment=1
|
||||||
|
resharper_blank_lines_between_using_groups=1
|
||||||
|
resharper_braces_for_for=required
|
||||||
|
resharper_braces_for_foreach=required
|
||||||
|
resharper_braces_for_ifelse=required
|
||||||
|
resharper_braces_for_while=required
|
||||||
|
resharper_can_use_global_alias=false
|
||||||
|
resharper_csharp_blank_lines_around_single_line_field=1
|
||||||
|
resharper_csharp_blank_lines_around_single_line_invocable=1
|
||||||
|
resharper_csharp_indent_style=tab
|
||||||
|
resharper_csharp_insert_final_newline=true
|
||||||
|
resharper_csharp_keep_blank_lines_in_code=1
|
||||||
|
resharper_csharp_keep_blank_lines_in_declarations=1
|
||||||
|
resharper_csharp_new_line_before_while=true
|
||||||
|
resharper_csharp_use_indent_from_vs=false
|
||||||
|
resharper_csharp_wrap_arguments_style=chop_if_long
|
||||||
|
resharper_csharp_wrap_extends_list_style=chop_if_long
|
||||||
|
resharper_csharp_wrap_parameters_style=chop_if_long
|
||||||
|
resharper_css_insert_final_newline=false
|
||||||
|
resharper_enforce_line_ending_style=true
|
||||||
|
resharper_html_insert_final_newline=false
|
||||||
|
resharper_indent_nested_fixed_stmt=true
|
||||||
|
resharper_js_indent_style=tab
|
||||||
|
resharper_js_insert_final_newline=true
|
||||||
|
resharper_js_keep_blank_lines_in_code=1
|
||||||
|
resharper_js_stick_comment=false
|
||||||
|
resharper_js_use_indent_from_vs=false
|
||||||
|
resharper_js_wrap_before_binary_opsign=true
|
||||||
|
resharper_js_wrap_chained_method_calls=chop_if_long
|
||||||
|
resharper_keep_blank_lines_between_declarations=1
|
||||||
|
resharper_min_blank_lines_after_imports=1
|
||||||
|
resharper_place_attribute_on_same_line=False
|
||||||
|
resharper_place_constructor_initializer_on_same_line=false
|
||||||
|
resharper_place_type_constraints_on_same_line=false
|
||||||
|
resharper_protobuf_insert_final_newline=false
|
||||||
|
resharper_qualified_using_at_nested_scope=true
|
||||||
|
resharper_resx_insert_final_newline=false
|
||||||
|
resharper_space_within_single_line_array_initializer_braces=true
|
||||||
|
resharper_use_indents_from_main_language_in_file=false
|
||||||
|
resharper_vb_insert_final_newline=false
|
||||||
|
resharper_wrap_after_declaration_lpar=true
|
||||||
|
resharper_wrap_after_invocation_lpar=true
|
||||||
|
resharper_wrap_before_extends_colon=true
|
||||||
|
resharper_wrap_before_first_type_parameter_constraint=true
|
||||||
|
resharper_wrap_before_type_parameter_langle=true
|
||||||
|
resharper_xmldoc_indent_child_elements=ZeroIndent
|
||||||
|
resharper_xmldoc_indent_text=ZeroIndent
|
||||||
|
resharper_xmldoc_insert_final_newline=false
|
||||||
|
resharper_xml_insert_final_newline=false
|
||||||
|
|
||||||
|
# ReSharper inspection severities
|
||||||
|
resharper_check_namespace_highlighting=none
|
||||||
|
resharper_convert_to_auto_property_highlighting=none
|
||||||
|
resharper_localizable_element_highlighting=none
|
||||||
|
resharper_redundant_comma_in_attribute_list_highlighting=none
|
||||||
|
resharper_redundant_comma_in_enum_declaration_highlighting=none
|
||||||
|
resharper_redundant_comma_in_initializer_highlighting=none
|
||||||
|
resharper_string_compare_to_is_culture_specific_highlighting=none
|
||||||
|
resharper_string_index_of_is_culture_specific_1_highlighting=none
|
||||||
|
resharper_use_null_propagation_highlighting=none
|
||||||
|
resharper_use_object_or_collection_initializer_highlighting=hint
|
||||||
|
resharper_use_string_interpolation_highlighting=hint
|
||||||
|
|
||||||
|
# Matches the exact files either package.json or .travis.yml
|
||||||
|
[{package.json,.travis.yml}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
tab_width=2
|
||||||
|
|
||||||
|
[*.{cs,js,json,jsx,proto,resjson,ts,tsx}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=space
|
||||||
|
tab_width=4
|
||||||
|
|
||||||
|
[*.{asax,ascx,aspx,cshtml,css,htm,html,master,razor,skin,vb,xaml,xamlx,xoml}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=4
|
||||||
|
tab_width=4
|
||||||
|
|
||||||
|
[*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,proj,props,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
tab_width=2
|
||||||
|
|
||||||
|
[*.proto]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
tab_width=2
|
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use asdf
|
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
launchSettings.json
|
||||||
|
|
||||||
|
*~
|
||||||
|
*.user
|
||||||
|
Directory.Build.props
|
||||||
|
|
||||||
|
obj/
|
||||||
|
[Bb]in/
|
||||||
|
.vs/
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
_ReSharper.Caches/
|
||||||
|
node_modules/
|
51
.gitlab-ci.yml
Normal file
51
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
|
||||||
|
default:
|
||||||
|
before_script:
|
||||||
|
- curl -sL https://deb.nodesource.com/setup_15.x | bash -
|
||||||
|
- apt-get install -y nodejs
|
||||||
|
|
||||||
|
build:
|
||||||
|
image: mcr.microsoft.com/dotnet/sdk:5.0
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
# Set up the environment.
|
||||||
|
- npx npm install --ci
|
||||||
|
- npx commitlint-gitlab-ci -x @commitlint/config-conventional
|
||||||
|
|
||||||
|
# Build and test everything.
|
||||||
|
- dotnet restore
|
||||||
|
- dotnet build
|
||||||
|
#- 'dotnet test --test-adapter-path:. --logger:"junit;LogFilePath=../artifacts/{assembly}-test-result.xml;MethodFormat=Default;FailureBodyFormat=Verbose" --collect:"XPlat Code Coverage"'
|
||||||
|
|
||||||
|
# Summarize the output for Gitlab CI reporting.
|
||||||
|
#- dotnet new tool-manifest
|
||||||
|
#- dotnet tool install dotnet-reportgenerator-globaltool
|
||||||
|
#- dotnet tool run reportgenerator -reports:src/*/TestResults/*/coverage.cobertura.xml -targetdir:./coverage "-reporttypes:Cobertura;TextSummary"
|
||||||
|
#- grep "Line coverage" coverage/Summary.txt
|
||||||
|
|
||||||
|
# Perform the release.
|
||||||
|
- npx semantic-release
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_TITLE =~ /^chore\(release\)/'
|
||||||
|
when: never
|
||||||
|
- if: '$CI_COMMIT_TAG'
|
||||||
|
when: never
|
||||||
|
- if: '$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
|
||||||
|
when: never
|
||||||
|
- when: on_success
|
||||||
|
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
paths:
|
||||||
|
- ./**/*test-result.xml
|
||||||
|
- ./coverage/Cobertura.xml
|
||||||
|
- ./coverage/Summary.*
|
||||||
|
- ./**/*.nupkg
|
||||||
|
reports:
|
||||||
|
junit:
|
||||||
|
- ./**/*test-result.xml
|
||||||
|
cobertura:
|
||||||
|
- ./coverage/Cobertura.xml
|
4
.husky/commit-msg
Executable file
4
.husky/commit-msg
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx --no-install commitlint --edit $1
|
3
.tool-versions
Normal file
3
.tool-versions
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
dotnet-core 5.0.100
|
||||||
|
yarn 1.22.10
|
||||||
|
nodejs 15.0.1
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) Moonfire Games
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
264
Nitride.sln
Normal file
264
Nitride.sln
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
VisualStudioVersion = 15.0.26124.0
|
||||||
|
MinimumVisualStudioVersion = 15.0.26124.0
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{570184FD-ECE4-4EC8-86E1-C1265E17D647}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Calendar", "src\Nitride.Calendar\Nitride.Calendar.csproj", "{D480943C-764D-4A8A-B546-642ED10586BB}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Feeds", "src\Nitride.Feeds\Nitride.Feeds.csproj", "{1204DECC-654A-433A-BF82-53F98AB24DCF}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Gemtext", "src\Nitride.Gemtext\Nitride.Gemtext.csproj", "{23C7CBF7-9624-457A-8296-C03F75BC9BC6}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Generators", "src\Nitride.Generators\Nitride.Generators.csproj", "{4ACB11B7-1EEB-48E7-845A-528770839125}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Handlebars", "src\Nitride.Handlebars\Nitride.Handlebars.csproj", "{56E595A6-7880-416E-B328-93B9617F92A6}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Html", "src\Nitride.Html\Nitride.Html.csproj", "{3F9292D3-DA50-4DBA-AE90-E33E462470A1}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.IO", "src\Nitride.IO\Nitride.IO.csproj", "{534BF940-25B2-4948-A101-7890CC9C4EA5}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.IO.Tests", "src\Nitride.IO.Tests\Nitride.IO.Tests.csproj", "{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Javascript", "src\Nitride.Javascript\Nitride.Javascript.csproj", "{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Markdown", "src\Nitride.Markdown\Nitride.Markdown.csproj", "{41FF3823-7008-43B1-AD6A-92437E0600B7}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride", "src\Nitride\Nitride.csproj", "{757BA115-3465-46C5-ADDB-7B96D6900F33}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Slugs", "src\Nitride.Slugs\Nitride.Slugs.csproj", "{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Temporal", "src\Nitride.Temporal\Nitride.Temporal.csproj", "{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Tests", "src\Nitride.Tests\Nitride.Tests.csproj", "{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Yaml", "src\Nitride.Yaml\Nitride.Yaml.csproj", "{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitride.Yaml.Tests", "src\Nitride.Yaml.Tests\Nitride.Yaml.Tests.csproj", "{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{D480943C-764D-4A8A-B546-642ED10586BB} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{1204DECC-654A-433A-BF82-53F98AB24DCF} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{23C7CBF7-9624-457A-8296-C03F75BC9BC6} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{4ACB11B7-1EEB-48E7-845A-528770839125} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{56E595A6-7880-416E-B328-93B9617F92A6} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{3F9292D3-DA50-4DBA-AE90-E33E462470A1} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{534BF940-25B2-4948-A101-7890CC9C4EA5} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{57F4C4C3-A2C9-4D6C-AA51-9A7EC926918D} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{285A855E-B2FC-4770-AFD9-4CC67AD3C83C} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{41FF3823-7008-43B1-AD6A-92437E0600B7} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{757BA115-3465-46C5-ADDB-7B96D6900F33} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{17BF2A03-2C1D-4F75-9C18-B4341FAAF1A5} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{FEFFA469-245E-45A7-A094-3F0E89CEF3A9} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{A0EED899-35C7-4C1C-8BBF-F7FD2F281839} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{1A5B4B4D-CF32-4458-8F2C-83A2AE494837} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
{C8CD60C2-2A26-48AE-848D-A71AEE2267C9} = {570184FD-ECE4-4EC8-86E1-C1265E17D647}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
3
Nitride.sln.DotSettings
Normal file
3
Nitride.sln.DotSettings
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Gemtext/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Markdig/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
1370
Nitride.sln.Dotsettings
Normal file
1370
Nitride.sln.Dotsettings
Normal file
File diff suppressed because it is too large
Load diff
7
NuGet.Config
Normal file
7
NuGet.Config
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||||
|
<add key="mfgames" value="https://www.myget.org/F/mfgames/api/v3/index.json" protocolVersion="3" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
4
README.md
Normal file
4
README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Nitride CIL
|
||||||
|
===========
|
||||||
|
|
||||||
|
A static site generator based on the [Gallium ECS](https://gitlab.com/mfgames-cil/gallium-cil/).
|
3
commitlint.config.js
Normal file
3
commitlint.config.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: ["@commitlint/config-conventional"],
|
||||||
|
};
|
12304
package-lock.json
generated
Normal file
12304
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
21
package.json
Normal file
21
package.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "nitride-cil",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"prepare": "husky install"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^13.1.0",
|
||||||
|
"@commitlint/config-conventional": "^13.1.0",
|
||||||
|
"@semantic-release/changelog": "^5.0.1",
|
||||||
|
"@semantic-release/git": "^9.0.0",
|
||||||
|
"@semantic-release/gitlab": "^6.2.2",
|
||||||
|
"@semantic-release/npm": "^7.1.3",
|
||||||
|
"commitlint-gitlab-ci": "^0.0.4",
|
||||||
|
"husky": "^7.0.2",
|
||||||
|
"semantic-release": "^17.4.7",
|
||||||
|
"semantic-release-dotnet": "^1.0.0",
|
||||||
|
"semantic-release-nuget": "^1.1.0"
|
||||||
|
}
|
||||||
|
}
|
20
release.config.js
Normal file
20
release.config.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
module.exports = {
|
||||||
|
branches: ["main"],
|
||||||
|
message: "chore(release): v${nextRelease.version}\n\n${nextRelease.notes}",
|
||||||
|
plugins: [
|
||||||
|
"@semantic-release/commit-analyzer",
|
||||||
|
"@semantic-release/release-notes-generator",
|
||||||
|
"@semantic-release/npm",
|
||||||
|
"semantic-release-dotnet",
|
||||||
|
[
|
||||||
|
"semantic-release-nuget",
|
||||||
|
{
|
||||||
|
packArguments: ["--include-symbols", "--include-source"],
|
||||||
|
pushFiles: ["src/*/bin/Debug/*.nupkg"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@semantic-release/changelog",
|
||||||
|
"@semantic-release/git",
|
||||||
|
"@semantic-release/gitlab",
|
||||||
|
],
|
||||||
|
};
|
14
src/Directory.Build.props
Normal file
14
src/Directory.Build.props
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<Project>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<Authors>Dylan Moonfire</Authors>
|
||||||
|
<Company>Moonfire Games</Company>
|
||||||
|
<RepositoryUrl>https://gitlab.com/mfgames-cil/nitride-cil</RepositoryUrl>
|
||||||
|
<RepositoryType>Git</RepositoryType>
|
||||||
|
<PackageTags>nitride</PackageTags>
|
||||||
|
<PackageProjectUrl>https://gitlab.com/mfgames-cil/nitride-cil</PackageProjectUrl>
|
||||||
|
<PackageLicense>MIT</PackageLicense>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
104
src/Nitride.Calendar/CreateCalender.cs
Normal file
104
src/Nitride.Calendar/CreateCalender.cs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Gallium;
|
||||||
|
using Ical.Net.CalendarComponents;
|
||||||
|
using Ical.Net.DataTypes;
|
||||||
|
using Ical.Net.Serialization;
|
||||||
|
using Nitride.Contents;
|
||||||
|
using Nitride.Temporal;
|
||||||
|
using NodaTime;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.Calendar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an iCalendar file from all the entities passed into the method
|
||||||
|
/// that have a NodaTime.Instant component. This will write both past and
|
||||||
|
/// future events.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class CreateCalender : NitrideOperationBase
|
||||||
|
{
|
||||||
|
private readonly NitrideClock clock;
|
||||||
|
|
||||||
|
public CreateCalender(NitrideClock clock)
|
||||||
|
{
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a callback to get the summary of the event representing
|
||||||
|
/// the entity.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, string>? GetEventSummary { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a callback to get the optional URL of an event for
|
||||||
|
/// the entity.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, Uri?>? GetEventUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the file system path for the resulting calendar.
|
||||||
|
/// </summary>
|
||||||
|
public UPath? Path { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
this.CheckNotNull(x => x.Path);
|
||||||
|
this.CheckNotNull(x => x.GetEventSummary);
|
||||||
|
|
||||||
|
IEnumerable<Entity> output = input
|
||||||
|
.ForEntities<Instant>(this.CreateCalendarEntity);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
calendar.TimeZones.Add(new VTimeZone(this.clock.DateTimeZone.Id));
|
||||||
|
|
||||||
|
// Go through the events and add all of them.
|
||||||
|
List<Entity> input = entities.ToList();
|
||||||
|
IEnumerable<CalendarEvent> events =
|
||||||
|
input.Select(this.CreateCalendarEvent);
|
||||||
|
|
||||||
|
calendar.Events.AddRange(events);
|
||||||
|
|
||||||
|
// Create the iCalendar file.
|
||||||
|
var serializer = new CalendarSerializer();
|
||||||
|
string serializedCalendar = serializer.SerializeToString(calendar);
|
||||||
|
|
||||||
|
// Create the calendar entity and populate everything.
|
||||||
|
Entity calendarEntity = new Entity()
|
||||||
|
.Set(IsCalendar.Instance)
|
||||||
|
.Set(this.Path!.Value)
|
||||||
|
.SetTextContent(serializedCalendar);
|
||||||
|
|
||||||
|
// Return the results along with the new calendar.
|
||||||
|
return input.Union(new[] { calendarEntity });
|
||||||
|
}
|
||||||
|
|
||||||
|
private CalendarEvent CreateCalendarEvent(Entity entity)
|
||||||
|
{
|
||||||
|
var instant = entity.Get<Instant>();
|
||||||
|
var when = this.clock.ToDateTime(instant);
|
||||||
|
string summary = this.GetEventSummary!(entity);
|
||||||
|
Uri? url = this.GetEventUrl?.Invoke(entity);
|
||||||
|
var calendarEvent = new CalendarEvent
|
||||||
|
{
|
||||||
|
Summary = summary,
|
||||||
|
Start = new CalDateTime(when),
|
||||||
|
Url = url,
|
||||||
|
};
|
||||||
|
|
||||||
|
return calendarEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/Nitride.Calendar/IsCalendar.cs
Normal file
10
src/Nitride.Calendar/IsCalendar.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Nitride.Calendar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A marker component for identifying an entity that represents a calendar.
|
||||||
|
/// </summary>
|
||||||
|
public class IsCalendar
|
||||||
|
{
|
||||||
|
public static IsCalendar Instance { get; } = new();
|
||||||
|
}
|
||||||
|
}
|
37
src/Nitride.Calendar/Nitride.Calendar.csproj
Normal file
37
src/Nitride.Calendar/Nitride.Calendar.csproj
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>An extension to Nitride static site generator to generate iCalendar files.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.IO\Nitride.IO.csproj" />
|
||||||
|
<ProjectReference Include="..\Nitride.Temporal\Nitride.Temporal.csproj" />
|
||||||
|
<ProjectReference Include="..\Nitride\Nitride.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Gallium" Version="1.0.2" />
|
||||||
|
<PackageReference Include="Ical.Net" Version="4.2.0" />
|
||||||
|
<PackageReference Include="NodaTime" Version="3.0.5" />
|
||||||
|
<PackageReference Include="Zio" Version="0.12.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the source generator -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.Generators\Nitride.Generators.csproj">
|
||||||
|
<OutputItemType>Analyzer</OutputItemType>
|
||||||
|
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
16
src/Nitride.Calendar/NitrideCalendarBuilderExtensions.cs
Normal file
16
src/Nitride.Calendar/NitrideCalendarBuilderExtensions.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using Autofac;
|
||||||
|
using Nitride.Temporal;
|
||||||
|
|
||||||
|
namespace Nitride.Calendar
|
||||||
|
{
|
||||||
|
public static class NitrideCalendarBuilderExtensions
|
||||||
|
{
|
||||||
|
public static NitrideBuilder UseCalendar(this NitrideBuilder builder)
|
||||||
|
{
|
||||||
|
return builder
|
||||||
|
.UseTemporal()
|
||||||
|
.ConfigureContainer(
|
||||||
|
x => x.RegisterModule<NitrideCalendarModule>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/Nitride.Calendar/NitrideCalendarModule.cs
Normal file
6
src/Nitride.Calendar/NitrideCalendarModule.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Nitride.Calendar
|
||||||
|
{
|
||||||
|
public class NitrideCalendarModule : NitrideModuleBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
123
src/Nitride.Feeds/CreateAtomFeeds.cs
Normal file
123
src/Nitride.Feeds/CreateAtomFeeds.cs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Gallium;
|
||||||
|
using Nitride.Contents;
|
||||||
|
using Nitride.Feeds.Structure;
|
||||||
|
using NodaTime;
|
||||||
|
using Serilog;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.Feeds
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates various feeds from the given input.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class CreateAtomFeeds : NitrideOperationBase
|
||||||
|
{
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
public CreateAtomFeeds(ILogger logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.GetAlternateMimeType = _ => "text/html";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the base URL for all the links.
|
||||||
|
/// </summary>
|
||||||
|
public string? BaseUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the alternate MIME type.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, string> GetAlternateMimeType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the alternate URL associated with the feed.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, Uri>? GetAlternateUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the callback to get the author for the feed.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, AtomAuthor>? GetAuthor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the callback to get the entries associated with the
|
||||||
|
/// feed.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, IEnumerable<AtomEntry>>? GetEntries { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier (typically a URL) of the feed.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, string>? GetId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the callback to get the path of the generated feed.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, UPath>? GetPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the rights (license) of the feed.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, string>? GetRights { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A callback that gets the title of the feed from the given entity.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, string>? GetTitle { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the updated timestamp for the feed.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, Instant>? GetUpdated { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the URL associated with the feed.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, Uri>? GetUrl { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
this.CheckNotNull(x => this.GetTitle);
|
||||||
|
this.CheckNotNull(x => this.GetPath);
|
||||||
|
this.CheckNotNull(x => this.GetEntries);
|
||||||
|
|
||||||
|
return input.SelectMany(this.CreateEntityFeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Entity> CreateEntityFeed(Entity entity)
|
||||||
|
{
|
||||||
|
// Create the top-level feed. All the nullable callbacks were
|
||||||
|
// verified in the function that calls this.
|
||||||
|
var feed = new AtomFeed()
|
||||||
|
.WithTitle(this.GetTitle?.Invoke(entity))
|
||||||
|
.WithId(this.GetId?.Invoke(entity))
|
||||||
|
.WithRights(this.GetRights?.Invoke(entity))
|
||||||
|
.WithUpdated(this.GetUpdated?.Invoke(entity))
|
||||||
|
.WithUrl(this.GetUrl?.Invoke(entity))
|
||||||
|
.WithAlternateUrl(this.GetAlternateUrl?.Invoke(entity))
|
||||||
|
.WithAlternateMimeType(this.GetAlternateMimeType.Invoke(entity))
|
||||||
|
.WithAuthor(this.GetAuthor?.Invoke(entity))
|
||||||
|
.ToXElement();
|
||||||
|
|
||||||
|
// Go through all the items inside the feed and add them.
|
||||||
|
foreach (var entry in this.GetEntries!(entity))
|
||||||
|
{
|
||||||
|
feed.Add(entry.ToXElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the feed entity and return both objects.
|
||||||
|
Entity feedEntity = new Entity()
|
||||||
|
.Set(IsFeed.Instance)
|
||||||
|
.Set(this.GetPath!(entity))
|
||||||
|
.SetTextContent(feed + "\n");
|
||||||
|
|
||||||
|
return new[] { entity, feedEntity };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/Nitride.Feeds/HasFeed.cs
Normal file
15
src/Nitride.Feeds/HasFeed.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
namespace Nitride.Feeds
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A marker component that indicates this entity has a feed associated with
|
||||||
|
/// it.
|
||||||
|
/// </summary>
|
||||||
|
public class HasFeed
|
||||||
|
{
|
||||||
|
public HasFeed()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HasFeed Instance { get; } = new();
|
||||||
|
}
|
||||||
|
}
|
14
src/Nitride.Feeds/IsFeed.cs
Normal file
14
src/Nitride.Feeds/IsFeed.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
namespace Nitride.Feeds
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A marker component that indicates this page is a feed.
|
||||||
|
/// </summary>
|
||||||
|
public class IsFeed
|
||||||
|
{
|
||||||
|
public IsFeed()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IsFeed Instance { get; } = new();
|
||||||
|
}
|
||||||
|
}
|
36
src/Nitride.Feeds/Nitride.Feeds.csproj
Normal file
36
src/Nitride.Feeds/Nitride.Feeds.csproj
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>An extension to Nitride static site generator to generate Atom feeds.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.IO\Nitride.IO.csproj" />
|
||||||
|
<ProjectReference Include="..\Nitride.Temporal\Nitride.Temporal.csproj" />
|
||||||
|
<ProjectReference Include="..\Nitride\Nitride.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Gallium" Version="1.0.2" />
|
||||||
|
<PackageReference Include="NodaTime" Version="3.0.5" />
|
||||||
|
<PackageReference Include="Zio" Version="0.12.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the source generator -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.Generators\Nitride.Generators.csproj">
|
||||||
|
<OutputItemType>Analyzer</OutputItemType>
|
||||||
|
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
16
src/Nitride.Feeds/NitrideFeedsBuilderExtensions.cs
Normal file
16
src/Nitride.Feeds/NitrideFeedsBuilderExtensions.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using Autofac;
|
||||||
|
using Nitride.Temporal;
|
||||||
|
|
||||||
|
namespace Nitride.Feeds
|
||||||
|
{
|
||||||
|
public static class NitrideFeedsBuilderExtensions
|
||||||
|
{
|
||||||
|
public static NitrideBuilder UseFeeds(this NitrideBuilder builder)
|
||||||
|
{
|
||||||
|
return builder
|
||||||
|
.UseTemporal()
|
||||||
|
.ConfigureContainer(
|
||||||
|
x => x.RegisterModule<NitrideFeedsModule>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/Nitride.Feeds/NitrideFeedsModule.cs
Normal file
6
src/Nitride.Feeds/NitrideFeedsModule.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Nitride.Feeds
|
||||||
|
{
|
||||||
|
public class NitrideFeedsModule : NitrideModuleBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
41
src/Nitride.Feeds/Structure/AtomAuthor.cs
Normal file
41
src/Nitride.Feeds/Structure/AtomAuthor.cs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace Nitride.Feeds.Structure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type-safe structure for an author element.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class AtomAuthor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the author.
|
||||||
|
/// </summary>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an XML element out of the feed along with all items inside
|
||||||
|
/// the feed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XElement? ToXElement()
|
||||||
|
{
|
||||||
|
if (this.Name == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var author = new XElement(XmlConstants.AtomNamespace + "author");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(this.Name))
|
||||||
|
{
|
||||||
|
author.Add(
|
||||||
|
new XElement(
|
||||||
|
XmlConstants.AtomNamespace + "name",
|
||||||
|
new XText(this.Name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
src/Nitride.Feeds/Structure/AtomCategory.cs
Normal file
57
src/Nitride.Feeds/Structure/AtomCategory.cs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
using System;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace Nitride.Feeds.Structure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type-safe structure for a entry's category element.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class AtomCategory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the label associated with the category.
|
||||||
|
/// </summary>
|
||||||
|
public string? Label { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the scheme associated with the category.
|
||||||
|
/// </summary>
|
||||||
|
public Uri? Scheme { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the term of the category.
|
||||||
|
/// </summary>
|
||||||
|
public string? Term { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an XML element out of the feed along with all items inside
|
||||||
|
/// the feed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XElement ToXElement()
|
||||||
|
{
|
||||||
|
if (this.Term == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException(
|
||||||
|
"Category term cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var elem = new XElement(
|
||||||
|
XmlConstants.AtomNamespace + "category",
|
||||||
|
new XAttribute("term", this.Term));
|
||||||
|
|
||||||
|
if (this.Scheme != null)
|
||||||
|
{
|
||||||
|
elem.Add(new XAttribute("scheme", this.Scheme.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(this.Label))
|
||||||
|
{
|
||||||
|
elem.Add(new XAttribute("label", this.Label));
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
src/Nitride.Feeds/Structure/AtomEntry.cs
Normal file
121
src/Nitride.Feeds/Structure/AtomEntry.cs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using NodaTime;
|
||||||
|
using static Nitride.Feeds.Structure.XmlConstants;
|
||||||
|
|
||||||
|
namespace Nitride.Feeds.Structure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type-safe structure for an entry in the Atom feed.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class AtomEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the author for the feed.
|
||||||
|
/// </summary>
|
||||||
|
public AtomAuthor? Author { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the categories associated with this entry.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<AtomCategory>? Categories { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the content of the entry.
|
||||||
|
/// </summary>
|
||||||
|
public string? Content { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type of content (text, html) of the content.
|
||||||
|
/// </summary>
|
||||||
|
public string ContentType { get; set; } = "html";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ID of the feed.
|
||||||
|
/// </summary>
|
||||||
|
public string? Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the summary of the entry.
|
||||||
|
/// </summary>
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type of content (text, html) of the summary.
|
||||||
|
/// </summary>
|
||||||
|
public string SummaryType { get; set; } = "html";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the title of the Feed.
|
||||||
|
/// </summary>
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timestamp that the feed was updated.
|
||||||
|
/// </summary>
|
||||||
|
public Instant? Updated { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the URL associated with this feed.
|
||||||
|
/// </summary>
|
||||||
|
public Uri? Url { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an XML element out of the feed along with all items inside
|
||||||
|
/// the feed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XElement? ToXElement()
|
||||||
|
{
|
||||||
|
var elem = new XElement(AtomNamespace + "entry");
|
||||||
|
|
||||||
|
AtomHelper.AddIfSet(elem, "title", this.Title);
|
||||||
|
|
||||||
|
if (this.Url != null)
|
||||||
|
{
|
||||||
|
elem.Add(
|
||||||
|
new XElement(
|
||||||
|
AtomNamespace + "link",
|
||||||
|
new XAttribute("rel", "alternate"),
|
||||||
|
new XAttribute("href", this.Url.ToString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomHelper.AddIfSet(
|
||||||
|
elem,
|
||||||
|
"updated",
|
||||||
|
this.Updated?.ToString("g", null));
|
||||||
|
AtomHelper.AddIfSet(elem, "id", this.Id);
|
||||||
|
AtomHelper.AddIfSet(elem, this.Author?.ToXElement());
|
||||||
|
|
||||||
|
if (this.Categories != null)
|
||||||
|
{
|
||||||
|
foreach (var category in this.Categories)
|
||||||
|
{
|
||||||
|
elem.Add(category.ToXElement());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(this.Summary))
|
||||||
|
{
|
||||||
|
elem.Add(
|
||||||
|
new XElement(
|
||||||
|
AtomNamespace + "summary",
|
||||||
|
new XAttribute("type", this.SummaryType),
|
||||||
|
new XText(this.Summary)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(this.Content))
|
||||||
|
{
|
||||||
|
elem.Add(
|
||||||
|
new XElement(
|
||||||
|
AtomNamespace + "content",
|
||||||
|
new XAttribute("type", this.ContentType),
|
||||||
|
new XText(this.Content)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
src/Nitride.Feeds/Structure/AtomFeed.cs
Normal file
104
src/Nitride.Feeds/Structure/AtomFeed.cs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
using System;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using NodaTime;
|
||||||
|
using static Nitride.Feeds.Structure.XmlConstants;
|
||||||
|
|
||||||
|
namespace Nitride.Feeds.Structure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type-safe structure of the top-level feed.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class AtomFeed
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the MIME type for the alternate URL.
|
||||||
|
/// </summary>
|
||||||
|
public string AlternateMimeType { get; set; } = "text/html";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the alternate URL for this feed.
|
||||||
|
/// </summary>
|
||||||
|
public Uri? AlternateUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the author for the feed.
|
||||||
|
/// </summary>
|
||||||
|
public AtomAuthor? Author { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ID of the feed.
|
||||||
|
/// </summary>
|
||||||
|
public string? Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the rights (license) of the feed.
|
||||||
|
/// </summary>
|
||||||
|
public string? Rights { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the title of the Feed.
|
||||||
|
/// </summary>
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timestamp that the feed was updated.
|
||||||
|
/// </summary>
|
||||||
|
public Instant? Updated { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the URL associated with this feed.
|
||||||
|
/// </summary>
|
||||||
|
public Uri? Url { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an XML element out of the feed along with all items inside
|
||||||
|
/// the feed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XElement ToXElement()
|
||||||
|
{
|
||||||
|
var elem = new XElement(AtomNamespace + "feed");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(this.Title))
|
||||||
|
{
|
||||||
|
elem.Add(
|
||||||
|
new XElement(
|
||||||
|
AtomNamespace + "title",
|
||||||
|
new XAttribute("type", "text"),
|
||||||
|
new XAttribute(XNamespace.Xml + "lang", "en"),
|
||||||
|
new XText(this.Title)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Url != null)
|
||||||
|
{
|
||||||
|
elem.Add(
|
||||||
|
new XElement(
|
||||||
|
AtomNamespace + "link",
|
||||||
|
new XAttribute("type", "application/atom+xml"),
|
||||||
|
new XAttribute("href", this.Url.ToString()),
|
||||||
|
new XAttribute("rel", "self")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.AlternateUrl != null)
|
||||||
|
{
|
||||||
|
elem.Add(
|
||||||
|
new XElement(
|
||||||
|
AtomNamespace + "link",
|
||||||
|
new XAttribute("type", this.AlternateMimeType),
|
||||||
|
new XAttribute("href", this.AlternateUrl.ToString()),
|
||||||
|
new XAttribute("rel", "alternate")));
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomHelper.AddIfSet(
|
||||||
|
elem,
|
||||||
|
"updated",
|
||||||
|
this.Updated?.ToString("g", null));
|
||||||
|
AtomHelper.AddIfSet(elem, "id", this.Id);
|
||||||
|
AtomHelper.AddIfSet(elem, this.Author?.ToXElement());
|
||||||
|
AtomHelper.AddIfSet(elem, "rights", this.Rights);
|
||||||
|
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/Nitride.Feeds/Structure/AtomHelper.cs
Normal file
29
src/Nitride.Feeds/Structure/AtomHelper.cs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace Nitride.Feeds.Structure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper methods for working with XML elements.
|
||||||
|
/// </summary>
|
||||||
|
public static class AtomHelper
|
||||||
|
{
|
||||||
|
public static void AddIfSet(XElement root, XElement? elem)
|
||||||
|
{
|
||||||
|
if (elem != null)
|
||||||
|
{
|
||||||
|
root.Add(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddIfSet(XElement elem, string name, string? text)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(text))
|
||||||
|
{
|
||||||
|
elem.Add(
|
||||||
|
new XElement(
|
||||||
|
XmlConstants.AtomNamespace + name,
|
||||||
|
new XText(text)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/Nitride.Feeds/Structure/XmlConstants.cs
Normal file
22
src/Nitride.Feeds/Structure/XmlConstants.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace Nitride.Feeds.Structure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Common constants used while generating feeds.
|
||||||
|
/// </summary>
|
||||||
|
public static class XmlConstants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The XML namespace for Atom feeds.
|
||||||
|
/// </summary>
|
||||||
|
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/";
|
||||||
|
}
|
||||||
|
}
|
11
src/Nitride.Gemtext/IsGemtext.cs
Normal file
11
src/Nitride.Gemtext/IsGemtext.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace Nitride.Gemtext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A marker component for indicating that an entity is Gemtext, the format
|
||||||
|
/// for text files using the Gemini protocol.
|
||||||
|
/// </summary>
|
||||||
|
public class IsGemtext
|
||||||
|
{
|
||||||
|
public static IsGemtext Instance { get; } = new IsGemtext();
|
||||||
|
}
|
||||||
|
}
|
27
src/Nitride.Gemtext/Nitride.Gemtext.csproj
Normal file
27
src/Nitride.Gemtext/Nitride.Gemtext.csproj
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>An extension to Nitride static site generator to generate Gemtext output.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride\Nitride.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the source generator -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.Generators\Nitride.Generators.csproj">
|
||||||
|
<OutputItemType>Analyzer</OutputItemType>
|
||||||
|
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
14
src/Nitride.Gemtext/NitrideGemtextBuilderExtensions.cs
Normal file
14
src/Nitride.Gemtext/NitrideGemtextBuilderExtensions.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Autofac;
|
||||||
|
|
||||||
|
namespace Nitride.Gemtext
|
||||||
|
{
|
||||||
|
public static class NitrideGemtextBuilderExtensions
|
||||||
|
{
|
||||||
|
public static NitrideBuilder UseGemtext(this NitrideBuilder builder)
|
||||||
|
{
|
||||||
|
return builder
|
||||||
|
.ConfigureContainer(
|
||||||
|
x => x.RegisterModule<NitrideGemtextModule>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/Nitride.Gemtext/NitrideGemtextModule.cs
Normal file
6
src/Nitride.Gemtext/NitrideGemtextModule.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Nitride.Gemtext
|
||||||
|
{
|
||||||
|
public class NitrideGemtextModule : NitrideModuleBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
159
src/Nitride.Generators/CodeAnalysisExtensions.cs
Normal file
159
src/Nitride.Generators/CodeAnalysisExtensions.cs
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Nitride.Generators
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Various wrappers around the diagnostics to simplify generation.
|
||||||
|
/// </summary>
|
||||||
|
public static class CodeAnalysisExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an error message to break the build while generating code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that contains the diagnostic.</param>
|
||||||
|
/// <param name="messageCode">The normalized message code.</param>
|
||||||
|
/// <param name="format">The string format for the message.</param>
|
||||||
|
/// <param name="parameters">The optional parameters.</param>
|
||||||
|
public static void Error(
|
||||||
|
this GeneratorExecutionContext context,
|
||||||
|
MessageCode messageCode,
|
||||||
|
string format,
|
||||||
|
params object[] parameters)
|
||||||
|
{
|
||||||
|
Error(context, messageCode, null, format, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an error message to break the build while generating code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that contains the diagnostic.</param>
|
||||||
|
/// <param name="messageCode">The normalized message code.</param>
|
||||||
|
/// <param name="location">The optional location for the message.</param>
|
||||||
|
/// <param name="format">The string format for the message.</param>
|
||||||
|
/// <param name="parameters">The optional parameters.</param>
|
||||||
|
public static void Error(
|
||||||
|
this GeneratorExecutionContext context,
|
||||||
|
MessageCode messageCode,
|
||||||
|
Location? location,
|
||||||
|
string format,
|
||||||
|
params object[] parameters)
|
||||||
|
{
|
||||||
|
context.Message(
|
||||||
|
messageCode,
|
||||||
|
location,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
format,
|
||||||
|
parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an informational message to break the build while generating code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that contains the diagnostic.</param>
|
||||||
|
/// <param name="messageCode">The normalized message code.</param>
|
||||||
|
/// <param name="format">The string format for the message.</param>
|
||||||
|
/// <param name="parameters">The optional parameters.</param>
|
||||||
|
public static void Information(
|
||||||
|
this GeneratorExecutionContext context,
|
||||||
|
MessageCode messageCode,
|
||||||
|
string format,
|
||||||
|
params object[] parameters)
|
||||||
|
{
|
||||||
|
Information(context, messageCode, null, format, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an informational message to break the build while generating code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that contains the diagnostic.</param>
|
||||||
|
/// <param name="messageCode">The normalized message code.</param>
|
||||||
|
/// <param name="location">The optional location for the message.</param>
|
||||||
|
/// <param name="format">The string format for the message.</param>
|
||||||
|
/// <param name="parameters">The optional parameters.</param>
|
||||||
|
public static void Information(
|
||||||
|
this GeneratorExecutionContext context,
|
||||||
|
MessageCode messageCode,
|
||||||
|
Location? location,
|
||||||
|
string format,
|
||||||
|
params object[] parameters)
|
||||||
|
{
|
||||||
|
context.Message(
|
||||||
|
messageCode,
|
||||||
|
location,
|
||||||
|
DiagnosticSeverity.Info,
|
||||||
|
format,
|
||||||
|
parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a warning message to break the build while generating code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that contains the diagnostic.</param>
|
||||||
|
/// <param name="messageCode">The normalized message code.</param>
|
||||||
|
/// <param name="format">The string format for the message.</param>
|
||||||
|
/// <param name="parameters">The optional parameters.</param>
|
||||||
|
public static void Warning(
|
||||||
|
this GeneratorExecutionContext context,
|
||||||
|
MessageCode messageCode,
|
||||||
|
string format,
|
||||||
|
params object[] parameters)
|
||||||
|
{
|
||||||
|
Warning(context, messageCode, null, format, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a warning message to break the build while generating code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that contains the diagnostic.</param>
|
||||||
|
/// <param name="messageCode">The normalized message code.</param>
|
||||||
|
/// <param name="location">The optional location for the message.</param>
|
||||||
|
/// <param name="format">The string format for the message.</param>
|
||||||
|
/// <param name="parameters">The optional parameters.</param>
|
||||||
|
public static void Warning(
|
||||||
|
this GeneratorExecutionContext context,
|
||||||
|
MessageCode messageCode,
|
||||||
|
Location? location,
|
||||||
|
string format,
|
||||||
|
params object[] parameters)
|
||||||
|
{
|
||||||
|
context.Message(
|
||||||
|
messageCode,
|
||||||
|
location,
|
||||||
|
DiagnosticSeverity.Warning,
|
||||||
|
format,
|
||||||
|
parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a message to break the build while generating code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that contains the diagnostic.</param>
|
||||||
|
/// <param name="messageCode">The normalized message code.</param>
|
||||||
|
/// <param name="location">The optional location for the message.</param>
|
||||||
|
/// <param name="format">The string format for the message.</param>
|
||||||
|
/// <param name="parameters">The optional parameters.</param>
|
||||||
|
/// <param name="severity">The severity of the message.</param>
|
||||||
|
private static void Message(
|
||||||
|
this GeneratorExecutionContext context,
|
||||||
|
MessageCode messageCode,
|
||||||
|
Location? location,
|
||||||
|
DiagnosticSeverity severity,
|
||||||
|
string format,
|
||||||
|
params object[] parameters)
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(
|
||||||
|
Diagnostic.Create(
|
||||||
|
"GN" + ((int)messageCode).ToString("D4"),
|
||||||
|
"Nitride",
|
||||||
|
string.Format(format, parameters),
|
||||||
|
severity,
|
||||||
|
severity,
|
||||||
|
true,
|
||||||
|
severity is DiagnosticSeverity.Warning
|
||||||
|
or DiagnosticSeverity.Info
|
||||||
|
? 4
|
||||||
|
: 0,
|
||||||
|
location: location));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/Nitride.Generators/MessageCode.cs
Normal file
10
src/Nitride.Generators/MessageCode.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Nitride.Generators
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All the error messages produced by the generators.
|
||||||
|
/// </summary>
|
||||||
|
public enum MessageCode
|
||||||
|
{
|
||||||
|
Debug = 1,
|
||||||
|
}
|
||||||
|
}
|
19
src/Nitride.Generators/Nitride.Generators.csproj
Normal file
19
src/Nitride.Generators/Nitride.Generators.csproj
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>Common source generators for Nitride.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Gallium" Version="1.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
209
src/Nitride.Generators/WithPropertySourceGenerator.cs
Normal file
209
src/Nitride.Generators/WithPropertySourceGenerator.cs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace Nitride.Generators
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements a source generator that creates Set* methods for the various
|
||||||
|
/// properties that also returns the same object for purposes of chaining
|
||||||
|
/// together calls.
|
||||||
|
/// </summary>
|
||||||
|
[Generator]
|
||||||
|
public class WithPropertySourceGenerator : ISourceGenerator
|
||||||
|
{
|
||||||
|
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.
|
||||||
|
var syntaxReceiver = (SyntaxReceiver?)context.SyntaxReceiver;
|
||||||
|
|
||||||
|
if (syntaxReceiver == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report any messages.
|
||||||
|
foreach (var 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.ClassesToAugment.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through each one.
|
||||||
|
foreach (var cds in syntaxReceiver.ClassesToAugment)
|
||||||
|
{
|
||||||
|
this.GenerateClassFile(context, cds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(GeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
// Register a factory that can create our custom syntax receiver
|
||||||
|
context.RegisterForSyntaxNotifications(
|
||||||
|
() => new SyntaxReceiver(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateClassFile(
|
||||||
|
GeneratorExecutionContext context,
|
||||||
|
ClassDeclarationSyntax cds)
|
||||||
|
{
|
||||||
|
// Get the namespace.
|
||||||
|
var nds = (NamespaceDeclarationSyntax?)cds.Parent;
|
||||||
|
|
||||||
|
if (nds == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? ns = nds.Name.ToString();
|
||||||
|
|
||||||
|
// Create the partial class.
|
||||||
|
StringBuilder buffer = new();
|
||||||
|
buffer.AppendLine("#nullable enable");
|
||||||
|
|
||||||
|
// Copy the using statements from the file.
|
||||||
|
if (nds.Parent is CompilationUnitSyntax cus)
|
||||||
|
{
|
||||||
|
foreach (var uds in cus.Usings)
|
||||||
|
{
|
||||||
|
buffer.AppendLine(uds.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the namespace.
|
||||||
|
buffer.AppendLine($"namespace {ns}");
|
||||||
|
buffer.AppendLine("{");
|
||||||
|
buffer.AppendLine($" public partial class {cds.Identifier}");
|
||||||
|
buffer.AppendLine(" {");
|
||||||
|
|
||||||
|
// Go through the properties of the namespace.
|
||||||
|
IEnumerable<PropertyDeclarationSyntax> properties = cds.Members
|
||||||
|
.Where(m => m.Kind() == SyntaxKind.PropertyDeclaration)
|
||||||
|
.Cast<PropertyDeclarationSyntax>();
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
foreach (PropertyDeclarationSyntax pds in properties)
|
||||||
|
{
|
||||||
|
// See if we have a setter.
|
||||||
|
bool found =
|
||||||
|
pds.AccessorList?.Accessors
|
||||||
|
.Any(x => x.Keyword.ToString() == "set")
|
||||||
|
?? false;
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we aren't first, then add a newline before it.
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write some documentation.
|
||||||
|
buffer.AppendLine(" /// <summary>");
|
||||||
|
buffer.AppendLine(
|
||||||
|
string.Format(
|
||||||
|
" /// Sets the {0} value and returns the operation for chaining.",
|
||||||
|
pds.Identifier));
|
||||||
|
buffer.AppendLine(" /// </summary>");
|
||||||
|
|
||||||
|
// We have the components for writing out a setter.
|
||||||
|
buffer.AppendLine(
|
||||||
|
string.Format(
|
||||||
|
" public {0} With{1}({2} value)",
|
||||||
|
cds.Identifier,
|
||||||
|
pds.Identifier,
|
||||||
|
pds.Type));
|
||||||
|
buffer.AppendLine(" {");
|
||||||
|
buffer.AppendLine(
|
||||||
|
string.Format(
|
||||||
|
" this.{0} = value;",
|
||||||
|
pds.Identifier));
|
||||||
|
buffer.AppendLine(" return this;");
|
||||||
|
buffer.AppendLine(" }");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish up the class.
|
||||||
|
buffer.AppendLine(" }");
|
||||||
|
buffer.AppendLine("}");
|
||||||
|
|
||||||
|
// Create the source text and write out the file.
|
||||||
|
SourceText sourceText = SourceText.From(
|
||||||
|
buffer.ToString(),
|
||||||
|
Encoding.UTF8);
|
||||||
|
context.AddSource(cds.Identifier + ".Generated.cs", sourceText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyntaxReceiver : ISyntaxReceiver
|
||||||
|
{
|
||||||
|
private readonly GeneratorInitializationContext context;
|
||||||
|
|
||||||
|
public SyntaxReceiver(GeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
this.context = context;
|
||||||
|
this.ClassesToAugment = new List<ClassDeclarationSyntax>();
|
||||||
|
this.Messages = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClassDeclarationSyntax> ClassesToAugment { get; }
|
||||||
|
|
||||||
|
public List<string> Messages { get; }
|
||||||
|
|
||||||
|
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||||
|
{
|
||||||
|
// We only care about class declarations.
|
||||||
|
if (syntaxNode is not ClassDeclarationSyntax cds)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if the class has our set properties attribute.
|
||||||
|
bool found = cds
|
||||||
|
.AttributeLists
|
||||||
|
.AsEnumerable()
|
||||||
|
.SelectMany(x => x.Attributes)
|
||||||
|
.Select(x => x.Name.ToString())
|
||||||
|
.Any(
|
||||||
|
x => x switch
|
||||||
|
{
|
||||||
|
"WithProperties" => true,
|
||||||
|
"WithPropertiesAttribute" => true,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
this.ClassesToAugment.Add(cds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
src/Nitride.Handlebars/ApplyContentHandlebarsTemplate.cs
Normal file
74
src/Nitride.Handlebars/ApplyContentHandlebarsTemplate.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using HandlebarsDotNet;
|
||||||
|
using Nitride.Contents;
|
||||||
|
|
||||||
|
namespace Nitride.Handlebars
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An operation that uses the content to create a template that is then
|
||||||
|
/// applied against the content and metadata and then replaces the content
|
||||||
|
/// of that entity.
|
||||||
|
/// </summary>
|
||||||
|
public class ApplyContentHandlebarsTemplate<TModel> : NitrideOperationBase
|
||||||
|
{
|
||||||
|
private readonly HandlebarsTemplateCache cache;
|
||||||
|
|
||||||
|
public ApplyContentHandlebarsTemplate(HandlebarsTemplateCache cache)
|
||||||
|
{
|
||||||
|
this.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the callback used to create a model from a given
|
||||||
|
/// entity. This allows for the website to customize what information is
|
||||||
|
/// being passed to the template.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, TModel>? CreateModelCallback { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
this.CheckNotNull(x => x.CreateModelCallback);
|
||||||
|
|
||||||
|
return input
|
||||||
|
.ForEachEntity<HasHandlebarsTemplate, ITextContent>(this.Apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the callback for the template and returns the operation to
|
||||||
|
/// chain operations.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">The callback to set.</param>
|
||||||
|
/// <returns>The ApplyContentHandlebarsTemplate to chain requests.</returns>
|
||||||
|
public ApplyContentHandlebarsTemplate<TModel> WithCreateModelCallback(
|
||||||
|
Func<Entity, TModel>? callback)
|
||||||
|
{
|
||||||
|
this.CreateModelCallback = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entity Apply(
|
||||||
|
Entity entity,
|
||||||
|
HasHandlebarsTemplate _,
|
||||||
|
ITextContent content)
|
||||||
|
{
|
||||||
|
// Create the model using the callback.
|
||||||
|
TModel model = this.CreateModelCallback!(entity);
|
||||||
|
|
||||||
|
// Create a template from the contents.
|
||||||
|
string text = content.GetText();
|
||||||
|
HandlebarsTemplate<object, object> template =
|
||||||
|
this.cache.GetLiteralTemplate(text);
|
||||||
|
|
||||||
|
// Render the template and create a new entity with the updated
|
||||||
|
// text.
|
||||||
|
string result = template(model!);
|
||||||
|
|
||||||
|
return entity
|
||||||
|
.Remove<HasHandlebarsTemplate>()
|
||||||
|
.SetTextContent(new StringTextContent(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
88
src/Nitride.Handlebars/ApplyStyleHandlebarsTemplate.cs
Normal file
88
src/Nitride.Handlebars/ApplyStyleHandlebarsTemplate.cs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using HandlebarsDotNet;
|
||||||
|
using Nitride.Contents;
|
||||||
|
|
||||||
|
namespace Nitride.Handlebars
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An operation that applies a common or shared template on the content of
|
||||||
|
/// a document that includes theme or styling information.
|
||||||
|
/// </summary>
|
||||||
|
public class ApplyStyleHandlebarsTemplate<TModel> : NitrideOperationBase
|
||||||
|
{
|
||||||
|
private readonly HandlebarsTemplateCache cache;
|
||||||
|
|
||||||
|
public ApplyStyleHandlebarsTemplate(HandlebarsTemplateCache cache)
|
||||||
|
{
|
||||||
|
this.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the callback used to create a model from a given
|
||||||
|
/// entity. This allows for the website to customize what information is
|
||||||
|
/// being passed to the template.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, TModel>? CreateModelCallback { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the callback used to determine which template to use
|
||||||
|
/// for a given entity. This lets one have a per-page template change.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, string>? GetTemplateName { get; set; }
|
||||||
|
|
||||||
|
public IHandlebars? Handlebars { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
// Make sure we have sane data.
|
||||||
|
this.CheckNotNull(x => x.CreateModelCallback);
|
||||||
|
this.CheckNotNull(x => x.Handlebars);
|
||||||
|
this.CheckNotNull(x => x.GetTemplateName);
|
||||||
|
|
||||||
|
// Create and set up the Handlebars.
|
||||||
|
return input
|
||||||
|
.ForEachEntity<ITextContent>(this.Apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the callback for the template and returns the operation to
|
||||||
|
/// chain operations.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">The callback to set.</param>
|
||||||
|
/// <returns>The ApplyContentHandlebarsTemplate to chain requests.</returns>
|
||||||
|
public ApplyStyleHandlebarsTemplate<TModel> WithCreateModelCallback(
|
||||||
|
Func<Entity, TModel>? callback)
|
||||||
|
{
|
||||||
|
this.CreateModelCallback = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplyStyleHandlebarsTemplate<TModel> WithGetTemplateName(
|
||||||
|
Func<Entity, string>? callback)
|
||||||
|
{
|
||||||
|
this.GetTemplateName = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplyStyleHandlebarsTemplate<TModel> WithHandlebars(
|
||||||
|
IHandlebars? handlebars)
|
||||||
|
{
|
||||||
|
this.Handlebars = handlebars;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entity Apply(Entity entity, ITextContent content)
|
||||||
|
{
|
||||||
|
TModel model = this.CreateModelCallback!(entity);
|
||||||
|
string name = this.GetTemplateName!(entity);
|
||||||
|
HandlebarsTemplate<object, object> template =
|
||||||
|
this.cache.GetNamedTemplate(name);
|
||||||
|
string result = template(model!);
|
||||||
|
|
||||||
|
return entity.SetTextContent(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
src/Nitride.Handlebars/HandlebarsTemplateCache.cs
Normal file
70
src/Nitride.Handlebars/HandlebarsTemplateCache.cs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using HandlebarsDotNet;
|
||||||
|
using Open.Threading;
|
||||||
|
|
||||||
|
namespace Nitride.Handlebars
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements a cache for templates to prevent compiling the same template
|
||||||
|
/// more than once.
|
||||||
|
/// </summary>
|
||||||
|
public class HandlebarsTemplateCache
|
||||||
|
{
|
||||||
|
private readonly IHandlebars handlebars;
|
||||||
|
|
||||||
|
private readonly ModificationSynchronizer locker;
|
||||||
|
|
||||||
|
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>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caches the template by name based on the contents of the disk. It
|
||||||
|
/// does it once in a multi-threaded manner to ensure it is only cached
|
||||||
|
/// once in memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="literal">The string that contains the template.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public HandlebarsTemplate<object, object> GetLiteralTemplate(
|
||||||
|
string literal)
|
||||||
|
{
|
||||||
|
// Start with a read lock to see if we've already compiled it.
|
||||||
|
this.locker.Modifying(
|
||||||
|
() => !this.templates.ContainsKey(literal),
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
HandlebarsTemplate<object, object> template =
|
||||||
|
this.handlebars
|
||||||
|
!.Compile(literal);
|
||||||
|
|
||||||
|
this.templates[literal] = template;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.locker.Reading(() => this.templates[literal]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caches the template by name based on the contents of the disk. It
|
||||||
|
/// does it once in a multi-threaded manner to ensure it is only cached
|
||||||
|
/// once in memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="templateName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public HandlebarsTemplate<object, object> GetNamedTemplate(
|
||||||
|
string templateName)
|
||||||
|
{
|
||||||
|
string template = $"{{{{> {templateName}}}}}";
|
||||||
|
|
||||||
|
return this.GetLiteralTemplate(template);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/Nitride.Handlebars/HasHandlebarsTemplate.cs
Normal file
11
src/Nitride.Handlebars/HasHandlebarsTemplate.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace Nitride.Handlebars
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A marker component that indicates that a given file with text component
|
||||||
|
/// has a Handlebars template in it.
|
||||||
|
/// </summary>
|
||||||
|
public class HasHandlebarsTemplate
|
||||||
|
{
|
||||||
|
public static HasHandlebarsTemplate Instance { get; } = new();
|
||||||
|
}
|
||||||
|
}
|
32
src/Nitride.Handlebars/IdentifyHandlebars.cs
Normal file
32
src/Nitride.Handlebars/IdentifyHandlebars.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using Nitride.Contents;
|
||||||
|
|
||||||
|
namespace Nitride.Handlebars
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An operation that discovers which text files have a Handlebars template
|
||||||
|
/// inside them.
|
||||||
|
/// </summary>
|
||||||
|
public class IdentifyHandlebars : INitrideOperation
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
return input
|
||||||
|
.ForEachEntity<ITextContent>(this.ScanContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entity ScanContent(Entity entity, ITextContent content)
|
||||||
|
{
|
||||||
|
string text = content.GetText();
|
||||||
|
|
||||||
|
if (text.Contains("{{") && text.Contains("}}"))
|
||||||
|
{
|
||||||
|
return entity.Set(HasHandlebarsTemplate.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
src/Nitride.Handlebars/Nitride.Handlebars.csproj
Normal file
36
src/Nitride.Handlebars/Nitride.Handlebars.csproj
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>An extension to Nitride static site generator to style output with Handlebars.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Autofac" Version="6.2.0" />
|
||||||
|
<PackageReference Include="Gallium" Version="1.0.2" />
|
||||||
|
<PackageReference Include="Handlebars.Net" Version="2.0.8" />
|
||||||
|
<PackageReference Include="NodaTime.Testing" Version="3.0.5" />
|
||||||
|
<PackageReference Include="Open.Threading" Version="1.6.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride\Nitride.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the source generator -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.Generators\Nitride.Generators.csproj">
|
||||||
|
<OutputItemType>Analyzer</OutputItemType>
|
||||||
|
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
14
src/Nitride.Handlebars/NitrideHandlebarsBuilderExtensions.cs
Normal file
14
src/Nitride.Handlebars/NitrideHandlebarsBuilderExtensions.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Autofac;
|
||||||
|
|
||||||
|
namespace Nitride.Handlebars
|
||||||
|
{
|
||||||
|
public static class NitrideHandlebarsBuilderExtensions
|
||||||
|
{
|
||||||
|
public static NitrideBuilder UseHandlebars(this NitrideBuilder builder)
|
||||||
|
{
|
||||||
|
return builder
|
||||||
|
.ConfigureContainer(
|
||||||
|
x => x.RegisterModule<NitrideHandlebarsModule>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/Nitride.Handlebars/NitrideHandlebarsModule.cs
Normal file
22
src/Nitride.Handlebars/NitrideHandlebarsModule.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using Autofac;
|
||||||
|
|
||||||
|
namespace Nitride.Handlebars
|
||||||
|
{
|
||||||
|
public class NitrideHandlebarsModule : NitrideModuleBase
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Register(ContainerBuilder builder)
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.RegisterType<HandlebarsTemplateCache>()
|
||||||
|
.AsSelf()
|
||||||
|
.SingleInstance();
|
||||||
|
builder
|
||||||
|
.RegisterGeneric(typeof(ApplyContentHandlebarsTemplate<>))
|
||||||
|
.As(typeof(ApplyContentHandlebarsTemplate<>));
|
||||||
|
builder
|
||||||
|
.RegisterGeneric(typeof(ApplyStyleHandlebarsTemplate<>))
|
||||||
|
.As(typeof(ApplyStyleHandlebarsTemplate<>));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/Nitride.Handlebars/README.md
Normal file
26
src/Nitride.Handlebars/README.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
Nitride.Handlebars
|
||||||
|
==================
|
||||||
|
|
||||||
|
This is a collection
|
||||||
|
of [Handlebars](https://github.com/Handlebars-Net/Handlebars.Net)
|
||||||
|
operations that work with Nitride. There are two ways of using Handelbars in
|
||||||
|
this package.
|
||||||
|
|
||||||
|
## IHandlebars
|
||||||
|
|
||||||
|
This library does *not* configure or register `IHandlebars` but requires it to
|
||||||
|
be injected into the DI container (Autofac) along with any templates or inlines
|
||||||
|
loaded.
|
||||||
|
|
||||||
|
## Styling Templates
|
||||||
|
|
||||||
|
The first is to use it to apply a Handlebars theme to a page or text content.
|
||||||
|
This would include adding links to the CSS, any javascript, generating menus,
|
||||||
|
and common navigation elements.
|
||||||
|
|
||||||
|
## Content Templates
|
||||||
|
|
||||||
|
The second is used to apply it to the Handlebars inside the text content. This
|
||||||
|
is the page-specific content that needs to be resolved. A good example of this
|
||||||
|
might be creating a "last five posts" page or embedding some metadata from the
|
||||||
|
YAML header into the page.
|
31
src/Nitride.Html/HtmlEntitiesToUnicode.cs
Normal file
31
src/Nitride.Html/HtmlEntitiesToUnicode.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using Gallium;
|
||||||
|
using Nitride.Contents;
|
||||||
|
|
||||||
|
namespace Nitride.Html
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the text input that uses HTML entities and turns them into
|
||||||
|
/// Unicode variations.
|
||||||
|
/// </summary>
|
||||||
|
public class HtmlEntitiesToUnicode : NitrideOperationBase
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
return input
|
||||||
|
.ForEachEntity<ITextContent>(this.ResolveHtmlEntities);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entity ResolveHtmlEntities(
|
||||||
|
Entity entity,
|
||||||
|
ITextContent content)
|
||||||
|
{
|
||||||
|
string text = content.GetText();
|
||||||
|
string resolved = WebUtility.HtmlDecode(text);
|
||||||
|
|
||||||
|
return entity.SetTextContent(resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/Nitride.Html/IsHtml.cs
Normal file
10
src/Nitride.Html/IsHtml.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Nitride.Html
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A marker component that indicates that the entity is an HTML file.
|
||||||
|
/// </summary>
|
||||||
|
public class IsHtml
|
||||||
|
{
|
||||||
|
public static IsHtml Instance { get; } = new();
|
||||||
|
}
|
||||||
|
}
|
31
src/Nitride.Html/Nitride.Html.csproj
Normal file
31
src/Nitride.Html/Nitride.Html.csproj
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>An extension to Nitride static site generator to generate HTML output.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride\Nitride.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the source generator -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.Generators\Nitride.Generators.csproj">
|
||||||
|
<OutputItemType>Analyzer</OutputItemType>
|
||||||
|
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Gallium" Version="1.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
14
src/Nitride.Html/NitrideHtmlBuilderExtensions.cs
Normal file
14
src/Nitride.Html/NitrideHtmlBuilderExtensions.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Autofac;
|
||||||
|
|
||||||
|
namespace Nitride.Html
|
||||||
|
{
|
||||||
|
public static class NitrideHtmlBuilderExtensions
|
||||||
|
{
|
||||||
|
public static NitrideBuilder UseHtml(this NitrideBuilder builder)
|
||||||
|
{
|
||||||
|
return builder
|
||||||
|
.ConfigureContainer(
|
||||||
|
x => x.RegisterModule<NitrideHtmlModule>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/Nitride.Html/NitrideHtmlModule.cs
Normal file
6
src/Nitride.Html/NitrideHtmlModule.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Nitride.Html
|
||||||
|
{
|
||||||
|
public class NitrideHtmlModule : NitrideModuleBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
48
src/Nitride.IO.Tests/AddPathPrefixTests.cs
Normal file
48
src/Nitride.IO.Tests/AddPathPrefixTests.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Linq;
|
||||||
|
using Autofac;
|
||||||
|
using Nitride.IO.Contents;
|
||||||
|
using Nitride.IO.Paths;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Zio;
|
||||||
|
using Zio.FileSystems;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Tests
|
||||||
|
{
|
||||||
|
public class AddPathPrefixTests : NitrideIOTestsBase
|
||||||
|
{
|
||||||
|
private readonly MemoryFileSystem fileSystem;
|
||||||
|
|
||||||
|
public AddPathPrefixTests(ITestOutputHelper output)
|
||||||
|
: base(output)
|
||||||
|
{
|
||||||
|
this.fileSystem = new MemoryFileSystem();
|
||||||
|
this.fileSystem.CreateFile("/b1.txt");
|
||||||
|
this.fileSystem.CreateFile("/c1.md");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PrefixAllFiles()
|
||||||
|
{
|
||||||
|
// Set up the operation.
|
||||||
|
var readFiles = this.Container.Resolve<ReadFiles.Factory>();
|
||||||
|
var op = new AddPathPrefix("/prefix");
|
||||||
|
|
||||||
|
// Read and replace the paths.
|
||||||
|
IOrderedEnumerable<string> output = readFiles(this.fileSystem)
|
||||||
|
.Read()
|
||||||
|
.Run(op)
|
||||||
|
.Select(x => x.Get<UPath>().ToString())
|
||||||
|
.OrderBy(x => x);
|
||||||
|
|
||||||
|
// Verify the results.
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"/prefix/b1.txt",
|
||||||
|
"/prefix/c1.md",
|
||||||
|
},
|
||||||
|
output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
src/Nitride.IO.Tests/MoveToIndexPathsTests.cs
Normal file
80
src/Nitride.IO.Tests/MoveToIndexPathsTests.cs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
using System.Linq;
|
||||||
|
using Autofac;
|
||||||
|
using Nitride.IO.Contents;
|
||||||
|
using Nitride.IO.Paths;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Zio;
|
||||||
|
using Zio.FileSystems;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Tests
|
||||||
|
{
|
||||||
|
public class MoveToIndexPathsTests : NitrideIOTestsBase
|
||||||
|
{
|
||||||
|
private readonly MemoryFileSystem fileSystem;
|
||||||
|
|
||||||
|
public MoveToIndexPathsTests(ITestOutputHelper output)
|
||||||
|
: base(output)
|
||||||
|
{
|
||||||
|
this.fileSystem = new MemoryFileSystem();
|
||||||
|
this.fileSystem.CreateDirectory("/c1");
|
||||||
|
this.fileSystem.CreateFile("/a1");
|
||||||
|
this.fileSystem.CreateFile("/b1.txt");
|
||||||
|
this.fileSystem.CreateFile("/c1/index.md");
|
||||||
|
this.fileSystem.CreateFile("/d1.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MoveAllFiles()
|
||||||
|
{
|
||||||
|
// Set up the operation.
|
||||||
|
var readFiles = this.Container.Resolve<ReadFiles.Factory>();
|
||||||
|
var op = new MoveToIndexPaths();
|
||||||
|
|
||||||
|
// Read and replace the paths.
|
||||||
|
IOrderedEnumerable<string> output = readFiles(this.fileSystem)
|
||||||
|
.Read()
|
||||||
|
.Run(op)
|
||||||
|
.Select(x => x.Get<UPath>().ToString())
|
||||||
|
.OrderBy(x => x);
|
||||||
|
|
||||||
|
// Verify the results.
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"/a1",
|
||||||
|
"/b1.txt",
|
||||||
|
"/c1/index.md",
|
||||||
|
"/d1/index.html",
|
||||||
|
},
|
||||||
|
output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OverrideCanMoveCallback()
|
||||||
|
{
|
||||||
|
// Set up the operation.
|
||||||
|
var readFiles = this.Container.Resolve<ReadFiles.Factory>();
|
||||||
|
MoveToIndexPaths? op = new MoveToIndexPaths()
|
||||||
|
.WithCanMoveCallback((path) => path.ToString().Contains("a1"));
|
||||||
|
|
||||||
|
// Read and replace the paths.
|
||||||
|
IOrderedEnumerable<string> output = readFiles(this.fileSystem)
|
||||||
|
.Read()
|
||||||
|
.Run(op)
|
||||||
|
.Select(x => x.Get<UPath>().ToString())
|
||||||
|
.OrderBy(x => x);
|
||||||
|
|
||||||
|
// Verify the results.
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"/a1/index",
|
||||||
|
"/b1.txt",
|
||||||
|
"/c1/index.md",
|
||||||
|
"/d1.html",
|
||||||
|
},
|
||||||
|
output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/Nitride.IO.Tests/Nitride.IO.Tests.csproj
Normal file
29
src/Nitride.IO.Tests/Nitride.IO.Tests.csproj
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.IO\Nitride.IO.csproj" />
|
||||||
|
<ProjectReference Include="..\Nitride.Tests\Nitride.Tests.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Gallium" Version="1.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||||
|
<PackageReference Include="JunitXml.TestLogger" Version="2.1.81" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Zio" Version="0.12.0" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
21
src/Nitride.IO.Tests/NitrideIOTestsBase.cs
Normal file
21
src/Nitride.IO.Tests/NitrideIOTestsBase.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using Autofac;
|
||||||
|
using Nitride.Tests;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Tests
|
||||||
|
{
|
||||||
|
public abstract class NitrideIOTestsBase : NitrideTestsBase
|
||||||
|
{
|
||||||
|
protected NitrideIOTestsBase(ITestOutputHelper output)
|
||||||
|
: base(output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void ConfigureContainer(ContainerBuilder builder)
|
||||||
|
{
|
||||||
|
base.ConfigureContainer(builder);
|
||||||
|
builder.RegisterModule<NitrideIOModule>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
src/Nitride.IO.Tests/ReadFilesTests.cs
Normal file
97
src/Nitride.IO.Tests/ReadFilesTests.cs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
using System.Linq;
|
||||||
|
using Autofac;
|
||||||
|
using Nitride.IO.Contents;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Zio;
|
||||||
|
using Zio.FileSystems;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests the functionality of the ReadFiles().
|
||||||
|
/// </summary>
|
||||||
|
public class ReadFilesTests : NitrideIOTestsBase
|
||||||
|
{
|
||||||
|
private readonly MemoryFileSystem fileSystem;
|
||||||
|
|
||||||
|
public ReadFilesTests(ITestOutputHelper output)
|
||||||
|
: base(output)
|
||||||
|
{
|
||||||
|
this.fileSystem = new MemoryFileSystem();
|
||||||
|
this.fileSystem.CreateDirectory("/b1");
|
||||||
|
this.fileSystem.CreateDirectory("/c1");
|
||||||
|
this.fileSystem.CreateDirectory("/c1/c2");
|
||||||
|
this.fileSystem.CreateDirectory("/d1");
|
||||||
|
this.fileSystem.WriteAllText("/a.txt", "File A");
|
||||||
|
this.fileSystem.WriteAllText("/b1/b.md", "File B");
|
||||||
|
this.fileSystem.WriteAllText("/c1/c.txt", "File C");
|
||||||
|
this.fileSystem.WriteAllText("/c1/c2/e.md", "File C");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadAllFiles()
|
||||||
|
{
|
||||||
|
// Set up the operation.
|
||||||
|
var factory = this.Container.Resolve<ReadFiles.Factory>();
|
||||||
|
ReadFiles op = factory(this.fileSystem);
|
||||||
|
|
||||||
|
// Verify the paths.
|
||||||
|
IOrderedEnumerable<string> paths = op.Read()
|
||||||
|
.Select(x => x.Get<UPath>())
|
||||||
|
.Select(x => (string)x)
|
||||||
|
.OrderBy(x => x);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"/a.txt",
|
||||||
|
"/b1/b.md",
|
||||||
|
"/c1/c.txt",
|
||||||
|
"/c1/c2/e.md",
|
||||||
|
},
|
||||||
|
paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadGlob()
|
||||||
|
{
|
||||||
|
// Set up the operation.
|
||||||
|
var factory = this.Container.Resolve<ReadFiles.Factory>();
|
||||||
|
ReadFiles op = factory(this.fileSystem);
|
||||||
|
|
||||||
|
// Verify the paths.
|
||||||
|
IOrderedEnumerable<string> paths = op.Read("/*.txt")
|
||||||
|
.Select(x => (string)x.Get<UPath>())
|
||||||
|
.OrderBy(x => x);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"/a.txt",
|
||||||
|
},
|
||||||
|
paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadGlobWithSubdirectories()
|
||||||
|
{
|
||||||
|
// Set up the operation.
|
||||||
|
var factory = this.Container.Resolve<ReadFiles.Factory>();
|
||||||
|
ReadFiles op = factory(this.fileSystem);
|
||||||
|
|
||||||
|
// Verify the paths.
|
||||||
|
IOrderedEnumerable<string> paths = op.Read("**/*.txt")
|
||||||
|
.Select(x => (string)x.Get<UPath>())
|
||||||
|
.OrderBy(x => x);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"/a.txt",
|
||||||
|
"/c1/c.txt",
|
||||||
|
},
|
||||||
|
paths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/Nitride.IO.Tests/RemovePathPrefixTests.cs
Normal file
50
src/Nitride.IO.Tests/RemovePathPrefixTests.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using System.Linq;
|
||||||
|
using Autofac;
|
||||||
|
using Nitride.IO.Contents;
|
||||||
|
using Nitride.IO.Paths;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Zio;
|
||||||
|
using Zio.FileSystems;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Tests
|
||||||
|
{
|
||||||
|
public class RemovePathPrefixTests : NitrideIOTestsBase
|
||||||
|
{
|
||||||
|
private readonly MemoryFileSystem fileSystem;
|
||||||
|
|
||||||
|
public RemovePathPrefixTests(ITestOutputHelper output)
|
||||||
|
: base(output)
|
||||||
|
{
|
||||||
|
this.fileSystem = new MemoryFileSystem();
|
||||||
|
this.fileSystem.CreateDirectory("/a");
|
||||||
|
this.fileSystem.CreateDirectory("/a/a");
|
||||||
|
this.fileSystem.CreateFile("/a/b1.txt");
|
||||||
|
this.fileSystem.CreateFile("/a/a/c1.md");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PrefixAllFiles()
|
||||||
|
{
|
||||||
|
// Set up the operation.
|
||||||
|
var readFiles = this.Container.Resolve<ReadFiles.Factory>();
|
||||||
|
var op = new RemovePathPrefix("/a");
|
||||||
|
|
||||||
|
// Read and replace the paths.
|
||||||
|
IOrderedEnumerable<string> output = readFiles(this.fileSystem)
|
||||||
|
.Read()
|
||||||
|
.Run(op)
|
||||||
|
.Select(x => x.Get<UPath>().ToString())
|
||||||
|
.OrderBy(x => x);
|
||||||
|
|
||||||
|
// Verify the results.
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"/a/c1.md",
|
||||||
|
"/b1.txt",
|
||||||
|
},
|
||||||
|
output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
src/Nitride.IO.Tests/WriteFilesTest.cs
Normal file
63
src/Nitride.IO.Tests/WriteFilesTest.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using System.Linq;
|
||||||
|
using Autofac;
|
||||||
|
using Nitride.Contents;
|
||||||
|
using Nitride.IO.Contents;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Zio;
|
||||||
|
using Zio.FileSystems;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests the functionality of the WriteFiles().
|
||||||
|
/// </summary>
|
||||||
|
public class WriteFilesTest : NitrideIOTestsBase
|
||||||
|
{
|
||||||
|
private readonly MemoryFileSystem fileSystem;
|
||||||
|
|
||||||
|
public WriteFilesTest(ITestOutputHelper output)
|
||||||
|
: base(output)
|
||||||
|
{
|
||||||
|
this.fileSystem = new MemoryFileSystem();
|
||||||
|
this.fileSystem.CreateDirectory("/b1");
|
||||||
|
this.fileSystem.CreateDirectory("/c1");
|
||||||
|
this.fileSystem.CreateDirectory("/c1/c2");
|
||||||
|
this.fileSystem.CreateDirectory("/d1");
|
||||||
|
this.fileSystem.WriteAllText("/a.txt", "File A");
|
||||||
|
this.fileSystem.WriteAllText("/b1/b.md", "File B");
|
||||||
|
this.fileSystem.WriteAllText("/c1/c2/e.md", "File E");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WriteAllFiles()
|
||||||
|
{
|
||||||
|
// Set up the operation.
|
||||||
|
var output = new MemoryFileSystem();
|
||||||
|
var readFiles = this.Container.Resolve<ReadFiles.Factory>();
|
||||||
|
var factory = this.Container.Resolve<WriteFiles.Factory>();
|
||||||
|
WriteFiles op = factory(output);
|
||||||
|
|
||||||
|
// Read and write out the files. We switch one of the files to be
|
||||||
|
// text content to make sure that works too.
|
||||||
|
readFiles(this.fileSystem)
|
||||||
|
.Read()
|
||||||
|
.Select(
|
||||||
|
x => x.Get<UPath>() == "/b1/b.md"
|
||||||
|
? x.SetTextContent(
|
||||||
|
((ITextContentConvertable)x.GetBinaryContent())
|
||||||
|
.ToTextContent())
|
||||||
|
: x)
|
||||||
|
.Run(op);
|
||||||
|
|
||||||
|
// Verify the results.
|
||||||
|
Assert.True(output.FileExists("/a.txt"));
|
||||||
|
Assert.True(output.FileExists("/b1/b.md"));
|
||||||
|
Assert.True(output.FileExists("/c1/c2/e.md"));
|
||||||
|
|
||||||
|
Assert.Equal("File A", output.ReadAllText("/a.txt"));
|
||||||
|
Assert.Equal("File B", output.ReadAllText("/b1/b.md"));
|
||||||
|
Assert.Equal("File E", output.ReadAllText("/c1/c2/e.md"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
src/Nitride.IO/Contents/FileEntryBinaryContent.cs
Normal file
35
src/Nitride.IO/Contents/FileEntryBinaryContent.cs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
using System.IO;
|
||||||
|
using Nitride.Contents;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Contents
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains a wrapper around a file entry to retrieve the binary data.
|
||||||
|
/// </summary>
|
||||||
|
public class FileEntryBinaryContent
|
||||||
|
: IBinaryContent, ITextContentConvertable
|
||||||
|
{
|
||||||
|
private readonly FileEntry entry;
|
||||||
|
|
||||||
|
public FileEntryBinaryContent(FileEntry entry)
|
||||||
|
{
|
||||||
|
this.entry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Stream GetStream()
|
||||||
|
{
|
||||||
|
return this.entry.Open(
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.Read);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ITextContent ToTextContent()
|
||||||
|
{
|
||||||
|
return new FileEntryTextContent(this.entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/Nitride.IO/Contents/FileEntryTextContent.cs
Normal file
38
src/Nitride.IO/Contents/FileEntryTextContent.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using Nitride.Contents;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Contents
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains a wrapper around a file entry to retrieve text data.
|
||||||
|
/// </summary>
|
||||||
|
public class FileEntryTextContent
|
||||||
|
: ITextContent, IBinaryContentConvertable
|
||||||
|
{
|
||||||
|
private readonly FileEntry entry;
|
||||||
|
|
||||||
|
public FileEntryTextContent(FileEntry entry)
|
||||||
|
{
|
||||||
|
this.entry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public TextReader GetReader()
|
||||||
|
{
|
||||||
|
return new StreamReader(
|
||||||
|
this.entry.Open(
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.Read),
|
||||||
|
Encoding.UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IBinaryContent ToBinaryContent()
|
||||||
|
{
|
||||||
|
return new FileEntryBinaryContent(this.entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
src/Nitride.IO/Contents/ReadFiles.cs
Normal file
89
src/Nitride.IO/Contents/ReadFiles.cs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using DotNet.Globbing;
|
||||||
|
using Gallium;
|
||||||
|
using Nitride.Contents;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Contents
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A module that reads files from the file system and wraps them in an
|
||||||
|
/// entity with the following components: UPath, IContent.
|
||||||
|
/// </summary>
|
||||||
|
public class ReadFiles : FileSystemOperation
|
||||||
|
{
|
||||||
|
public ReadFiles(IFileSystem fileSystem)
|
||||||
|
: base(fileSystem)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Primary method for creating a read file.
|
||||||
|
/// </summary>
|
||||||
|
public delegate ReadFiles Factory(IFileSystem fileSystem);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads all files from the file system and returns them.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A populated collection of entities.</returns>
|
||||||
|
public IEnumerable<Entity> Read(
|
||||||
|
UPath path = new(),
|
||||||
|
string searchPattern = "*",
|
||||||
|
SearchOption search = SearchOption.AllDirectories)
|
||||||
|
{
|
||||||
|
// Normalize the path.
|
||||||
|
path = path == new UPath() ? "/" : path;
|
||||||
|
|
||||||
|
// Search for the file and wrap the results.
|
||||||
|
IEnumerable<FileEntry> files = this.FileSystem
|
||||||
|
.EnumerateFileEntries(path, searchPattern, search);
|
||||||
|
IEnumerable<Entity> entities = files.Select(this.ToEntity);
|
||||||
|
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads all files from the file system and returns them.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A populated collection of entities.</returns>
|
||||||
|
public IEnumerable<Entity> Read(string glob)
|
||||||
|
{
|
||||||
|
Glob parsed = Glob.Parse(glob);
|
||||||
|
|
||||||
|
return this.Read(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads all files from the file system, filtering them out by the
|
||||||
|
/// minimatch pattern (as defined by DotNet.Blob).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A populated collection of entities.</returns>
|
||||||
|
public IEnumerable<Entity> Read(Glob glob)
|
||||||
|
{
|
||||||
|
IEnumerable<FileEntry> files = this.FileSystem
|
||||||
|
.EnumerateFileEntries("/", "*.*", SearchOption.AllDirectories)
|
||||||
|
.Where(x => glob.IsMatch(x.Path.ToString()));
|
||||||
|
IEnumerable<Entity> entities = files.Select(this.ToEntity);
|
||||||
|
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an entity with the standard components for all Zio-based
|
||||||
|
/// files. This attaches the file's path relative to the file system
|
||||||
|
/// and a way of accessing the content from the file system.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The Zio file entry.</param>
|
||||||
|
/// <returns>An Entity with appropriate content.</returns>
|
||||||
|
private Entity ToEntity(FileEntry file)
|
||||||
|
{
|
||||||
|
Entity entity = new Entity()
|
||||||
|
.Set(file.Path)
|
||||||
|
.SetBinaryContent(new FileEntryBinaryContent(file));
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
220
src/Nitride.IO/Contents/WriteFiles.cs
Normal file
220
src/Nitride.IO/Contents/WriteFiles.cs
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Gallium;
|
||||||
|
using Nitride.Contents;
|
||||||
|
using Serilog;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Contents
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An operation that writes out entities to a file system.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class WriteFiles : FileSystemOperation, INitrideOperation
|
||||||
|
{
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
private Dictionary<Type, Func<IContent, Stream>> factories;
|
||||||
|
|
||||||
|
public WriteFiles(
|
||||||
|
IFileSystem fileSystem,
|
||||||
|
ILogger logger)
|
||||||
|
: base(fileSystem)
|
||||||
|
{
|
||||||
|
this.logger = logger.ForContext<WriteFiles>();
|
||||||
|
this.factories = new Dictionary<Type, Func<IContent, Stream>>
|
||||||
|
{
|
||||||
|
[typeof(IBinaryContent)] = GetBinaryStream,
|
||||||
|
[typeof(ITextContent)] = this.GetTextStream,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Primary method for creating a write files operation.
|
||||||
|
/// </summary>
|
||||||
|
public delegate WriteFiles Factory(IFileSystem fileSystem);
|
||||||
|
|
||||||
|
public Dictionary<Type, Func<IContent, Stream>> StreamFactories
|
||||||
|
{
|
||||||
|
get => this.factories;
|
||||||
|
set => this.factories =
|
||||||
|
value ?? throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the encoding to force any text output.
|
||||||
|
/// </summary>
|
||||||
|
public Encoding? TextEncoding { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes out all the files to the given file system using the paths
|
||||||
|
/// currently stored in the `UPath` component. Only files that have
|
||||||
|
/// a path and a registered writer will be written.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entities">The entities to parse.</param>
|
||||||
|
/// <returns>The same list of entities without changes.</returns>
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> entities)
|
||||||
|
{
|
||||||
|
// We need the `ToList()` here, otherwise it doesn't work.
|
||||||
|
IEnumerable<Entity> results = entities
|
||||||
|
.ForEachEntity<UPath>(this.Process)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream GetBinaryStream(IContent content)
|
||||||
|
{
|
||||||
|
return ((IBinaryContent)content).GetStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream GetTextStream(IContent content)
|
||||||
|
{
|
||||||
|
// See if we can convert the stream first. If that is the case, then
|
||||||
|
// we don't have to load it entirely in memory.
|
||||||
|
if (content is IBinaryContentConvertable convertable)
|
||||||
|
{
|
||||||
|
return convertable.ToBinaryContent().GetStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have the load the text into memory and convert it.
|
||||||
|
var textContent = (ITextContent)content;
|
||||||
|
string text = textContent.GetReader().ReadToEnd();
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
var writer = new StreamWriter(
|
||||||
|
stream,
|
||||||
|
this.TextEncoding ?? Encoding.UTF8);
|
||||||
|
|
||||||
|
writer.Write(text);
|
||||||
|
writer.Flush();
|
||||||
|
stream.Position = 0;
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal method for writing out the entity. This handles the
|
||||||
|
/// registered writers to allow for multiple `IContent` types being
|
||||||
|
/// written out automatically.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity to write out.</param>
|
||||||
|
/// <param name="path">The path of the entity.</param>
|
||||||
|
/// <returns>The entity passed in.</returns>
|
||||||
|
private Entity Process(Entity entity, UPath path)
|
||||||
|
{
|
||||||
|
// See if we have any content. If we don't, then there is nothing
|
||||||
|
// to do.
|
||||||
|
if (!entity.HasContent())
|
||||||
|
{
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First see if we have a factory for the exact type of content.
|
||||||
|
IContent content = entity.GetContent();
|
||||||
|
|
||||||
|
if (this.factories.TryGetValue(
|
||||||
|
content.GetType(),
|
||||||
|
out var getStream))
|
||||||
|
{
|
||||||
|
Stream stream = getStream(content);
|
||||||
|
|
||||||
|
return this.Process(entity, path, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 var binaryContent))
|
||||||
|
{
|
||||||
|
Stream stream =
|
||||||
|
binaryContent(binaryConvertable.ToBinaryContent());
|
||||||
|
|
||||||
|
return this.Process(entity, path, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content is ITextContentConvertable textConvertable
|
||||||
|
&& this.factories.TryGetValue(
|
||||||
|
typeof(ITextContent),
|
||||||
|
out var textContent))
|
||||||
|
{
|
||||||
|
Stream stream = textContent(textConvertable.ToTextContent());
|
||||||
|
|
||||||
|
return this.Process(entity, path, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For everything else, we have to find a content that we have a
|
||||||
|
// registered type for by walking up the inheritance tree and
|
||||||
|
// finding the right type.
|
||||||
|
List<Type> types = new() { content.GetType() };
|
||||||
|
|
||||||
|
while (types.Count > 0)
|
||||||
|
{
|
||||||
|
// Check to see if we have any of these types.
|
||||||
|
Func<IContent, Stream>? found = types
|
||||||
|
.Select(
|
||||||
|
x => this.factories.TryGetValue(x, out var factory)
|
||||||
|
? factory
|
||||||
|
: null)
|
||||||
|
.FirstOrDefault(x => x != null);
|
||||||
|
|
||||||
|
if (found != null)
|
||||||
|
{
|
||||||
|
Stream stream = found(content);
|
||||||
|
|
||||||
|
return this.Process(entity, path, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()))
|
||||||
|
.Where(x => x != null)
|
||||||
|
.Select(x => x!)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got this far, we never found a content to handle.
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Cannot write out entity "
|
||||||
|
+ path
|
||||||
|
+ " because cannot determine how to get a stream out content type "
|
||||||
|
+ content.GetType().FullName
|
||||||
|
+ ". To resolve, register a function to this.StreamFactories.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes out a stream to the given path in the file system.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity being written out.</param>
|
||||||
|
/// <param name="path">The path to write out, directories will be created.</param>
|
||||||
|
/// <param name="stream">The stream to write out.</param>
|
||||||
|
/// <returns>The entity passed in.</returns>
|
||||||
|
private Entity Process(Entity entity, UPath path, Stream stream)
|
||||||
|
{
|
||||||
|
// Make sure we have the directory structure.
|
||||||
|
UPath directory = path.GetDirectory();
|
||||||
|
|
||||||
|
if (directory != "/")
|
||||||
|
{
|
||||||
|
this.FileSystem.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out the file.
|
||||||
|
using Stream fileStream = this.FileSystem.CreateFile(path);
|
||||||
|
|
||||||
|
stream.CopyTo(fileStream);
|
||||||
|
stream.Close();
|
||||||
|
|
||||||
|
// Return the entity because we've written out the files.
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
src/Nitride.IO/Directories/ClearDirectory.cs
Normal file
82
src/Nitride.IO/Directories/ClearDirectory.cs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using Serilog;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Directories
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Nitride operation that removes the contents of a directory but not
|
||||||
|
/// the directory itself. This is used because some tools don't handle
|
||||||
|
/// when the root directory is removed.
|
||||||
|
/// This will create the top-level directory if it doesn't exist.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class ClearDirectory : FileSystemOperation, INitrideOperation
|
||||||
|
{
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
public ClearDirectory(
|
||||||
|
ILogger logger,
|
||||||
|
IFileSystem fileSystem)
|
||||||
|
: base(fileSystem)
|
||||||
|
{
|
||||||
|
this.logger = logger.ForContext<ClearDirectory>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path of the directory to clear.
|
||||||
|
/// </summary>
|
||||||
|
public UPath? Path { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<Entity> Run()
|
||||||
|
{
|
||||||
|
return this.Run(new List<Entity>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
// This really isn't an input-type of operation, but it can fit
|
||||||
|
// inside one to keep a pattern.
|
||||||
|
if (!this.Path.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
nameof(ClearDirectory)
|
||||||
|
+ "cannot be used without setting the path either by the"
|
||||||
|
+ "factory method, the constructor, the property, or "
|
||||||
|
+ "SetPath method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if the directory exists. If it doesn't, then we make it.
|
||||||
|
UPath path = this.Path.Value;
|
||||||
|
|
||||||
|
if (!this.FileSystem.DirectoryExists(path))
|
||||||
|
{
|
||||||
|
this.logger.Information(
|
||||||
|
"Creating the directory {Path}",
|
||||||
|
path);
|
||||||
|
this.FileSystem.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear out the contents.
|
||||||
|
IEnumerable<UPath> files = this.FileSystem.EnumerateFiles(path);
|
||||||
|
IEnumerable<UPath> directories =
|
||||||
|
this.FileSystem.EnumerateDirectories(path);
|
||||||
|
|
||||||
|
foreach (UPath file in files)
|
||||||
|
{
|
||||||
|
this.FileSystem.DeleteFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (UPath directory in directories)
|
||||||
|
{
|
||||||
|
this.FileSystem.DeleteDirectory(directory, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just pass the input on.
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/Nitride.IO/FileSystemOperation.cs
Normal file
14
src/Nitride.IO/FileSystemOperation.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO
|
||||||
|
{
|
||||||
|
public abstract class FileSystemOperation
|
||||||
|
{
|
||||||
|
protected FileSystemOperation(IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
this.FileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IFileSystem FileSystem { get; }
|
||||||
|
}
|
||||||
|
}
|
37
src/Nitride.IO/Nitride.IO.csproj
Normal file
37
src/Nitride.IO/Nitride.IO.csproj
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Nitride.IO</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>An extension to Nitride static site generator to read and write files.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Autofac" Version="6.2.0" />
|
||||||
|
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
|
||||||
|
<PackageReference Include="Gallium" Version="1.0.2" />
|
||||||
|
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||||
|
<PackageReference Include="Zio" Version="0.12.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride\Nitride.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the source generator -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.Generators\Nitride.Generators.csproj">
|
||||||
|
<OutputItemType>Analyzer</OutputItemType>
|
||||||
|
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
14
src/Nitride.IO/NitrideIOBuilderExtensions.cs
Normal file
14
src/Nitride.IO/NitrideIOBuilderExtensions.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Autofac;
|
||||||
|
|
||||||
|
namespace Nitride.IO
|
||||||
|
{
|
||||||
|
public static class NitrideIOBuilderExtensions
|
||||||
|
{
|
||||||
|
public static NitrideBuilder UseIO(this NitrideBuilder builder)
|
||||||
|
{
|
||||||
|
return builder
|
||||||
|
.ConfigureContainer(
|
||||||
|
x => x.RegisterModule<NitrideIOModule>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/Nitride.IO/NitrideIOModule.cs
Normal file
6
src/Nitride.IO/NitrideIOModule.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Nitride.IO
|
||||||
|
{
|
||||||
|
public class NitrideIOModule : NitrideModuleBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
48
src/Nitride.IO/Paths/AddPathPrefix.cs
Normal file
48
src/Nitride.IO/Paths/AddPathPrefix.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Paths
|
||||||
|
{
|
||||||
|
[WithProperties]
|
||||||
|
public partial class AddPathPrefix : INitrideOperation
|
||||||
|
{
|
||||||
|
public AddPathPrefix()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddPathPrefix(UPath pathPrefix)
|
||||||
|
{
|
||||||
|
this.PathPrefix = pathPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the prefix for the path operations.
|
||||||
|
/// </summary>
|
||||||
|
public UPath? PathPrefix { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
if (this.PathPrefix == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
nameof(AddPathPrefix)
|
||||||
|
+ ".Prefix was not set "
|
||||||
|
+ "(via constructor, property, or SetPrefix) before "
|
||||||
|
+ "Run() was called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplacePaths replacePaths = new ReplacePaths()
|
||||||
|
.WithReplacement(this.RunReplacement);
|
||||||
|
|
||||||
|
return replacePaths.Run(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UPath RunReplacement(Entity _, UPath path)
|
||||||
|
{
|
||||||
|
string innerRelativePath = path.ToString().TrimStart('/');
|
||||||
|
return this.PathPrefix!.Value / innerRelativePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/Nitride.IO/Paths/ChangePathExtension.cs
Normal file
50
src/Nitride.IO/Paths/ChangePathExtension.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Paths
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the extension of the paths given.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class ChangePathExtension : INitrideOperation
|
||||||
|
{
|
||||||
|
public ChangePathExtension()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChangePathExtension(string extension)
|
||||||
|
{
|
||||||
|
this.Extension = extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the prefix for the path operations.
|
||||||
|
/// </summary>
|
||||||
|
public string? Extension { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
if (this.Extension == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
nameof(AddPathPrefix)
|
||||||
|
+ ".Extension was not set "
|
||||||
|
+ "(via constructor, property, or SetExtension) before "
|
||||||
|
+ "Run() was called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplacePaths replacePaths = new ReplacePaths()
|
||||||
|
.WithReplacement(this.RunReplacement);
|
||||||
|
|
||||||
|
return replacePaths.Run(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UPath RunReplacement(Entity _, UPath path)
|
||||||
|
{
|
||||||
|
return path.ChangeExtension(this.Extension!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
src/Nitride.IO/Paths/MoveToIndexPaths.cs
Normal file
86
src/Nitride.IO/Paths/MoveToIndexPaths.cs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Paths
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Moves various files to indexes of a direction with the base filename.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class MoveToIndexPaths : INitrideOperation
|
||||||
|
{
|
||||||
|
public MoveToIndexPaths()
|
||||||
|
{
|
||||||
|
this.CanMoveCallback = DefaultCanMoveCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the callback to determine if the file should be moved.
|
||||||
|
/// This will not be called if the file is already an index.
|
||||||
|
/// </summary>
|
||||||
|
public Func<UPath, bool>? CanMoveCallback { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default implement of the operation moves .html, .htm, .md, and
|
||||||
|
/// .markdown files into their indexes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <returns>True if the file should move, otherwise false.</returns>
|
||||||
|
public static bool DefaultCanMoveCallback(UPath path)
|
||||||
|
{
|
||||||
|
return path.GetExtensionWithDot() switch
|
||||||
|
{
|
||||||
|
".htm" => true,
|
||||||
|
".html" => true,
|
||||||
|
".md" => true,
|
||||||
|
".markdown" => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
if (this.CanMoveCallback == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
nameof(MoveToIndexPaths)
|
||||||
|
+ ".CanMoveCallback was not set "
|
||||||
|
+ "(via constructor, property, or SetCanMoveCallback) before "
|
||||||
|
+ "Run() was called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplacePaths replacePaths = new ReplacePaths()
|
||||||
|
.WithReplacement(this.RunReplacement);
|
||||||
|
|
||||||
|
return replacePaths.Run(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UPath RunReplacement(Entity _, UPath path)
|
||||||
|
{
|
||||||
|
// See if we are already an index. If that is true, then we don't
|
||||||
|
// have to move any further.
|
||||||
|
string? nameWithoutExtension = path.GetNameWithoutExtension();
|
||||||
|
|
||||||
|
if (nameWithoutExtension is null or "index")
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if the path should be moved. If it can't, then just stop
|
||||||
|
// processing.
|
||||||
|
if (!this.CanMoveCallback!.Invoke(path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the file to an index.
|
||||||
|
UPath parent = path.GetDirectory();
|
||||||
|
string? extension = path.GetExtensionWithDot();
|
||||||
|
string index = "index" + extension;
|
||||||
|
|
||||||
|
return parent / nameWithoutExtension / index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
src/Nitride.IO/Paths/RemovePathPrefix.cs
Normal file
58
src/Nitride.IO/Paths/RemovePathPrefix.cs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Paths
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An operation that removes a path prefix from the input.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class RemovePathPrefix : INitrideOperation
|
||||||
|
{
|
||||||
|
public RemovePathPrefix()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemovePathPrefix(UPath pathPrefix)
|
||||||
|
{
|
||||||
|
this.PathPrefix = pathPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the prefix for the path operations.
|
||||||
|
/// </summary>
|
||||||
|
public UPath? PathPrefix { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
if (this.PathPrefix == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
nameof(RemovePathPrefix)
|
||||||
|
+ ".Prefix was not set "
|
||||||
|
+ "(via constructor, property, or SetPrefix) before "
|
||||||
|
+ "Replace() was called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplacePaths replacePaths = new ReplacePaths()
|
||||||
|
.WithReplacement(this.RunReplacement);
|
||||||
|
|
||||||
|
return replacePaths.Run(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UPath RunReplacement(Entity _, UPath path)
|
||||||
|
{
|
||||||
|
string normalized = path.ToString();
|
||||||
|
string prefix = this.PathPrefix.ToString()!;
|
||||||
|
|
||||||
|
if (normalized.StartsWith(prefix))
|
||||||
|
{
|
||||||
|
return (UPath)path.ToString().Substring(prefix.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
src/Nitride.IO/Paths/ReplacePaths.cs
Normal file
59
src/Nitride.IO/Paths/ReplacePaths.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Paths
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A pipeline operation that replaces the UPath of the given entity
|
||||||
|
/// with the results of a lambda output. Entities without a path component
|
||||||
|
/// are passed on without touching.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class ReplacePaths : INitrideOperation
|
||||||
|
{
|
||||||
|
public ReplacePaths()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacePaths(Func<Entity, UPath, UPath> replacement)
|
||||||
|
{
|
||||||
|
this.Replacement = replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the replacement callback to alter the paths.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Entity, UPath, UPath>? Replacement { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the replacement on the input streams and outputs the
|
||||||
|
/// resulting entities. Only entities that have had their paths changed
|
||||||
|
/// will be updated, the others will be passed on as-is.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The list of input entities.</param>
|
||||||
|
/// <returns>The output entities.</returns>
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
if (this.Replacement == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"ReplacePaths.Replacement was not set "
|
||||||
|
+ "(via constructor, property, or SetReplacement) before "
|
||||||
|
+ "Replace() was called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return input
|
||||||
|
.ForEachEntity<UPath>(
|
||||||
|
(entity, oldPath) =>
|
||||||
|
{
|
||||||
|
UPath newPath = this.Replacement(entity, oldPath);
|
||||||
|
|
||||||
|
return newPath != oldPath
|
||||||
|
? entity.Set(newPath)
|
||||||
|
: entity;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
src/Nitride.IO/Paths/UPathExtensions.cs
Normal file
41
src/Nitride.IO/Paths/UPathExtensions.cs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.IO.Paths
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the UPath class.
|
||||||
|
/// </summary>
|
||||||
|
public static class UPathExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the directory path, which excludes the directory at the end
|
||||||
|
/// of the path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to manipulate.</param>
|
||||||
|
/// <returns>A normalized path.</returns>
|
||||||
|
public static string GetDirectoryIndexPath(this UPath path)
|
||||||
|
{
|
||||||
|
if (path.GetNameWithoutExtension() == "index")
|
||||||
|
{
|
||||||
|
return path.GetDirectory().ToString().TrimEnd('/') + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetParentDirectoryIndexPath(this UPath path)
|
||||||
|
{
|
||||||
|
UPath indexPath = path.GetDirectoryIndexPath();
|
||||||
|
UPath parent = indexPath.GetDirectory();
|
||||||
|
|
||||||
|
if (parent == null!)
|
||||||
|
{
|
||||||
|
parent = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
string parentPath = parent.ToString().TrimEnd('/') + "/";
|
||||||
|
|
||||||
|
return parentPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/Nitride.IO/README.md
Normal file
31
src/Nitride.IO/README.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Nitride.IO
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Internally, this assembly uses [Zio](https://github.com/xoofx/zio), an file
|
||||||
|
system abstraction that also has an in-memory implementation which is ideal for
|
||||||
|
running unit tests. Zio also provides a way of treating an arbitrary directory
|
||||||
|
as a root directory so all paths are relative to the "root".
|
||||||
|
|
||||||
|
## Path Normalization
|
||||||
|
|
||||||
|
Zio also provides `UPath`, a normalization class that handles relative and
|
||||||
|
absolute paths. Entities that are read from the disk, such as with `ReadFiles`,
|
||||||
|
will have a `UPath` component with the path from the file. This component is
|
||||||
|
also used to determine the path when writing out the results.
|
||||||
|
|
||||||
|
## Disk-Based Content
|
||||||
|
|
||||||
|
This assembly also extends the `Nitride.Contents.IBinaryContent` and
|
||||||
|
`Nitride.Contents.ITextContent` to have file system based implementations. These
|
||||||
|
keep track of the original path and file system regardless of changes made to
|
||||||
|
the `UPath` component.
|
||||||
|
|
110
src/Nitride.Javascript/InstallYarn.cs
Normal file
110
src/Nitride.Javascript/InstallYarn.cs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Gallium;
|
||||||
|
using Serilog;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.Javascript
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Installs Yarn in a given directory if it is missing. If the
|
||||||
|
/// `node_modules` is already there, this step is skipped unless
|
||||||
|
/// ForceInstall is set to true.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class InstallYarn : INitrideOperation
|
||||||
|
{
|
||||||
|
private readonly IFileSystem fileSystem;
|
||||||
|
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
public InstallYarn(ILogger logger, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
this.fileSystem = fileSystem;
|
||||||
|
this.logger = logger.ForContext<InstallYarn>();
|
||||||
|
this.Directory = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the directory that the install should be run from. This
|
||||||
|
/// is typically the same file as the package.json and the yarn.lock.
|
||||||
|
/// </summary>
|
||||||
|
public UPath? Directory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the install should be
|
||||||
|
/// forced.
|
||||||
|
/// </summary>
|
||||||
|
public bool ForceInstall { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
// Make sure we have a sane value.
|
||||||
|
if (!this.Directory.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
nameof(InstallYarn)
|
||||||
|
+ ".Directory was not set before Run() was called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the directory exists in case we are the first.
|
||||||
|
UPath directory = this.Directory.Value;
|
||||||
|
|
||||||
|
if (!this.fileSystem.DirectoryExists(directory))
|
||||||
|
{
|
||||||
|
this.logger.Information("Creating directory {Path}", directory);
|
||||||
|
this.fileSystem.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if the directory exists.
|
||||||
|
UPath modulesPath = directory / "node_modules";
|
||||||
|
bool exists = this.fileSystem.DirectoryExists(modulesPath);
|
||||||
|
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
if (!this.ForceInstall)
|
||||||
|
{
|
||||||
|
this.logger.Debug(
|
||||||
|
"{Path} already exists, skipping",
|
||||||
|
modulesPath);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.Information(
|
||||||
|
"{Path} already exists, forcing installation",
|
||||||
|
modulesPath);
|
||||||
|
this.fileSystem.DeleteDirectory(modulesPath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run Yarn install on the directory. Sadly, Yarn doesn't understand
|
||||||
|
// a C# abstraction layer, so we need the "real" path to work with
|
||||||
|
// these operations.
|
||||||
|
string realPath = this.fileSystem
|
||||||
|
.ConvertPathToInternal(directory);
|
||||||
|
|
||||||
|
// Run the install.
|
||||||
|
string command = YarnHelper.GetYarnCommand();
|
||||||
|
var start = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = command,
|
||||||
|
Arguments = "install",
|
||||||
|
WorkingDirectory = realPath,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
};
|
||||||
|
Process process = Process.Start(start);
|
||||||
|
|
||||||
|
if (process == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Cannot start yarn install");
|
||||||
|
}
|
||||||
|
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
// We are done, so just pass our input on.
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/Nitride.Javascript/Nitride.Javascript.csproj
Normal file
33
src/Nitride.Javascript/Nitride.Javascript.csproj
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>An extension to Nitride static site generator to generate Webpack output.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.IO\Nitride.IO.csproj" />
|
||||||
|
<ProjectReference Include="..\Nitride\Nitride.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Gallium" Version="1.0.2" />
|
||||||
|
<PackageReference Include="Zio" Version="0.12.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the source generator -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.Generators\Nitride.Generators.csproj">
|
||||||
|
<OutputItemType>Analyzer</OutputItemType>
|
||||||
|
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
14
src/Nitride.Javascript/NitrideJavascriptBuilderExtensions.cs
Normal file
14
src/Nitride.Javascript/NitrideJavascriptBuilderExtensions.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Autofac;
|
||||||
|
|
||||||
|
namespace Nitride.Javascript
|
||||||
|
{
|
||||||
|
public static class NitrideJavascriptBuilderExtensions
|
||||||
|
{
|
||||||
|
public static NitrideBuilder UseJavascript(this NitrideBuilder builder)
|
||||||
|
{
|
||||||
|
return builder
|
||||||
|
.ConfigureContainer(
|
||||||
|
x => x.RegisterModule<NitrideJavascriptModule>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/Nitride.Javascript/NitrideJavascriptModule.cs
Normal file
6
src/Nitride.Javascript/NitrideJavascriptModule.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Nitride.Javascript
|
||||||
|
{
|
||||||
|
public class NitrideJavascriptModule : NitrideModuleBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
115
src/Nitride.Javascript/RunWebpack.cs
Normal file
115
src/Nitride.Javascript/RunWebpack.cs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using Gallium;
|
||||||
|
using Nitride.IO.Contents;
|
||||||
|
using Nitride.IO.Paths;
|
||||||
|
using Serilog;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.Javascript
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Runs `webpack` in the directory, writing out whatever files need to be
|
||||||
|
/// written.
|
||||||
|
/// </summary>
|
||||||
|
[WithProperties]
|
||||||
|
public partial class RunWebpack : INitrideOperation
|
||||||
|
{
|
||||||
|
private readonly IFileSystem fileSystem;
|
||||||
|
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
private readonly ReadFiles readFiles;
|
||||||
|
|
||||||
|
public RunWebpack(
|
||||||
|
ILogger logger,
|
||||||
|
IFileSystem fileSystem,
|
||||||
|
ReadFiles readFiles)
|
||||||
|
{
|
||||||
|
this.fileSystem = fileSystem;
|
||||||
|
this.readFiles = readFiles;
|
||||||
|
this.logger = logger.ForContext<RunWebpack>();
|
||||||
|
this.WebpackDirectory = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the directory that contains the output from the
|
||||||
|
/// webpack execution.
|
||||||
|
/// </summary>
|
||||||
|
public UPath? OutputDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the directory that the install should be run from. This
|
||||||
|
/// is typically the same file as the webpack.config.js.
|
||||||
|
/// </summary>
|
||||||
|
public UPath? WebpackDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
// Make sure we have a sane values.
|
||||||
|
if (!this.WebpackDirectory.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
nameof(InstallYarn)
|
||||||
|
+ ".WebpackDirectory was not set before Run() was called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.OutputDirectory.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
nameof(InstallYarn)
|
||||||
|
+ ".OutputDirectory was not set before Run() was called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the directory exists in case we are the first.
|
||||||
|
UPath directory = this.WebpackDirectory.Value;
|
||||||
|
|
||||||
|
if (!this.fileSystem.DirectoryExists(directory))
|
||||||
|
{
|
||||||
|
this.logger.Information("Creating directory {Path}", directory);
|
||||||
|
this.fileSystem.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sadly, Yarn doesn't understand virtual file systems, so we need
|
||||||
|
// the "real" path to the directory for this.
|
||||||
|
string realPath = this.fileSystem
|
||||||
|
.ConvertPathToInternal(directory);
|
||||||
|
|
||||||
|
// Run the install.
|
||||||
|
this.logger.Debug("Running Webpack via Yarn");
|
||||||
|
|
||||||
|
string command = YarnHelper.GetYarnCommand();
|
||||||
|
var start = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = command,
|
||||||
|
Arguments = "run webpack",
|
||||||
|
WorkingDirectory = realPath,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
};
|
||||||
|
Process process = Process.Start(start);
|
||||||
|
|
||||||
|
if (process == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Cannot start yarn run webpack");
|
||||||
|
}
|
||||||
|
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
// We are done and all the output will be written into the
|
||||||
|
// filesystem, so merge it with our input.
|
||||||
|
this.logger.Debug("Adding Webpack output to the pipeline");
|
||||||
|
|
||||||
|
UPath outputDirectory = this.OutputDirectory.Value;
|
||||||
|
|
||||||
|
return input
|
||||||
|
.Union(
|
||||||
|
this.readFiles
|
||||||
|
.Read(outputDirectory)
|
||||||
|
.Run(new RemovePathPrefix(outputDirectory)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/Nitride.Javascript/YarnHelper.cs
Normal file
34
src/Nitride.Javascript/YarnHelper.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Nitride.Javascript
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A helper class for finding how to run or install Yarn.
|
||||||
|
/// </summary>
|
||||||
|
public static class YarnHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the command to run Yarn.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The command to run.</returns>
|
||||||
|
public static string GetYarnCommand()
|
||||||
|
{
|
||||||
|
// Figure out how to run Yarn. This is needed because Windows can't
|
||||||
|
// run the script like the shell can.
|
||||||
|
string command = "yarn";
|
||||||
|
string appData = Path.Combine(
|
||||||
|
Environment.GetFolderPath(
|
||||||
|
Environment.SpecialFolder.ApplicationData),
|
||||||
|
"npm",
|
||||||
|
"yarn.cmd");
|
||||||
|
|
||||||
|
if (File.Exists(appData))
|
||||||
|
{
|
||||||
|
command = appData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
src/Nitride.Markdown/IdentifyMarkdown.cs
Normal file
83
src/Nitride.Markdown/IdentifyMarkdown.cs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using Nitride.Contents;
|
||||||
|
using Zio;
|
||||||
|
|
||||||
|
namespace Nitride.Markdown
|
||||||
|
{
|
||||||
|
/// <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 IdentifyMarkdown : INitrideOperation
|
||||||
|
{
|
||||||
|
public IdentifyMarkdown()
|
||||||
|
{
|
||||||
|
this.IsMarkdownTest = DefaultIsMarkdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Func<Entity, UPath, bool> IsMarkdownTest { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
return input
|
||||||
|
.ForEachEntity<UPath, ITextContent>(
|
||||||
|
(entity, path, _) =>
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
})
|
||||||
|
.ForEachEntity<UPath, IBinaryContent>(
|
||||||
|
(entity, path, binary) =>
|
||||||
|
{
|
||||||
|
// If we aren't a Markdown file, then there is nothing
|
||||||
|
// we can do about that.
|
||||||
|
if (!this.IsMarkdownTest(entity, path))
|
||||||
|
{
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the file as a binary.
|
||||||
|
if (binary is ITextContentConvertable textConvertable)
|
||||||
|
{
|
||||||
|
entity = entity
|
||||||
|
.SetTextContent(textConvertable.ToTextContent())
|
||||||
|
.Set(IsMarkdown.Instance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Cannot convert a binary content to a "
|
||||||
|
+ "text without ITextContentConvertable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool DefaultIsMarkdown(Entity entity, UPath path)
|
||||||
|
{
|
||||||
|
return (path.GetExtensionWithDot() ?? string.Empty)
|
||||||
|
.ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
".md" => true,
|
||||||
|
".markdown" => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/Nitride.Markdown/IsMarkdown.cs
Normal file
10
src/Nitride.Markdown/IsMarkdown.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Nitride.Markdown
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A marker class that indicates that the file is a Markdown file.
|
||||||
|
/// </summary>
|
||||||
|
public class IsMarkdown
|
||||||
|
{
|
||||||
|
public static IsMarkdown Instance { get; } = new();
|
||||||
|
}
|
||||||
|
}
|
48
src/Nitride.Markdown/MarkdownOperationBase.cs
Normal file
48
src/Nitride.Markdown/MarkdownOperationBase.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gallium;
|
||||||
|
using Markdig;
|
||||||
|
using Nitride.Contents;
|
||||||
|
|
||||||
|
namespace Nitride.Markdown
|
||||||
|
{
|
||||||
|
public abstract class MarkdownOperationBase : INitrideOperation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an additional callback to configure additional features
|
||||||
|
/// from the baseline Markdown.
|
||||||
|
/// </summary>
|
||||||
|
public Action<MarkdownPipelineBuilder>? ConfigureMarkdown { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||||
|
{
|
||||||
|
// Create the Markdown pipeline used for formatting.
|
||||||
|
var builder = new MarkdownPipelineBuilder();
|
||||||
|
|
||||||
|
this.ConfigureMarkdown?.Invoke(builder);
|
||||||
|
|
||||||
|
MarkdownPipeline options = builder.Build();
|
||||||
|
|
||||||
|
// Process the Markdown files (while passing everything on).
|
||||||
|
return input
|
||||||
|
.ForEachEntity<IsMarkdown, ITextContent>(
|
||||||
|
(entity, _, content) => this.Convert(
|
||||||
|
entity,
|
||||||
|
content,
|
||||||
|
options));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the Markdown file into HTML.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity to convert.</param>
|
||||||
|
/// <param name="markdownContent">The content for this entity.</param>
|
||||||
|
/// <param name="options">The markdown pipeline.</param>
|
||||||
|
/// <returns>A converted entity.</returns>
|
||||||
|
protected abstract Entity Convert(
|
||||||
|
Entity entity,
|
||||||
|
ITextContent markdownContent,
|
||||||
|
MarkdownPipeline options);
|
||||||
|
}
|
||||||
|
}
|
40
src/Nitride.Markdown/MarkdownToGemtext.cs
Normal file
40
src/Nitride.Markdown/MarkdownToGemtext.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using Gallium;
|
||||||
|
using Markdig;
|
||||||
|
using MfGames.Markdown.Gemtext;
|
||||||
|
using Nitride.Contents;
|
||||||
|
using Nitride.Gemtext;
|
||||||
|
|
||||||
|
namespace Nitride.Markdown
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the input Markdown files into Gemtext using Markdig and
|
||||||
|
/// MfGames.Markdown.Gemtext. This only processes files with a text input
|
||||||
|
/// and the IsMarkdown component.
|
||||||
|
/// </summary>
|
||||||
|
public class MarkdownToGemtext : MarkdownOperationBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the Markdown file into HTML.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity to convert.</param>
|
||||||
|
/// <param name="markdownContent">The content for this entity.</param>
|
||||||
|
/// <param name="options">The markdown pipeline.</param>
|
||||||
|
/// <returns>A converted entity.</returns>
|
||||||
|
protected override Entity Convert(
|
||||||
|
Entity entity,
|
||||||
|
ITextContent markdownContent,
|
||||||
|
MarkdownPipeline options)
|
||||||
|
{
|
||||||
|
string markdown = markdownContent.GetText();
|
||||||
|
string gemtext = MarkdownGemtext.ToGemtext(markdown, options);
|
||||||
|
var content = new StringTextContent(gemtext);
|
||||||
|
|
||||||
|
entity = entity
|
||||||
|
.SetTextContent(content)
|
||||||
|
.Remove<IsMarkdown>()
|
||||||
|
.Set(IsGemtext.Instance);
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
src/Nitride.Markdown/MarkdownToHtml.cs
Normal file
40
src/Nitride.Markdown/MarkdownToHtml.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using Gallium;
|
||||||
|
using Markdig;
|
||||||
|
using Nitride.Contents;
|
||||||
|
using Nitride.Html;
|
||||||
|
|
||||||
|
namespace Nitride.Markdown
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the input Markdown files into HTML using Markdig. This only
|
||||||
|
/// processes files with a text input and the IsMarkdown component.
|
||||||
|
/// </summary>
|
||||||
|
public class MarkdownToHtml : MarkdownOperationBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the Markdown file into HTML.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity to convert.</param>
|
||||||
|
/// <param name="markdownContent">The content for this entity.</param>
|
||||||
|
/// <param name="options">The markdown pipeline.</param>
|
||||||
|
/// <returns>A converted entity.</returns>
|
||||||
|
protected override Entity Convert(
|
||||||
|
Entity entity,
|
||||||
|
ITextContent markdownContent,
|
||||||
|
MarkdownPipeline options)
|
||||||
|
{
|
||||||
|
// Convert the entity to Html.
|
||||||
|
string markdown = markdownContent.GetText();
|
||||||
|
string html = Markdig.Markdown.ToHtml(markdown, options);
|
||||||
|
var htmlContent = new StringTextContent(html);
|
||||||
|
|
||||||
|
entity = entity
|
||||||
|
.SetTextContent(htmlContent)
|
||||||
|
.Remove<IsMarkdown>()
|
||||||
|
.Set(IsHtml.Instance);
|
||||||
|
|
||||||
|
// Return the resulting entity.
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/Nitride.Markdown/MarkdownToOperationBaseExtension.cs
Normal file
22
src/Nitride.Markdown/MarkdownToOperationBaseExtension.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System;
|
||||||
|
using Markdig;
|
||||||
|
|
||||||
|
namespace Nitride.Markdown
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods to handle generics while configuring a Markdown
|
||||||
|
/// operation.
|
||||||
|
/// </summary>
|
||||||
|
public static class MarkdownToOperationBaseExtension
|
||||||
|
{
|
||||||
|
public static T WithConfigureMarkdown<T>(
|
||||||
|
this T operation,
|
||||||
|
Action<MarkdownPipelineBuilder>? callback)
|
||||||
|
where T : MarkdownOperationBase
|
||||||
|
|
||||||
|
{
|
||||||
|
operation.ConfigureMarkdown = callback;
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
src/Nitride.Markdown/Nitride.Markdown.csproj
Normal file
37
src/Nitride.Markdown/Nitride.Markdown.csproj
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>An extension to Nitride static site generator to render Markdown content.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.Gemtext\Nitride.Gemtext.csproj" />
|
||||||
|
<ProjectReference Include="..\Nitride.Html\Nitride.Html.csproj" />
|
||||||
|
<ProjectReference Include="..\Nitride\Nitride.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Gallium" Version="1.0.2" />
|
||||||
|
<PackageReference Include="Markdig" Version="0.25.0" />
|
||||||
|
<PackageReference Include="MfGames.Markdown.Gemtext" Version="1.0.0" />
|
||||||
|
<PackageReference Include="Zio" Version="0.12.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the source generator -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.Generators\Nitride.Generators.csproj">
|
||||||
|
<OutputItemType>Analyzer</OutputItemType>
|
||||||
|
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
14
src/Nitride.Markdown/NitrideMarkdownBuilderExtensions.cs
Normal file
14
src/Nitride.Markdown/NitrideMarkdownBuilderExtensions.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Autofac;
|
||||||
|
|
||||||
|
namespace Nitride.Markdown
|
||||||
|
{
|
||||||
|
public static class NitrideMarkdownBuilderExtensions
|
||||||
|
{
|
||||||
|
public static NitrideBuilder UseMarkdown(this NitrideBuilder builder)
|
||||||
|
{
|
||||||
|
return builder
|
||||||
|
.ConfigureContainer(
|
||||||
|
x => x.RegisterModule<NitrideMarkdownModule>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/Nitride.Markdown/NitrideMarkdownModule.cs
Normal file
6
src/Nitride.Markdown/NitrideMarkdownModule.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Nitride.Markdown
|
||||||
|
{
|
||||||
|
public class NitrideMarkdownModule : NitrideModuleBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
15
src/Nitride.Slugs/ISlugProvider.cs
Normal file
15
src/Nitride.Slugs/ISlugProvider.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
namespace Nitride.Slugs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface that provides slugs for various paths.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISlugProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given input into a slug.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The input string to normalize.</param>
|
||||||
|
/// <returns>The resulting slug.</returns>
|
||||||
|
string ToSlug(string input);
|
||||||
|
}
|
||||||
|
}
|
31
src/Nitride.Slugs/Nitride.Slugs.csproj
Normal file
31
src/Nitride.Slugs/Nitride.Slugs.csproj
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>An extension to Nitride static site generator to generate slugs.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Slugify.Core" Version="3.0.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride\Nitride.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the source generator -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nitride.Generators\Nitride.Generators.csproj">
|
||||||
|
<OutputItemType>Analyzer</OutputItemType>
|
||||||
|
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
28
src/Nitride.Slugs/NitrideSlugsBuilderExtensions.cs
Normal file
28
src/Nitride.Slugs/NitrideSlugsBuilderExtensions.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using Autofac;
|
||||||
|
|
||||||
|
namespace Nitride.Slugs
|
||||||
|
{
|
||||||
|
public static class NitrideSlugsBuilderExtensions
|
||||||
|
{
|
||||||
|
public static NitrideBuilder UseSlugs(
|
||||||
|
this NitrideBuilder builder,
|
||||||
|
ISlugProvider slugs)
|
||||||
|
{
|
||||||
|
if (slugs == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(slugs));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.ConfigureContainer(
|
||||||
|
x =>
|
||||||
|
{
|
||||||
|
x.RegisterInstance(slugs)
|
||||||
|
.As<ISlugProvider>()
|
||||||
|
.SingleInstance();
|
||||||
|
x.RegisterModule<NitrideSlugsModule>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/Nitride.Slugs/NitrideSlugsModule.cs
Normal file
8
src/Nitride.Slugs/NitrideSlugsModule.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
using Autofac;
|
||||||
|
|
||||||
|
namespace Nitride.Slugs
|
||||||
|
{
|
||||||
|
public class NitrideSlugsModule : Module
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
78
src/Nitride.Slugs/SimpleSlugProvider.cs
Normal file
78
src/Nitride.Slugs/SimpleSlugProvider.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Slugify;
|
||||||
|
|
||||||
|
namespace Nitride.Slugs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A default implementation of ISlugProvider.
|
||||||
|
/// </summary>
|
||||||
|
public class SimpleSlugProvider : ISlugProvider, IEnumerable<string>
|
||||||
|
{
|
||||||
|
private readonly List<(string, string)> replacements;
|
||||||
|
|
||||||
|
public SimpleSlugProvider()
|
||||||
|
{
|
||||||
|
this.replacements = new List<(string, string)>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleSlugProvider(IDictionary<string, string> replacements)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<string, string> pair in replacements)
|
||||||
|
{
|
||||||
|
this.Add(pair.Key, pair.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a replacement for the resulting slug.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="search">The text to search for.</param>
|
||||||
|
/// <param name="replace">The replacement string.</param>
|
||||||
|
public void Add(string search, string replace)
|
||||||
|
{
|
||||||
|
this.replacements.Add((search, replace));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerator<string> GetEnumerator()
|
||||||
|
{
|
||||||
|
return this.replacements
|
||||||
|
.Select(x => x.Item1)
|
||||||
|
.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual string ToSlug(string input)
|
||||||
|
{
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a slug..
|
||||||
|
var helper = new SlugHelper();
|
||||||
|
string output = helper.GenerateSlug(input);
|
||||||
|
|
||||||
|
// Perform any additional replacements.
|
||||||
|
foreach ((string search, string replace) in this.replacements)
|
||||||
|
{
|
||||||
|
output = output.Replace(search, replace);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return this.GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
src/Nitride.Slugs/UnicodeNormalizingSlugProvider.cs
Normal file
58
src/Nitride.Slugs/UnicodeNormalizingSlugProvider.cs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Nitride.Slugs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extends the slug provider to strip out accented characters for
|
||||||
|
/// a normalized form.
|
||||||
|
/// </summary>
|
||||||
|
public class UnicodeNormalizingSlugProvider : SimpleSlugProvider
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public UnicodeNormalizingSlugProvider()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public UnicodeNormalizingSlugProvider(
|
||||||
|
IDictionary<string, string> replacements)
|
||||||
|
: base(replacements)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToSlug(string input)
|
||||||
|
{
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the Unicode objects.
|
||||||
|
// Strip out the accents. This is a cheesy way of doing so.
|
||||||
|
char[] chars = input
|
||||||
|
.Normalize(NormalizationForm.FormD)
|
||||||
|
.Where(this.IsNonSpacingMark)
|
||||||
|
.ToArray();
|
||||||
|
string normalized = new string(chars)
|
||||||
|
.Normalize(NormalizationForm.FormC);
|
||||||
|
|
||||||
|
// Return the base implementation.
|
||||||
|
return base.ToSlug(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsNonSpacingMark(char c)
|
||||||
|
{
|
||||||
|
UnicodeCategory category = CharUnicodeInfo.GetUnicodeCategory(c);
|
||||||
|
|
||||||
|
return category != UnicodeCategory.NonSpacingMark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue