diff --git a/.editorconfig b/.editorconfig index 080a294fcb13bf2c8836c30f5e1ceade5fd77584..4382be9b1b94649f842a00815b06649f534fe167 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,5 @@ -# Editor configuration, see https://editorconfig.org root = true +# Editor configuration, see https://editorconfig.org [*] charset = utf-8 @@ -10,6 +10,57 @@ insert_final_newline = true trim_trailing_whitespace = true max_line_length = 80 +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers = false +csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion +csharp_style_expression_bodied_accessors = true:error +csharp_style_expression_bodied_properties = true:error +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_var_elsewhere = true:error +csharp_style_var_for_built_in_types = true:error +csharp_style_var_when_type_is_apparent = true:error +dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True +dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field +dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef +dotnet_naming_rule.unity_serialized_field_rule.severity = warning +dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style +dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +# ReSharper properties +resharper_apply_auto_detected_rules = false +resharper_enforce_line_ending_style = true +resharper_use_indent_from_vs = false +resharper_wrap_lines = true + +# ReSharper inspection severities +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_this_qualifier_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_razor_assembly_not_resolved_highlighting = warning +resharper_redundant_base_qualifier_highlighting = warning +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning + [*.cs] csharp_indent_block_contents = true csharp_indent_case_contents = true @@ -19,12 +70,8 @@ csharp_indent_switch_sections = true csharp_style_namespace_declarations = file_scoped:warning -dotnet_diagnostic.IDE0090.severity = warning # Use 'new(...)' instead of 'new ...' -dotnet_diagnostic.IDE1006.severity = warning - -dotnet_naming_rule.instance_fields_should_be_camel_case.severity = error -dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields -dotnet_naming_rule.instance_fields_should_be_camel_case.style = camel_case_style +dotnet_diagnostic.ide0090.severity = warning # Use 'new(...)' instead of 'new ...' +dotnet_diagnostic.ide1006.severity = warning dotnet_naming_symbols.instance_fields.applicable_kinds = field dotnet_naming_symbols.instance_fields.applicable_accessibilities = private @@ -53,23 +100,28 @@ csharp_space_after_keywords_in_control_flow_statements = true trim_trailing_whitespace = true # Code quality -dotnet_diagnostic.CA1062.severity = warning -dotnet_diagnostic.CA1303.severity = error -dotnet_diagnostic.CA1304.severity = error -dotnet_diagnostic.CA1305.severity = warning -dotnet_diagnostic.CA1307.severity = error -dotnet_diagnostic.CA1308.severity = error -dotnet_diagnostic.CA1309.severity = error -dotnet_diagnostic.CA1820.severity = error -dotnet_diagnostic.CA2000.severity = error -dotnet_diagnostic.CA2100.severity = error -dotnet_diagnostic.CA2201.severity = error -dotnet_diagnostic.CA2208.severity = error -dotnet_diagnostic.CA2214.severity = error -dotnet_diagnostic.CS8019.severity = warning -dotnet_diagnostic.IDE0055.severity = warning +dotnet_diagnostic.ca1062.severity = warning +dotnet_diagnostic.ca1303.severity = error +dotnet_diagnostic.ca1304.severity = error +dotnet_diagnostic.ca1305.severity = warning +dotnet_diagnostic.ca1307.severity = error +dotnet_diagnostic.ca1308.severity = error +dotnet_diagnostic.ca1309.severity = error +dotnet_diagnostic.ca1820.severity = error +dotnet_diagnostic.ca2000.severity = error +dotnet_diagnostic.ca2100.severity = error +dotnet_diagnostic.ca2201.severity = error +dotnet_diagnostic.ca2208.severity = error +dotnet_diagnostic.ca2214.severity = error +dotnet_diagnostic.cs8019.severity = warning +dotnet_diagnostic.ide0055.severity = warning [**/obj/**/*.cs] -dotnet_diagnostic.CS8019.severity = none # disable on debug genereated files +dotnet_diagnostic.cs8019.severity = none # disable on debug genereated files exclude = true generated_code = true + +[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,c++m,cc,ccm,cginc,compute,cp,cpp,cppm,cs,cshtml,cu,cuh,cxx,cxxm,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,mxx,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,uxml,vb,xaml,xamlx,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/.gitignore b/.gitignore index ec824ea1c152d4a5f31277da4f30c2cd83ce944e..3e7978cfb9c2d35913d3c20c75fd70a7b3fb4d23 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ project.lock.json **/coverage-results/ .idea/ qodana.yaml +qodana.sarif.json diff --git a/.nuke/Build.Container.cs b/.nuke/Build.Container.cs index c52ab8448aefde3238cb693c1f0b879a26630712..7a9db5b42062eb41c7f56493b509a009398c0a38 100644 --- a/.nuke/Build.Container.cs +++ b/.nuke/Build.Container.cs @@ -70,7 +70,7 @@ sealed partial class Build : NukeBuild _ => throw new ArgumentException($"There is no container image for: {runtimeIdentifier}"), }; - private List ContainerTags() + List ContainerTags() { if (IsLocalBuild) { diff --git a/.nuke/Build.Test.cs b/.nuke/Build.Test.cs index c525413151f09b6eb59d5f3cf3640dc2d0cb99c0..6570230da9e3c37c8a19bea8edc7e49cc45fede3 100644 --- a/.nuke/Build.Test.cs +++ b/.nuke/Build.Test.cs @@ -17,9 +17,9 @@ namespace SuCoS.Nuke; /// sealed partial class Build : NukeBuild { - static AbsolutePath testDirectory => RootDirectory / "test"; + static AbsolutePath testDirectory => RootDirectory / "Tests"; static AbsolutePath testDLLDirectory => testDirectory / "bin" / "Debug" / "net8.0"; - static AbsolutePath testAssembly => testDLLDirectory / "test.dll"; + static AbsolutePath testAssembly => testDLLDirectory / "Tests.dll"; static AbsolutePath coverageDirectory => RootDirectory / "coverage-results"; static AbsolutePath coverageResultDirectory => coverageDirectory / "coverage"; static AbsolutePath coverageResultFile => coverageResultDirectory / "coverage.xml"; diff --git a/.nuke/_nuke.csproj.DotSettings b/.nuke/_nuke.csproj.DotSettings index 7bc28484c4f19abc080179950150a846c996c0d4..337271da985e7fcd3905777bad8ff8e83c983e64 100644 --- a/.nuke/_nuke.csproj.DotSettings +++ b/.nuke/_nuke.csproj.DotSettings @@ -16,6 +16,8 @@ False <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> True True True @@ -24,4 +26,5 @@ True True True - True + True + True diff --git a/SuCoS.sln b/SuCoS.sln index 0974c4cd732ea739a5d2adcb79515e0661bb3a33..49c59b38401d54723fcb0916f91f695d9399833d 100644 --- a/SuCoS.sln +++ b/SuCoS.sln @@ -9,7 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuCoS", "source\SuCoS.cspro EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_nuke", ".nuke\_nuke.csproj", "{26DB04F6-DA88-43D7-8F4B-535D4D68C24E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test", "test\test.csproj", "{F3D789FD-6AC5-4A45-B9AC-079035F5909C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{F3D789FD-6AC5-4A45-B9AC-079035F5909C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/test/.TestSites/01/content/categories.md b/Tests/.TestSites/01/content/categories.md similarity index 100% rename from test/.TestSites/01/content/categories.md rename to Tests/.TestSites/01/content/categories.md diff --git a/test/.TestSites/01/content/date-future.md b/Tests/.TestSites/01/content/date-future.md similarity index 100% rename from test/.TestSites/01/content/date-future.md rename to Tests/.TestSites/01/content/date-future.md diff --git a/test/.TestSites/01/content/date-ok.md b/Tests/.TestSites/01/content/date-ok.md similarity index 100% rename from test/.TestSites/01/content/date-ok.md rename to Tests/.TestSites/01/content/date-ok.md diff --git a/test/.TestSites/01/content/expired.md b/Tests/.TestSites/01/content/expired.md similarity index 100% rename from test/.TestSites/01/content/expired.md rename to Tests/.TestSites/01/content/expired.md diff --git a/test/.TestSites/01/content/publishdate-future.md b/Tests/.TestSites/01/content/publishdate-future.md similarity index 100% rename from test/.TestSites/01/content/publishdate-future.md rename to Tests/.TestSites/01/content/publishdate-future.md diff --git a/test/.TestSites/01/content/publishdate-ok.md b/Tests/.TestSites/01/content/publishdate-ok.md similarity index 100% rename from test/.TestSites/01/content/publishdate-ok.md rename to Tests/.TestSites/01/content/publishdate-ok.md diff --git a/test/.TestSites/01/content/test01.md b/Tests/.TestSites/01/content/test01.md similarity index 100% rename from test/.TestSites/01/content/test01.md rename to Tests/.TestSites/01/content/test01.md diff --git a/test/.TestSites/01/sucos.yaml b/Tests/.TestSites/01/sucos.yaml similarity index 100% rename from test/.TestSites/01/sucos.yaml rename to Tests/.TestSites/01/sucos.yaml diff --git a/test/.TestSites/02-have-index/content/alias.md b/Tests/.TestSites/02-have-index/content/alias.md similarity index 100% rename from test/.TestSites/02-have-index/content/alias.md rename to Tests/.TestSites/02-have-index/content/alias.md diff --git a/test/.TestSites/02-have-index/content/categories.md b/Tests/.TestSites/02-have-index/content/categories.md similarity index 100% rename from test/.TestSites/02-have-index/content/categories.md rename to Tests/.TestSites/02-have-index/content/categories.md diff --git a/test/.TestSites/02-have-index/content/date-future.md b/Tests/.TestSites/02-have-index/content/date-future.md similarity index 100% rename from test/.TestSites/02-have-index/content/date-future.md rename to Tests/.TestSites/02-have-index/content/date-future.md diff --git a/test/.TestSites/02-have-index/content/date-ok.md b/Tests/.TestSites/02-have-index/content/date-ok.md similarity index 100% rename from test/.TestSites/02-have-index/content/date-ok.md rename to Tests/.TestSites/02-have-index/content/date-ok.md diff --git a/test/.TestSites/02-have-index/content/expired.md b/Tests/.TestSites/02-have-index/content/expired.md similarity index 100% rename from test/.TestSites/02-have-index/content/expired.md rename to Tests/.TestSites/02-have-index/content/expired.md diff --git a/test/.TestSites/02-have-index/content/index.md b/Tests/.TestSites/02-have-index/content/index.md similarity index 100% rename from test/.TestSites/02-have-index/content/index.md rename to Tests/.TestSites/02-have-index/content/index.md diff --git a/test/.TestSites/02-have-index/content/publishdate-future.md b/Tests/.TestSites/02-have-index/content/publishdate-future.md similarity index 100% rename from test/.TestSites/02-have-index/content/publishdate-future.md rename to Tests/.TestSites/02-have-index/content/publishdate-future.md diff --git a/test/.TestSites/02-have-index/content/publishdate-ok.md b/Tests/.TestSites/02-have-index/content/publishdate-ok.md similarity index 100% rename from test/.TestSites/02-have-index/content/publishdate-ok.md rename to Tests/.TestSites/02-have-index/content/publishdate-ok.md diff --git a/test/.TestSites/02-have-index/content/test01.md b/Tests/.TestSites/02-have-index/content/test01.md similarity index 100% rename from test/.TestSites/02-have-index/content/test01.md rename to Tests/.TestSites/02-have-index/content/test01.md diff --git a/test/.TestSites/02-have-index/sucos.yaml b/Tests/.TestSites/02-have-index/sucos.yaml similarity index 100% rename from test/.TestSites/02-have-index/sucos.yaml rename to Tests/.TestSites/02-have-index/sucos.yaml diff --git a/test/.TestSites/03-section/content/blog/alias.md b/Tests/.TestSites/03-section/content/blog/alias.md similarity index 100% rename from test/.TestSites/03-section/content/blog/alias.md rename to Tests/.TestSites/03-section/content/blog/alias.md diff --git a/test/.TestSites/03-section/content/blog/categories.md b/Tests/.TestSites/03-section/content/blog/categories.md similarity index 100% rename from test/.TestSites/03-section/content/blog/categories.md rename to Tests/.TestSites/03-section/content/blog/categories.md diff --git a/test/.TestSites/03-section/content/blog/date-future.md b/Tests/.TestSites/03-section/content/blog/date-future.md similarity index 100% rename from test/.TestSites/03-section/content/blog/date-future.md rename to Tests/.TestSites/03-section/content/blog/date-future.md diff --git a/test/.TestSites/03-section/content/blog/date-ok.md b/Tests/.TestSites/03-section/content/blog/date-ok.md similarity index 100% rename from test/.TestSites/03-section/content/blog/date-ok.md rename to Tests/.TestSites/03-section/content/blog/date-ok.md diff --git a/test/.TestSites/03-section/content/blog/expired.md b/Tests/.TestSites/03-section/content/blog/expired.md similarity index 100% rename from test/.TestSites/03-section/content/blog/expired.md rename to Tests/.TestSites/03-section/content/blog/expired.md diff --git a/test/.TestSites/03-section/content/blog/publishdate-future.md b/Tests/.TestSites/03-section/content/blog/publishdate-future.md similarity index 100% rename from test/.TestSites/03-section/content/blog/publishdate-future.md rename to Tests/.TestSites/03-section/content/blog/publishdate-future.md diff --git a/test/.TestSites/03-section/content/blog/publishdate-ok.md b/Tests/.TestSites/03-section/content/blog/publishdate-ok.md similarity index 100% rename from test/.TestSites/03-section/content/blog/publishdate-ok.md rename to Tests/.TestSites/03-section/content/blog/publishdate-ok.md diff --git a/test/.TestSites/03-section/content/blog/test01.md b/Tests/.TestSites/03-section/content/blog/test01.md similarity index 100% rename from test/.TestSites/03-section/content/blog/test01.md rename to Tests/.TestSites/03-section/content/blog/test01.md diff --git a/test/.TestSites/03-section/content/blog/weight-negative-1.md b/Tests/.TestSites/03-section/content/blog/weight-negative-1.md similarity index 100% rename from test/.TestSites/03-section/content/blog/weight-negative-1.md rename to Tests/.TestSites/03-section/content/blog/weight-negative-1.md diff --git a/test/.TestSites/03-section/content/blog/weight-negative-100.md b/Tests/.TestSites/03-section/content/blog/weight-negative-100.md similarity index 100% rename from test/.TestSites/03-section/content/blog/weight-negative-100.md rename to Tests/.TestSites/03-section/content/blog/weight-negative-100.md diff --git a/test/.TestSites/03-section/content/blog/weight-positive-1.md b/Tests/.TestSites/03-section/content/blog/weight-positive-1.md similarity index 100% rename from test/.TestSites/03-section/content/blog/weight-positive-1.md rename to Tests/.TestSites/03-section/content/blog/weight-positive-1.md diff --git a/test/.TestSites/03-section/content/blog/weight-positive-100.md b/Tests/.TestSites/03-section/content/blog/weight-positive-100.md similarity index 100% rename from test/.TestSites/03-section/content/blog/weight-positive-100.md rename to Tests/.TestSites/03-section/content/blog/weight-positive-100.md diff --git a/test/.TestSites/03-section/sucos.yaml b/Tests/.TestSites/03-section/sucos.yaml similarity index 100% rename from test/.TestSites/03-section/sucos.yaml rename to Tests/.TestSites/03-section/sucos.yaml diff --git a/test/.TestSites/04-tags/content/blog/categories.md b/Tests/.TestSites/04-tags/content/blog/categories.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/categories.md rename to Tests/.TestSites/04-tags/content/blog/categories.md diff --git a/test/.TestSites/04-tags/content/blog/date-future.md b/Tests/.TestSites/04-tags/content/blog/date-future.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/date-future.md rename to Tests/.TestSites/04-tags/content/blog/date-future.md diff --git a/test/.TestSites/04-tags/content/blog/date-ok.md b/Tests/.TestSites/04-tags/content/blog/date-ok.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/date-ok.md rename to Tests/.TestSites/04-tags/content/blog/date-ok.md diff --git a/test/.TestSites/04-tags/content/blog/expired.md b/Tests/.TestSites/04-tags/content/blog/expired.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/expired.md rename to Tests/.TestSites/04-tags/content/blog/expired.md diff --git a/test/.TestSites/04-tags/content/blog/publishdate-future.md b/Tests/.TestSites/04-tags/content/blog/publishdate-future.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/publishdate-future.md rename to Tests/.TestSites/04-tags/content/blog/publishdate-future.md diff --git a/test/.TestSites/04-tags/content/blog/publishdate-ok.md b/Tests/.TestSites/04-tags/content/blog/publishdate-ok.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/publishdate-ok.md rename to Tests/.TestSites/04-tags/content/blog/publishdate-ok.md diff --git a/test/.TestSites/04-tags/content/blog/subsection/alias.md b/Tests/.TestSites/04-tags/content/blog/subsection/alias.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/subsection/alias.md rename to Tests/.TestSites/04-tags/content/blog/subsection/alias.md diff --git a/test/.TestSites/04-tags/content/blog/tags-01.md b/Tests/.TestSites/04-tags/content/blog/tags-01.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/tags-01.md rename to Tests/.TestSites/04-tags/content/blog/tags-01.md diff --git a/test/.TestSites/04-tags/content/blog/tags-02.md b/Tests/.TestSites/04-tags/content/blog/tags-02.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/tags-02.md rename to Tests/.TestSites/04-tags/content/blog/tags-02.md diff --git a/test/.TestSites/04-tags/content/blog/tags-03.md b/Tests/.TestSites/04-tags/content/blog/tags-03.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/tags-03.md rename to Tests/.TestSites/04-tags/content/blog/tags-03.md diff --git a/test/.TestSites/04-tags/content/blog/tags-04.md b/Tests/.TestSites/04-tags/content/blog/tags-04.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/tags-04.md rename to Tests/.TestSites/04-tags/content/blog/tags-04.md diff --git a/test/.TestSites/04-tags/content/blog/tags-05.md b/Tests/.TestSites/04-tags/content/blog/tags-05.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/tags-05.md rename to Tests/.TestSites/04-tags/content/blog/tags-05.md diff --git a/test/.TestSites/04-tags/content/blog/tags-06.md b/Tests/.TestSites/04-tags/content/blog/tags-06.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/tags-06.md rename to Tests/.TestSites/04-tags/content/blog/tags-06.md diff --git a/test/.TestSites/04-tags/content/blog/tags-07.md b/Tests/.TestSites/04-tags/content/blog/tags-07.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/tags-07.md rename to Tests/.TestSites/04-tags/content/blog/tags-07.md diff --git a/test/.TestSites/04-tags/content/blog/tags-08.md b/Tests/.TestSites/04-tags/content/blog/tags-08.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/tags-08.md rename to Tests/.TestSites/04-tags/content/blog/tags-08.md diff --git a/test/.TestSites/04-tags/content/blog/tags-09.md b/Tests/.TestSites/04-tags/content/blog/tags-09.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/tags-09.md rename to Tests/.TestSites/04-tags/content/blog/tags-09.md diff --git a/test/.TestSites/04-tags/content/blog/tags-10.md b/Tests/.TestSites/04-tags/content/blog/tags-10.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/tags-10.md rename to Tests/.TestSites/04-tags/content/blog/tags-10.md diff --git a/test/.TestSites/04-tags/content/blog/test01.md b/Tests/.TestSites/04-tags/content/blog/test01.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/test01.md rename to Tests/.TestSites/04-tags/content/blog/test01.md diff --git a/test/.TestSites/04-tags/content/blog/weight-negative-1.md b/Tests/.TestSites/04-tags/content/blog/weight-negative-1.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/weight-negative-1.md rename to Tests/.TestSites/04-tags/content/blog/weight-negative-1.md diff --git a/test/.TestSites/04-tags/content/blog/weight-negative-100.md b/Tests/.TestSites/04-tags/content/blog/weight-negative-100.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/weight-negative-100.md rename to Tests/.TestSites/04-tags/content/blog/weight-negative-100.md diff --git a/test/.TestSites/04-tags/content/blog/weight-positive-1.md b/Tests/.TestSites/04-tags/content/blog/weight-positive-1.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/weight-positive-1.md rename to Tests/.TestSites/04-tags/content/blog/weight-positive-1.md diff --git a/test/.TestSites/04-tags/content/blog/weight-positive-100.md b/Tests/.TestSites/04-tags/content/blog/weight-positive-100.md similarity index 100% rename from test/.TestSites/04-tags/content/blog/weight-positive-100.md rename to Tests/.TestSites/04-tags/content/blog/weight-positive-100.md diff --git a/test/.TestSites/04-tags/content/index.md b/Tests/.TestSites/04-tags/content/index.md similarity index 100% rename from test/.TestSites/04-tags/content/index.md rename to Tests/.TestSites/04-tags/content/index.md diff --git a/test/.TestSites/04-tags/sucos.yaml b/Tests/.TestSites/04-tags/sucos.yaml similarity index 100% rename from test/.TestSites/04-tags/sucos.yaml rename to Tests/.TestSites/04-tags/sucos.yaml diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/alias.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/alias.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/alias.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/alias.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/categories.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/categories.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/categories.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/categories.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/date-future.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/date-future.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/date-future.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/date-future.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/date-ok.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/date-ok.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/date-ok.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/date-ok.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/expired.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/expired.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/expired.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/expired.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/publishdate-future.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/publishdate-future.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/publishdate-future.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/publishdate-future.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/publishdate-ok.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/publishdate-ok.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/publishdate-ok.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/publishdate-ok.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/tags-01.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/tags-01.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/tags-01.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/tags-01.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/tags-02.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/tags-02.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/tags-02.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/tags-02.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/test01.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/test01.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/test01.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/test01.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-1.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/weight-negative-1.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-1.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/weight-negative-1.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-100.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/weight-negative-100.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-100.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/weight-negative-100.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-1.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/weight-positive-1.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-1.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/weight-positive-1.md diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-100.md b/Tests/.TestSites/05-theme-no-baseof/content/blog/weight-positive-100.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-100.md rename to Tests/.TestSites/05-theme-no-baseof/content/blog/weight-positive-100.md diff --git a/test/.TestSites/05-theme-no-baseof/content/index.md b/Tests/.TestSites/05-theme-no-baseof/content/index.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/content/index.md rename to Tests/.TestSites/05-theme-no-baseof/content/index.md diff --git a/test/.TestSites/05-theme-no-baseof/index.md b/Tests/.TestSites/05-theme-no-baseof/index.md similarity index 100% rename from test/.TestSites/05-theme-no-baseof/index.md rename to Tests/.TestSites/05-theme-no-baseof/index.md diff --git a/test/.TestSites/05-theme-no-baseof/sucos.yaml b/Tests/.TestSites/05-theme-no-baseof/sucos.yaml similarity index 100% rename from test/.TestSites/05-theme-no-baseof/sucos.yaml rename to Tests/.TestSites/05-theme-no-baseof/sucos.yaml diff --git a/test/.TestSites/05-theme-no-baseof/themes/test/_default/index.liquid b/Tests/.TestSites/05-theme-no-baseof/themes/test/_default/index.liquid similarity index 100% rename from test/.TestSites/05-theme-no-baseof/themes/test/_default/index.liquid rename to Tests/.TestSites/05-theme-no-baseof/themes/test/_default/index.liquid diff --git a/test/.TestSites/05-theme-no-baseof/themes/test/_default/list.liquid b/Tests/.TestSites/05-theme-no-baseof/themes/test/_default/list.liquid similarity index 100% rename from test/.TestSites/05-theme-no-baseof/themes/test/_default/list.liquid rename to Tests/.TestSites/05-theme-no-baseof/themes/test/_default/list.liquid diff --git a/test/.TestSites/05-theme-no-baseof/themes/test/_default/single.liquid b/Tests/.TestSites/05-theme-no-baseof/themes/test/_default/single.liquid similarity index 100% rename from test/.TestSites/05-theme-no-baseof/themes/test/_default/single.liquid rename to Tests/.TestSites/05-theme-no-baseof/themes/test/_default/single.liquid diff --git a/test/.TestSites/06-theme/content/blog/alias.md b/Tests/.TestSites/06-theme/content/blog/alias.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/alias.md rename to Tests/.TestSites/06-theme/content/blog/alias.md diff --git a/test/.TestSites/06-theme/content/blog/categories.md b/Tests/.TestSites/06-theme/content/blog/categories.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/categories.md rename to Tests/.TestSites/06-theme/content/blog/categories.md diff --git a/test/.TestSites/06-theme/content/blog/date-future.md b/Tests/.TestSites/06-theme/content/blog/date-future.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/date-future.md rename to Tests/.TestSites/06-theme/content/blog/date-future.md diff --git a/test/.TestSites/06-theme/content/blog/date-ok.md b/Tests/.TestSites/06-theme/content/blog/date-ok.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/date-ok.md rename to Tests/.TestSites/06-theme/content/blog/date-ok.md diff --git a/test/.TestSites/06-theme/content/blog/expired.md b/Tests/.TestSites/06-theme/content/blog/expired.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/expired.md rename to Tests/.TestSites/06-theme/content/blog/expired.md diff --git a/test/.TestSites/06-theme/content/blog/publishdate-future.md b/Tests/.TestSites/06-theme/content/blog/publishdate-future.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/publishdate-future.md rename to Tests/.TestSites/06-theme/content/blog/publishdate-future.md diff --git a/test/.TestSites/06-theme/content/blog/publishdate-ok.md b/Tests/.TestSites/06-theme/content/blog/publishdate-ok.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/publishdate-ok.md rename to Tests/.TestSites/06-theme/content/blog/publishdate-ok.md diff --git a/test/.TestSites/06-theme/content/blog/tags-01.md b/Tests/.TestSites/06-theme/content/blog/tags-01.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/tags-01.md rename to Tests/.TestSites/06-theme/content/blog/tags-01.md diff --git a/test/.TestSites/06-theme/content/blog/tags-02.md b/Tests/.TestSites/06-theme/content/blog/tags-02.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/tags-02.md rename to Tests/.TestSites/06-theme/content/blog/tags-02.md diff --git a/test/.TestSites/06-theme/content/blog/test01.md b/Tests/.TestSites/06-theme/content/blog/test01.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/test01.md rename to Tests/.TestSites/06-theme/content/blog/test01.md diff --git a/test/.TestSites/06-theme/content/blog/weight-negative-1.md b/Tests/.TestSites/06-theme/content/blog/weight-negative-1.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/weight-negative-1.md rename to Tests/.TestSites/06-theme/content/blog/weight-negative-1.md diff --git a/test/.TestSites/06-theme/content/blog/weight-negative-100.md b/Tests/.TestSites/06-theme/content/blog/weight-negative-100.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/weight-negative-100.md rename to Tests/.TestSites/06-theme/content/blog/weight-negative-100.md diff --git a/test/.TestSites/06-theme/content/blog/weight-positive-1.md b/Tests/.TestSites/06-theme/content/blog/weight-positive-1.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/weight-positive-1.md rename to Tests/.TestSites/06-theme/content/blog/weight-positive-1.md diff --git a/test/.TestSites/06-theme/content/blog/weight-positive-100.md b/Tests/.TestSites/06-theme/content/blog/weight-positive-100.md similarity index 100% rename from test/.TestSites/06-theme/content/blog/weight-positive-100.md rename to Tests/.TestSites/06-theme/content/blog/weight-positive-100.md diff --git a/test/.TestSites/06-theme/content/index.md b/Tests/.TestSites/06-theme/content/index.md similarity index 100% rename from test/.TestSites/06-theme/content/index.md rename to Tests/.TestSites/06-theme/content/index.md diff --git a/test/.TestSites/06-theme/index.md b/Tests/.TestSites/06-theme/index.md similarity index 100% rename from test/.TestSites/06-theme/index.md rename to Tests/.TestSites/06-theme/index.md diff --git a/test/.TestSites/06-theme/sucos.yaml b/Tests/.TestSites/06-theme/sucos.yaml similarity index 100% rename from test/.TestSites/06-theme/sucos.yaml rename to Tests/.TestSites/06-theme/sucos.yaml diff --git a/test/.TestSites/06-theme/themes/test/_default/baseof.liquid b/Tests/.TestSites/06-theme/themes/test/_default/baseof.liquid similarity index 100% rename from test/.TestSites/06-theme/themes/test/_default/baseof.liquid rename to Tests/.TestSites/06-theme/themes/test/_default/baseof.liquid diff --git a/test/.TestSites/06-theme/themes/test/_default/index.liquid b/Tests/.TestSites/06-theme/themes/test/_default/index.liquid similarity index 100% rename from test/.TestSites/06-theme/themes/test/_default/index.liquid rename to Tests/.TestSites/06-theme/themes/test/_default/index.liquid diff --git a/test/.TestSites/06-theme/themes/test/_default/list.liquid b/Tests/.TestSites/06-theme/themes/test/_default/list.liquid similarity index 100% rename from test/.TestSites/06-theme/themes/test/_default/list.liquid rename to Tests/.TestSites/06-theme/themes/test/_default/list.liquid diff --git a/test/.TestSites/06-theme/themes/test/_default/single.liquid b/Tests/.TestSites/06-theme/themes/test/_default/single.liquid similarity index 100% rename from test/.TestSites/06-theme/themes/test/_default/single.liquid rename to Tests/.TestSites/06-theme/themes/test/_default/single.liquid diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/alias.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/alias.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/alias.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/alias.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/categories.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/categories.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/categories.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/categories.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/date-future.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/date-future.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/date-future.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/date-future.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/date-ok.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/date-ok.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/date-ok.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/date-ok.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/expired.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/expired.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/expired.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/expired.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-future.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-future.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-future.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-future.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-ok.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-ok.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-ok.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-ok.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/tags-01.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/tags-01.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/tags-01.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/tags-01.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/tags-02.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/tags-02.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/tags-02.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/tags-02.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/test01.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/test01.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/test01.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/test01.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-1.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-1.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-1.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-1.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-100.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-100.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-100.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-100.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-1.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-1.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-1.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-1.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-100.md b/Tests/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-100.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-100.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-100.md diff --git a/test/.TestSites/07-theme-no-baseof-error/content/index.md b/Tests/.TestSites/07-theme-no-baseof-error/content/index.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/content/index.md rename to Tests/.TestSites/07-theme-no-baseof-error/content/index.md diff --git a/test/.TestSites/07-theme-no-baseof-error/index.md b/Tests/.TestSites/07-theme-no-baseof-error/index.md similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/index.md rename to Tests/.TestSites/07-theme-no-baseof-error/index.md diff --git a/test/.TestSites/07-theme-no-baseof-error/sucos.yaml b/Tests/.TestSites/07-theme-no-baseof-error/sucos.yaml similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/sucos.yaml rename to Tests/.TestSites/07-theme-no-baseof-error/sucos.yaml diff --git a/test/.TestSites/07-theme-no-baseof-error/themes/test/_default/index.liquid b/Tests/.TestSites/07-theme-no-baseof-error/themes/test/_default/index.liquid similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/themes/test/_default/index.liquid rename to Tests/.TestSites/07-theme-no-baseof-error/themes/test/_default/index.liquid diff --git a/test/.TestSites/07-theme-no-baseof-error/themes/test/_default/list.liquid b/Tests/.TestSites/07-theme-no-baseof-error/themes/test/_default/list.liquid similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/themes/test/_default/list.liquid rename to Tests/.TestSites/07-theme-no-baseof-error/themes/test/_default/list.liquid diff --git a/test/.TestSites/07-theme-no-baseof-error/themes/test/_default/single.liquid b/Tests/.TestSites/07-theme-no-baseof-error/themes/test/_default/single.liquid similarity index 100% rename from test/.TestSites/07-theme-no-baseof-error/themes/test/_default/single.liquid rename to Tests/.TestSites/07-theme-no-baseof-error/themes/test/_default/single.liquid diff --git a/test/.TestSites/08-theme-html/content/blog/alias.md b/Tests/.TestSites/08-theme-html/content/blog/alias.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/alias.md rename to Tests/.TestSites/08-theme-html/content/blog/alias.md diff --git a/test/.TestSites/08-theme-html/content/blog/categories.md b/Tests/.TestSites/08-theme-html/content/blog/categories.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/categories.md rename to Tests/.TestSites/08-theme-html/content/blog/categories.md diff --git a/test/.TestSites/08-theme-html/content/blog/date-future.md b/Tests/.TestSites/08-theme-html/content/blog/date-future.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/date-future.md rename to Tests/.TestSites/08-theme-html/content/blog/date-future.md diff --git a/test/.TestSites/08-theme-html/content/blog/date-ok.md b/Tests/.TestSites/08-theme-html/content/blog/date-ok.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/date-ok.md rename to Tests/.TestSites/08-theme-html/content/blog/date-ok.md diff --git a/test/.TestSites/08-theme-html/content/blog/expired.md b/Tests/.TestSites/08-theme-html/content/blog/expired.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/expired.md rename to Tests/.TestSites/08-theme-html/content/blog/expired.md diff --git a/test/.TestSites/08-theme-html/content/blog/publishdate-future.md b/Tests/.TestSites/08-theme-html/content/blog/publishdate-future.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/publishdate-future.md rename to Tests/.TestSites/08-theme-html/content/blog/publishdate-future.md diff --git a/test/.TestSites/08-theme-html/content/blog/publishdate-ok.md b/Tests/.TestSites/08-theme-html/content/blog/publishdate-ok.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/publishdate-ok.md rename to Tests/.TestSites/08-theme-html/content/blog/publishdate-ok.md diff --git a/test/.TestSites/08-theme-html/content/blog/tags-01.md b/Tests/.TestSites/08-theme-html/content/blog/tags-01.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/tags-01.md rename to Tests/.TestSites/08-theme-html/content/blog/tags-01.md diff --git a/test/.TestSites/08-theme-html/content/blog/tags-02.md b/Tests/.TestSites/08-theme-html/content/blog/tags-02.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/tags-02.md rename to Tests/.TestSites/08-theme-html/content/blog/tags-02.md diff --git a/test/.TestSites/08-theme-html/content/blog/test01.md b/Tests/.TestSites/08-theme-html/content/blog/test01.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/test01.md rename to Tests/.TestSites/08-theme-html/content/blog/test01.md diff --git a/test/.TestSites/08-theme-html/content/blog/weight-negative-1.md b/Tests/.TestSites/08-theme-html/content/blog/weight-negative-1.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/weight-negative-1.md rename to Tests/.TestSites/08-theme-html/content/blog/weight-negative-1.md diff --git a/test/.TestSites/08-theme-html/content/blog/weight-negative-100.md b/Tests/.TestSites/08-theme-html/content/blog/weight-negative-100.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/weight-negative-100.md rename to Tests/.TestSites/08-theme-html/content/blog/weight-negative-100.md diff --git a/test/.TestSites/08-theme-html/content/blog/weight-positive-1.md b/Tests/.TestSites/08-theme-html/content/blog/weight-positive-1.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/weight-positive-1.md rename to Tests/.TestSites/08-theme-html/content/blog/weight-positive-1.md diff --git a/test/.TestSites/08-theme-html/content/blog/weight-positive-100.md b/Tests/.TestSites/08-theme-html/content/blog/weight-positive-100.md similarity index 100% rename from test/.TestSites/08-theme-html/content/blog/weight-positive-100.md rename to Tests/.TestSites/08-theme-html/content/blog/weight-positive-100.md diff --git a/test/.TestSites/08-theme-html/content/index.md b/Tests/.TestSites/08-theme-html/content/index.md similarity index 100% rename from test/.TestSites/08-theme-html/content/index.md rename to Tests/.TestSites/08-theme-html/content/index.md diff --git a/test/.TestSites/08-theme-html/index.md b/Tests/.TestSites/08-theme-html/index.md similarity index 100% rename from test/.TestSites/08-theme-html/index.md rename to Tests/.TestSites/08-theme-html/index.md diff --git a/test/.TestSites/08-theme-html/sucos.yaml b/Tests/.TestSites/08-theme-html/sucos.yaml similarity index 100% rename from test/.TestSites/08-theme-html/sucos.yaml rename to Tests/.TestSites/08-theme-html/sucos.yaml diff --git a/test/.TestSites/08-theme-html/themes/test/_default/baseof.liquid b/Tests/.TestSites/08-theme-html/themes/test/_default/baseof.liquid similarity index 100% rename from test/.TestSites/08-theme-html/themes/test/_default/baseof.liquid rename to Tests/.TestSites/08-theme-html/themes/test/_default/baseof.liquid diff --git a/test/.TestSites/08-theme-html/themes/test/_default/index.liquid b/Tests/.TestSites/08-theme-html/themes/test/_default/index.liquid similarity index 100% rename from test/.TestSites/08-theme-html/themes/test/_default/index.liquid rename to Tests/.TestSites/08-theme-html/themes/test/_default/index.liquid diff --git a/test/.TestSites/08-theme-html/themes/test/_default/list.liquid b/Tests/.TestSites/08-theme-html/themes/test/_default/list.liquid similarity index 100% rename from test/.TestSites/08-theme-html/themes/test/_default/list.liquid rename to Tests/.TestSites/08-theme-html/themes/test/_default/list.liquid diff --git a/test/.TestSites/08-theme-html/themes/test/_default/single.liquid b/Tests/.TestSites/08-theme-html/themes/test/_default/single.liquid similarity index 100% rename from test/.TestSites/08-theme-html/themes/test/_default/single.liquid rename to Tests/.TestSites/08-theme-html/themes/test/_default/single.liquid diff --git a/test/Commands/BaseGeneratorCommandTests.cs b/Tests/Commands/BaseGeneratorCommandTests.cs similarity index 78% rename from test/Commands/BaseGeneratorCommandTests.cs rename to Tests/Commands/BaseGeneratorCommandTests.cs index ecfd6d2ca2ad22cf90a7c8e72791cd872ef7cd85..7e1a22f58332233903cda86674b561e4ec0c4573 100644 --- a/test/Commands/BaseGeneratorCommandTests.cs +++ b/Tests/Commands/BaseGeneratorCommandTests.cs @@ -1,42 +1,38 @@ using NSubstitute; using Serilog; -using SuCoS; using SuCoS.Models.CommandLineOptions; using SuCoS.TemplateEngine; using System.Reflection; +using SuCoS.Commands; +using SuCoS.Helpers; using Xunit; namespace Tests.Commands; public class BaseGeneratorCommandTests { - private static readonly IGenerateOptions testOptions = new GenerateOptions + private static readonly IGenerateOptions TestOptions = new GenerateOptions { SourceArgument = "test_source" }; - private static readonly ILogger testLogger = new LoggerConfiguration().CreateLogger(); + private static readonly ILogger TestLogger = new LoggerConfiguration().CreateLogger(); private class BaseGeneratorCommandStub(IGenerateOptions options, ILogger logger, IFileSystem fs) : BaseGeneratorCommand(options, logger, fs); - readonly IFileSystem fs; - - public BaseGeneratorCommandTests() - { - fs = Substitute.For(); - } + private readonly IFileSystem _fs = Substitute.For(); [Fact] public void Constructor_ShouldThrowArgumentNullException_WhenOptionsIsNull() { - _ = Assert.Throws(() => new BaseGeneratorCommandStub(null!, testLogger, fs)); + _ = Assert.Throws(() => new BaseGeneratorCommandStub(null!, TestLogger, _fs)); } [Fact] public void Constructor_ShouldThrowArgumentNullException_WhenLoggerIsNull() { - _ = Assert.Throws(() => new BaseGeneratorCommandStub(testOptions, null!, fs)); + _ = Assert.Throws(() => new BaseGeneratorCommandStub(TestOptions, null!, _fs)); } [Fact] diff --git a/test/Commands/BuildCommandTests.cs b/Tests/Commands/BuildCommandTests.cs similarity index 70% rename from test/Commands/BuildCommandTests.cs rename to Tests/Commands/BuildCommandTests.cs index eb1aef0b9e9f0832d696ef02dd526b8e02092fb1..10991131995c865b79928f5e8a34aedf3870979c 100644 --- a/test/Commands/BuildCommandTests.cs +++ b/Tests/Commands/BuildCommandTests.cs @@ -2,32 +2,33 @@ using SuCoS.Models.CommandLineOptions; using Xunit; using NSubstitute; using Serilog; -using SuCoS; +using SuCoS.Commands; +using SuCoS.Helpers; namespace Tests.Commands; public class BuildCommandTests { - readonly ILogger logger; - readonly IFileSystem fileSystem; - readonly BuildOptions options; + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + private readonly BuildOptions _options; public BuildCommandTests() { - logger = Substitute.For(); - fileSystem = Substitute.For(); - fileSystem.FileExists("./sucos.yaml").Returns(true); - fileSystem.FileReadAllText("./sucos.yaml").Returns(""" -Ttile: test + _logger = Substitute.For(); + _fileSystem = Substitute.For(); + _fileSystem.FileExists("./sucos.yaml").Returns(true); + _fileSystem.FileReadAllText("./sucos.yaml").Returns(""" +Title: test """); - options = new BuildOptions { Output = "test" }; + _options = new BuildOptions { Output = "test" }; } [Fact] public void Constructor_ShouldNotThrowException_WhenParametersAreValid() { // Act - var result = new BuildCommand(options, logger, fileSystem); + var result = new BuildCommand(_options, _logger, _fileSystem); // Assert Assert.IsType(result); @@ -37,14 +38,14 @@ Ttile: test public void Constructor_ShouldThrowArgumentNullException_WhenOptionsIsNull() { // Act and Assert - Assert.Throws(() => new BuildCommand(null!, logger, fileSystem)); + Assert.Throws(() => new BuildCommand(null!, _logger, _fileSystem)); } [Fact] public void Run() { // Act - var command = new BuildCommand(options, logger, fileSystem); + var command = new BuildCommand(_options, _logger, _fileSystem); var result = command.Run(); // Assert @@ -95,27 +96,27 @@ Ttile: test public void CopyFolder_ShouldCallCreateDirectory_WhenSourceFolderExists() { // Arrange - fileSystem.DirectoryExists("sourceFolder").Returns(true); - var buildCommand = new BuildCommand(options, logger, fileSystem); + _fileSystem.DirectoryExists("sourceFolder").Returns(true); + var buildCommand = new BuildCommand(_options, _logger, _fileSystem); // Act buildCommand.CopyFolder("sourceFolder", "outputFolder"); // Assert - fileSystem.Received(1).DirectoryCreateDirectory("outputFolder"); + _fileSystem.Received(1).DirectoryCreateDirectory("outputFolder"); } [Fact] public void CopyFolder_ShouldNotCallCreateDirectory_WhenSourceFolderDoesNotExist() { // Arrange - fileSystem.DirectoryExists("sourceFolder").Returns(false); - var buildCommand = new BuildCommand(options, logger, fileSystem); + _fileSystem.DirectoryExists("sourceFolder").Returns(false); + var buildCommand = new BuildCommand(_options, _logger, _fileSystem); // Act buildCommand.CopyFolder("sourceFolder", "outputFolder"); // Assert - fileSystem.DidNotReceive().DirectoryCreateDirectory(Arg.Any()); + _fileSystem.DidNotReceive().DirectoryCreateDirectory(Arg.Any()); } } diff --git a/test/Commands/NewSiteCommandTests.cs b/Tests/Commands/NewSiteCommandTests.cs similarity index 53% rename from test/Commands/NewSiteCommandTests.cs rename to Tests/Commands/NewSiteCommandTests.cs index cbb44dd3eb6f4fa26bc8d4ed599c819748c85c95..b1480004df3d62dd16eb7707daa5098b878b2c18 100644 --- a/test/Commands/NewSiteCommandTests.cs +++ b/Tests/Commands/NewSiteCommandTests.cs @@ -1,33 +1,27 @@ -using SuCoS; using SuCoS.Models; using SuCoS.Models.CommandLineOptions; using Xunit; using NSubstitute; using Serilog; +using SuCoS.Commands; +using SuCoS.Helpers; namespace Tests.Commands; public class NewSiteCommandTests { - readonly ILogger logger; - readonly IFileSystem fileSystem; - readonly ISite site; - - public NewSiteCommandTests() - { - logger = Substitute.For(); - fileSystem = Substitute.For(); - site = Substitute.For(); - } + private readonly ILogger _logger = Substitute.For(); + private readonly IFileSystem _fileSystem = Substitute.For(); + private readonly ISite _site = Substitute.For(); [Fact] public void Create_ShouldReturnNewSiteCommand_WhenParametersAreValid() { // Arrange - var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com" }; + var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseUrl = "https://test.com" }; // Act - var result = NewSiteCommand.Create(options, logger, fileSystem); + var result = NewSiteCommand.Create(options, _logger, _fileSystem); // Assert Assert.IsType(result); @@ -37,17 +31,17 @@ public class NewSiteCommandTests public void Create_ShouldThrowArgumentNullException_WhenOptionsIsNull() { // Act and Assert - Assert.Throws(testCode: () => NewSiteCommand.Create(null!, logger, fileSystem)); + Assert.Throws(testCode: () => NewSiteCommand.Create(null!, _logger, _fileSystem)); } [Fact] public void Create_ShouldNotReturnNull_WhenParametersAreValid() { // Arrange - var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com" }; + var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseUrl = "https://test.com" }; // Act - var result = NewSiteCommand.Create(options, logger, fileSystem); + var result = NewSiteCommand.Create(options, _logger, _fileSystem); // Assert Assert.NotNull(result); @@ -57,47 +51,47 @@ public class NewSiteCommandTests public void Run_ShouldLogInformation_WhenCreatingNewSite() { // Arrange - var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com", Force = false }; - fileSystem.FileExists(Arg.Any()).Returns(false); + var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseUrl = "https://test.com", Force = false }; + _fileSystem.FileExists(Arg.Any()).Returns(false); - var newSiteCommand = NewSiteCommand.Create(options, logger, fileSystem); + var newSiteCommand = NewSiteCommand.Create(options, _logger, _fileSystem); // Act newSiteCommand.Run(); // Assert - logger.Received(1).Information("Creating a new site: {title} at {outputPath}", options.Title, Arg.Any()); + _logger.Received(1).Information("Creating a new site: {title} at {outputPath}", options.Title, Arg.Any()); } [Fact] public void Run_ShouldCallCreateDirectoryWithCorrectPaths_ForEachFolder() { // Arrange - var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com", Force = false }; - site.SourceFolders.Returns(["folder1", "folder2"]); - fileSystem.FileExists(Arg.Any()).Returns(false); + var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseUrl = "https://test.com", Force = false }; + _site.SourceFolders.Returns(["folder1", "folder2"]); + _fileSystem.FileExists(Arg.Any()).Returns(false); - var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + var newSiteCommand = new NewSiteCommand(options, _logger, _fileSystem, _site); // Act newSiteCommand.Run(); // Assert - fileSystem.Received(1).DirectoryCreateDirectory("folder1"); - fileSystem.Received(1).DirectoryCreateDirectory("folder2"); + _fileSystem.Received(1).DirectoryCreateDirectory("folder1"); + _fileSystem.Received(1).DirectoryCreateDirectory("folder2"); } [Fact] public void Run_ShouldReturn1_WhenCreateDirectoryThrowsException() { // Arrange - var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com", Force = false }; - site.SourceFolders.Returns(["folder1", "folder2"]); - fileSystem.FileExists(Arg.Any()).Returns(false); - fileSystem.When(x => x.DirectoryCreateDirectory(Arg.Any())) - .Do(x => { throw new ArgumentNullException(); }); + var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseUrl = "https://test.com", Force = false }; + _site.SourceFolders.Returns(["folder1", "folder2"]); + _fileSystem.FileExists(Arg.Any()).Returns(false); + _fileSystem.When(x => x.DirectoryCreateDirectory(Arg.Any())) + .Do(_ => throw new ArgumentNullException()); - var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + var newSiteCommand = new NewSiteCommand(options, _logger, _fileSystem, _site); // Act var result = newSiteCommand.Run(); @@ -111,9 +105,9 @@ public class NewSiteCommandTests { // Arrange var options = new NewSiteOptions { Output = "test", Force = false }; - fileSystem.FileExists(Arg.Any()).Returns(false); + _fileSystem.FileExists(Arg.Any()).Returns(false); - var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + var newSiteCommand = new NewSiteCommand(options, _logger, _fileSystem, _site); // Act var result = newSiteCommand.Run(); @@ -127,9 +121,9 @@ public class NewSiteCommandTests { // Arrange var options = new NewSiteOptions { Output = "test", Force = false }; - fileSystem.FileExists(Arg.Any()).Returns(true); + _fileSystem.FileExists(Arg.Any()).Returns(true); - var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + var newSiteCommand = new NewSiteCommand(options, _logger, _fileSystem, _site); // Act var result = newSiteCommand.Run(); @@ -143,9 +137,9 @@ public class NewSiteCommandTests { // Arrange var options = new NewSiteOptions { Output = "test", Force = true }; - fileSystem.FileExists(Arg.Any()).Returns(true); + _fileSystem.FileExists(Arg.Any()).Returns(true); - var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + var newSiteCommand = new NewSiteCommand(options, _logger, _fileSystem, _site); // Act var result = newSiteCommand.Run(); @@ -159,12 +153,12 @@ public class NewSiteCommandTests { // Arrange var options = new NewSiteOptions { Output = "test", Force = true }; - fileSystem.FileExists(Arg.Any()).Returns(false); - site.Parser + _fileSystem.FileExists(Arg.Any()).Returns(false); + _site.Parser .When(x => x.Export(Arg.Any(), Arg.Any())) - .Do(x => { throw new ArgumentNullException(); }); + .Do(_ => throw new ArgumentNullException()); - var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + var newSiteCommand = new NewSiteCommand(options, _logger, _fileSystem, _site); // Act var result = newSiteCommand.Run(); @@ -178,15 +172,15 @@ public class NewSiteCommandTests { // Arrange var options = new NewSiteOptions { Output = "test", Force = false }; - site.SourceFolders.Returns(["folder1", "folder2"]); - fileSystem.FileExists(Arg.Any()).Returns(false); + _site.SourceFolders.Returns(["folder1", "folder2"]); + _fileSystem.FileExists(Arg.Any()).Returns(false); - var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + var newSiteCommand = new NewSiteCommand(options, _logger, _fileSystem, _site); // Act newSiteCommand.Run(); // Assert - fileSystem.Received(2).DirectoryCreateDirectory(Arg.Any()); + _fileSystem.Received(2).DirectoryCreateDirectory(Arg.Any()); } } diff --git a/test/Helpers/StopwatchReporterTests.cs b/Tests/Helpers/StopwatchReporterTests.cs similarity index 69% rename from test/Helpers/StopwatchReporterTests.cs rename to Tests/Helpers/StopwatchReporterTests.cs index b2e67db8e8b641d5c79422e6098b4f2ecfe757a6..adde67c117482bf574b6045175258cea39c42186 100644 --- a/test/Helpers/StopwatchReporterTests.cs +++ b/Tests/Helpers/StopwatchReporterTests.cs @@ -11,27 +11,27 @@ namespace Tests.Helpers; public class StopwatchReporterTests { - private readonly ILogger logger; - private readonly InMemorySink inMemorySink; + private readonly ILogger _logger; + private readonly InMemorySink _inMemorySink; public StopwatchReporterTests() { - inMemorySink = new InMemorySink(); - logger = new LoggerConfiguration().WriteTo.Sink(inMemorySink).CreateLogger(); + _inMemorySink = new InMemorySink(); + _logger = new LoggerConfiguration().WriteTo.Sink(_inMemorySink).CreateLogger(); } [Fact] public void Start_InitializesAndStartsStopwatchForStep() { // Arrange - var stepName = "TestStep"; + const string stepName = "TestStep"; var stopwatchReporter = new StopwatchReporter(new LoggerConfiguration().CreateLogger()); // Act stopwatchReporter.Start(stepName); // Assert - var stopwatchField = stopwatchReporter.GetType().GetField("stopwatches", BindingFlags.NonPublic | BindingFlags.Instance); + var stopwatchField = stopwatchReporter.GetType().GetField("_stopwatches", BindingFlags.NonPublic | BindingFlags.Instance); Assert.NotNull(stopwatchField); var stopwatchDictionary = stopwatchField.GetValue(stopwatchReporter) as Dictionary; @@ -44,11 +44,11 @@ public class StopwatchReporterTests [Fact] public void LogReport_CorrectlyLogsElapsedTime() { - var stepName = "TestStep"; - var siteTitle = "TestSite"; - var duration = 123; + const string stepName = "TestStep"; + const string siteTitle = "TestSite"; + const int duration = 123; - var stopwatchReporter = new StopwatchReporter(logger); + var stopwatchReporter = new StopwatchReporter(_logger); stopwatchReporter.Start(stepName); Thread.Sleep(duration); // Let's wait a bit to simulate some processing. stopwatchReporter.Stop(stepName, 1); @@ -56,9 +56,10 @@ public class StopwatchReporterTests stopwatchReporter.LogReport(siteTitle); // Assert - var logEvents = inMemorySink.LogEvents; - Assert.NotEmpty(logEvents); - var logMessage = logEvents.First().RenderMessage(CultureInfo.InvariantCulture); + var logEvents = _inMemorySink.LogEvents; + var logEventsList = logEvents.ToList(); + Assert.NotEmpty(logEventsList); + var logMessage = logEventsList.First().RenderMessage(CultureInfo.InvariantCulture); Assert.Contains($"Site '{siteTitle}' created!", logMessage, StringComparison.InvariantCulture); Assert.Contains(stepName, logMessage, StringComparison.InvariantCulture); // Assert.Contains($"{duration} ms", logMessage, StringComparison.InvariantCulture); // Ensure that our processing time was logged. @@ -67,8 +68,8 @@ public class StopwatchReporterTests [Fact] public void Stop_ThrowsExceptionWhenStopCalledWithoutStart() { - var stepName = "TestStep"; - var stopwatchReporter = new StopwatchReporter(logger); + const string stepName = "TestStep"; + var stopwatchReporter = new StopwatchReporter(_logger); // Don't call Start for stepName diff --git a/test/Helpers/UrlizerTests.cs b/Tests/Helpers/UrlizerTests.cs similarity index 100% rename from test/Helpers/UrlizerTests.cs rename to Tests/Helpers/UrlizerTests.cs diff --git a/test/Models/FrontMatterTests.cs b/Tests/Models/FrontMatterTests.cs similarity index 94% rename from test/Models/FrontMatterTests.cs rename to Tests/Models/FrontMatterTests.cs index bb33cdd59fdccf0b08a9f8b69c6d2c3bd73fbc24..2b01efc7333590b27c6909fc9d0418373472b5d2 100644 --- a/test/Models/FrontMatterTests.cs +++ b/Tests/Models/FrontMatterTests.cs @@ -7,9 +7,9 @@ namespace Tests.Models; public class FrontMatterTests : TestSetup { [Theory] - [InlineData("Title1", "Section1", "Type1", "URL1", Kind.single)] - [InlineData("Title2", "Section2", "Type2", "URL2", Kind.list)] - [InlineData("Title3", "Section3", "Type3", "URL3", Kind.index)] + [InlineData("Title1", "Section1", "Type1", "URL1", Kind.Single)] + [InlineData("Title2", "Section2", "Type2", "URL2", Kind.List)] + [InlineData("Title3", "Section3", "Type3", "URL3", Kind.Index)] public void Constructor_Sets_Properties_Correctly(string title, string section, string type, string url, Kind kind) { // Act diff --git a/test/Models/PageTests.cs b/Tests/Models/PageTests.cs similarity index 71% rename from test/Models/PageTests.cs rename to Tests/Models/PageTests.cs index 8dbb6c869742c7e28122e88510985c458ac01fc1..d3d0516d6d5eb732effd3042b67dd38a30257ab0 100644 --- a/test/Models/PageTests.cs +++ b/Tests/Models/PageTests.cs @@ -8,7 +8,7 @@ namespace Tests.Models; public class PageTests : TestSetup { - private const string markdown1CONST = @" + private const string Markdown1Const = """ # word01 word02 word03 word04 word05 6 7 eight @@ -17,43 +17,49 @@ word03 word04 word05 6 7 eight ```cs console.WriteLine('hello word') -```"; - private const string markdown2CONST = @" +``` +"""; + private const string Markdown2Const = """ # word01 word02 -word03 word04 word05 6 7 [eight](http://example.com)"; - private const string markdownPlain1CONST = @"word01 word02 -word03 word04 word05 6 7 eight -nine -console.WriteLine('hello word') -"; - private const string markdownPlain2CONST = @"word01 word02 -word03 word04 word05 6 7 eight -"; +word03 word04 word05 6 7 [eight](https://example.com) +"""; + private const string MarkdownPlain1Const = """ + word01 word02 + word03 word04 word05 6 7 eight + nine + console.WriteLine('hello word') + + """; + private const string MarkdownPlain2Const = """ + word01 word02 + word03 word04 word05 6 7 eight + + """; [Theory] [InlineData("Test Title", "/path/to/file.md", "file", "/path/to")] - public void Frontmatter_ShouldCreateWithCorrectProperties(string title, string sourcePath, string sourceFileNameWithoutExtension, string sourcePathDirectory) + public void FrontMatter_ShouldCreateWithCorrectProperties(string title, string sourcePath, string sourceFileNameWithoutExtension, string sourcePathDirectory) { - var page = new Page(frontMatterMock, site); + var page = new Page(FrontMatterMock, Site); // Assert Assert.Equal(title, page.Title); Assert.Equal(sourcePath, page.SourceRelativePath); - Assert.Same(site, page.Site); + Assert.Same(Site, page.Site); Assert.Equal(sourceFileNameWithoutExtension, page.SourceFileNameWithoutExtension); Assert.Equal(sourcePathDirectory, page.SourceRelativePathDirectory); } [Fact] - public void Frontmatter_ShouldHaveDefaultValuesForOptionalProperties() + public void FrontMatter_ShouldHaveDefaultValuesForOptionalProperties() { // Arrange - var page = new Page(frontMatterMock, site); + var page = new Page(FrontMatterMock, Site); // Assert Assert.Equal(string.Empty, page.Section); - Assert.Equal(Kind.single, page.Kind); + Assert.Equal(Kind.Single, page.Kind); Assert.Equal("page", page.Type); Assert.Null(page.URL); Assert.Empty(page.Params); @@ -63,13 +69,13 @@ word03 word04 word05 6 7 eight Assert.Null(page.ExpiryDate); Assert.Null(page.AliasesProcessed); Assert.Null(page.Permalink); - Assert.Empty(page.AllOutputURLs); + Assert.Empty(page.AllOutputUrLs); Assert.Equal(string.Empty, page.RawContent); Assert.Empty(page.TagsReference); Assert.Empty(page.PagesReferences); Assert.Empty(page.RegularPages); - Assert.False(site.IsDateExpired(page)); - Assert.True(site.IsDatePublishable(page)); + Assert.False(Site.IsDateExpired(page)); + Assert.True(Site.IsDatePublishable(page)); } [Theory] @@ -79,17 +85,17 @@ word03 word04 word05 6 7 eight { var page = new Page(new FrontMatter { - Title = titleCONST, - SourceRelativePath = sourcePathCONST, - Aliases = new() { "v123", "{{ page.Title }}", "{{ page.Title }}-2" } - }, site); + Title = TitleConst, + SourceRelativePath = SourcePathConst, + Aliases = ["v123", "{{ page.Title }}", "{{ page.Title }}-2"] + }, Site); // Act - site.PostProcessPage(page); + Site.PostProcessPage(page); // Assert - Assert.Equal(3, site.OutputReferences.Count); - _ = site.OutputReferences.TryGetValue(url, out var pageOther); + Assert.Equal(3, Site.OutputReferences.Count); + _ = Site.OutputReferences.TryGetValue(url, out var pageOther); Assert.NotNull(pageOther); Assert.Same(page, pageOther); } @@ -101,13 +107,13 @@ word03 word04 word05 6 7 eight { var page = new Page(new FrontMatter { - Title = titleCONST, - SourceRelativePath = sourcePathCONST, - ExpiryDate = systemClockMock.Now.AddDays(days) - }, site); + Title = TitleConst, + SourceRelativePath = SourcePathConst, + ExpiryDate = SystemClockMock.Now.AddDays(days) + }, Site); // Assert - Assert.Equal(expected, site.IsDateExpired(page)); + Assert.Equal(expected, Site.IsDateExpired(page)); } [Theory] @@ -120,14 +126,14 @@ word03 word04 word05 6 7 eight { var page = new Page(new FrontMatter { - Title = titleCONST, - SourceRelativePath = sourcePathCONST, + Title = TitleConst, + SourceRelativePath = SourcePathConst, PublishDate = publishDate is null ? null : DateTime.Parse(publishDate, CultureInfo.InvariantCulture), Date = date is null ? null : DateTime.Parse(date, CultureInfo.InvariantCulture) - }, site); + }, Site); // Assert - Assert.Equal(expectedValue, site.IsDatePublishable(page)); + Assert.Equal(expectedValue, Site.IsDatePublishable(page)); } [Theory] @@ -171,18 +177,18 @@ word03 word04 word05 6 7 eight { var page = new Page(new FrontMatter { - Title = titleCONST, - SourceRelativePath = sourcePathCONST, + Title = TitleConst, + SourceRelativePath = SourcePathConst, PublishDate = publishDate is null ? null : DateTime.Parse(publishDate, CultureInfo.InvariantCulture), Date = date is null ? null : DateTime.Parse(date, CultureInfo.InvariantCulture), Draft = draft - }, site); + }, Site); var options = Substitute.For(); _ = options.Draft.Returns(draftOption); // Assert - Assert.Equal(expectedValue, site.IsValidPage(page, options)); + Assert.Equal(expectedValue, Site.IsValidPage(page, options)); } [Theory] @@ -192,17 +198,17 @@ word03 word04 word05 6 7 eight { var page = new Page(new FrontMatter { - Title = titleCONST, - SourceRelativePath = sourcePathCONST, - Date = systemClockMock.Now.AddDays(1) - }, site); + Title = TitleConst, + SourceRelativePath = SourcePathConst, + Date = SystemClockMock.Now.AddDays(1) + }, Site); // Act var options = Substitute.For(); _ = options.Future.Returns(futureOption); // Assert - Assert.Equal(expected, site.IsValidDate(page, options)); + Assert.Equal(expected, Site.IsValidDate(page, options)); } [Theory] @@ -212,9 +218,9 @@ word03 word04 word05 6 7 eight { var page = new Page(new FrontMatter { - Title = titleCONST, + Title = TitleConst, SourceRelativePath = sourcePath - }, site); + }, Site); // Assert Assert.Equal(expectedUrl, page.CreatePermalink()); @@ -227,10 +233,10 @@ word03 word04 word05 6 7 eight { var page = new Page(new FrontMatter { - Title = titleCONST, - SourceRelativePath = sourcePathCONST, + Title = TitleConst, + SourceRelativePath = SourcePathConst, URL = urlTemplate - }, site); + }, Site); var actualPermalink = page.CreatePermalink(); // Assert @@ -238,43 +244,43 @@ word03 word04 word05 6 7 eight } [Theory] - [InlineData(Kind.single, true)] - [InlineData(Kind.list, false)] + [InlineData(Kind.Single, true)] + [InlineData(Kind.List, false)] public void RegularPages_ShouldReturnCorrectPages_WhenKindIsSingle(Kind kind, bool isExpectedPage) { - var page = new Page(frontMatterMock, site) { Kind = kind }; + var page = new Page(FrontMatterMock, Site) { Kind = kind }; // Act - site.PostProcessPage(page); + Site.PostProcessPage(page); // Assert - Assert.Equal(isExpectedPage, site.RegularPages.Contains(page)); + Assert.Equal(isExpectedPage, Site.RegularPages.Contains(page)); } [Theory] - [InlineData(markdown1CONST, 13)] - [InlineData(markdown2CONST, 8)] + [InlineData(Markdown1Const, 13)] + [InlineData(Markdown2Const, 8)] public void WordCount_ShouldReturnCorrectCounts(string rawContent, int wordCountExpected) { var page = new Page(new FrontMatter { RawContent = rawContent - }, site); + }, Site); // Assert Assert.Equal(wordCountExpected, page.WordCount); } [Theory] - [InlineData(markdown1CONST, markdownPlain1CONST)] - [InlineData(markdown2CONST, markdownPlain2CONST)] + [InlineData(Markdown1Const, MarkdownPlain1Const)] + [InlineData(Markdown2Const, MarkdownPlain2Const)] public void Plain_ShouldReturnCorrectPlainString(string rawContent, string plain) { ArgumentException.ThrowIfNullOrEmpty(plain); var page = new Page(new FrontMatter { RawContent = rawContent - }, site); + }, Site); // Required to make the test pass on Windows plain = plain.Replace("\r\n", "\n", StringComparison.Ordinal); diff --git a/test/Models/SiteTests.cs b/Tests/Models/SiteTests.cs similarity index 56% rename from test/Models/SiteTests.cs rename to Tests/Models/SiteTests.cs index 8b6532c662501beb7703020b50744aed528f0f60..ee91f87b5d9ab1d7f5f855be5eeb452dff86ca4c 100644 --- a/test/Models/SiteTests.cs +++ b/Tests/Models/SiteTests.cs @@ -1,7 +1,7 @@ -using SuCoS; using SuCoS.Helpers; using SuCoS.Models; using SuCoS.Models.CommandLineOptions; +using SuCoS.Parsers; using Xunit; namespace Tests.Models; @@ -11,110 +11,114 @@ namespace Tests.Models; /// public class SiteTests : TestSetup { - readonly IFileSystem fs; - - public SiteTests() - { - fs = new FileSystem(); - } + private readonly IFileSystem _fs = new FileSystem(); [Theory] [InlineData("test01.md")] [InlineData("date-ok.md")] - public void ScanAllMarkdownFiles_ShouldCountainFilenames(string fileName) + public void ScanAllMarkdownFiles_ShouldContainFilenames(string fileName) { var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST01)); - site.Options = new GenerateOptions + var siteFullPath = Path.GetFullPath(Path.Combine(TestSitesPath, TestSitePathConst01)); + Site.Options = new GenerateOptions { SourceArgument = siteFullPath }; // Act - site.ParseAndScanSourceFiles(fs, Path.Combine(siteFullPath, "content")); + Site.ParseAndScanSourceFiles(_fs, Path.Combine(siteFullPath, "content")); // Assert - Assert.Contains(site.Pages, page => page.SourceRelativePathDirectory!.Length == 0); - Assert.Contains(site.Pages, page => page.SourceFileNameWithoutExtension == fileNameWithoutExtension); + Assert.Contains(Site.Pages, page => page.SourceRelativePathDirectory!.Length == 0); + Assert.Contains(Site.Pages, page => page.SourceFileNameWithoutExtension == fileNameWithoutExtension); } [Theory] - [InlineData(testSitePathCONST01)] - [InlineData(testSitePathCONST02)] + [InlineData(TestSitePathConst01)] + [InlineData(TestSitePathConst02)] public void Home_ShouldReturnAHomePage(string sitePath) { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, sitePath)) }; - site.Options = options; + Site.Options = options; // Act - site.ParseAndScanSourceFiles(fs, site.SourceContentPath); + Site.ParseAndScanSourceFiles(_fs, Site.SourceContentPath); // Assert - Assert.NotNull(site.Home); - Assert.True(site.Home.IsHome); - _ = Assert.Single(site.OutputReferences.Values.Where(output => output is IPage page && page.IsHome)); + Assert.NotNull(Site.Home); + Assert.True(Site.Home.IsHome); + _ = Assert.Single(Site.OutputReferences.Values.Where(output => output is IPage + { + IsHome: true + })); } [Theory] - [InlineData(testSitePathCONST01, 0)] - [InlineData(testSitePathCONST02, 0)] - [InlineData(testSitePathCONST03, 1)] + [InlineData(TestSitePathConst01, 0)] + [InlineData(TestSitePathConst02, 0)] + [InlineData(TestSitePathConst03, 1)] public void Page_IsSection_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, sitePath)) }; - site.Options = options; + Site.Options = options; // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - Assert.Equal(expectedQuantity, site.OutputReferences.Values.Where(output => output is IPage page && page.IsSection).Count()); + Assert.Equal(expectedQuantity, Site.OutputReferences.Values.Count(output => output is IPage + { + IsSection: true + })); } [Theory] - [InlineData(testSitePathCONST01, 5)] - [InlineData(testSitePathCONST02, 1)] - [InlineData(testSitePathCONST03, 13)] - [InlineData(testSitePathCONST04, 26)] + [InlineData(TestSitePathConst01, 5)] + [InlineData(TestSitePathConst02, 1)] + [InlineData(TestSitePathConst03, 13)] + [InlineData(TestSitePathConst04, 26)] public void PagesReference_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, sitePath)) }; - site.Options = options; + Site.Options = options; // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - Assert.Equal(expectedQuantity, site.OutputReferences.Values.Where(output => output is IPage page).Count()); + Assert.Equal(expectedQuantity, Site.OutputReferences.Values.Count(output => output is IPage)); } [Theory] - [InlineData(testSitePathCONST01, 4)] - [InlineData(testSitePathCONST02, 0)] - [InlineData(testSitePathCONST03, 11)] - [InlineData(testSitePathCONST04, 21)] + [InlineData(TestSitePathConst01, 4)] + [InlineData(TestSitePathConst02, 0)] + [InlineData(TestSitePathConst03, 11)] + [InlineData(TestSitePathConst04, 21)] public void Page_IsPage_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, sitePath)) }; - site.Options = options; + Site.Options = options; // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - Assert.Equal(expectedQuantity, site.OutputReferences.Values.Where(output => output is IPage page && page.IsPage).Count()); + Assert.Equal(expectedQuantity, Site.OutputReferences.Values.Count(output => output is IPage + { + IsPage: true + })); } [Fact] @@ -122,16 +126,16 @@ public class SiteTests : TestSetup { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST03)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, TestSitePathConst03)) }; - site.Options = options; + Site.Options = options; // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - Assert.Equal(100, site.RegularPages.First().Weight); - Assert.Equal(-100, site.RegularPages.Last().Weight); + Assert.Equal(100, Site.RegularPages.First().Weight); + Assert.Equal(-100, Site.RegularPages.Last().Weight); } [Fact] @@ -139,16 +143,16 @@ public class SiteTests : TestSetup { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST01)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, TestSitePathConst01)) }; - site.Options = options; + Site.Options = options; // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - Assert.Equal(0, site.RegularPages.First().Weight); - Assert.Equal(0, site.RegularPages.Last().Weight); + Assert.Equal(0, Site.RegularPages.First().Weight); + Assert.Equal(0, Site.RegularPages.Last().Weight); } [Fact] @@ -156,15 +160,15 @@ public class SiteTests : TestSetup { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, TestSitePathConst04)) }; - site.Options = options; + Site.Options = options; // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - _ = site.OutputReferences.TryGetValue("/tags", out var output); + _ = Site.OutputReferences.TryGetValue("/tags", out var output); var tagSectionPage = output as IPage; Assert.NotNull(tagSectionPage); Assert.Equal(2, tagSectionPage.Pages.Count()); @@ -179,15 +183,15 @@ public class SiteTests : TestSetup { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, TestSitePathConst04)) }; - site.Options = options; + Site.Options = options; // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - _ = site.OutputReferences.TryGetValue("/tags/tag1", out var output); + _ = Site.OutputReferences.TryGetValue("/tags/tag1", out var output); var page = output as IPage; Assert.NotNull(page); Assert.Equal(10, page.Pages.Count()); @@ -204,15 +208,15 @@ public class SiteTests : TestSetup { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, TestSitePathConst04)) }; - site.Options = options; + Site.Options = options; // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - _ = site.OutputReferences.TryGetValue(url, out var output); + _ = Site.OutputReferences.TryGetValue(url, out var output); var page = output as IPage; Assert.NotNull(page); Assert.Equal(expectedContent, page.Content); @@ -239,17 +243,17 @@ public class SiteTests : TestSetup { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST05)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, TestSitePathConst05)) }; - var parser = new SuCoS.Parser.YAMLParser(); - var siteSettings = SiteHelper.ParseSettings("sucos.yaml", options, parser, fs); - site = new Site(options, siteSettings, parser, loggerMock, null); + var parser = new YamlParser(); + var siteSettings = SiteHelper.ParseSettings("sucos.yaml", options, parser, _fs); + Site = new Site(options, siteSettings, parser, LoggerMock, null); // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - _ = site.OutputReferences.TryGetValue(url, out var output); + _ = Site.OutputReferences.TryGetValue(url, out var output); var page = output as IPage; Assert.NotNull(page); Assert.Equal(expectedContentPreRendered, page.ContentPreRendered); @@ -267,17 +271,17 @@ public class SiteTests : TestSetup { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST07)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, TestSitePathConst07)) }; - var parser = new SuCoS.Parser.YAMLParser(); - var siteSettings = SiteHelper.ParseSettings("sucos.yaml", options, parser, fs); - site = new Site(options, siteSettings, parser, loggerMock, null); + var parser = new YamlParser(); + var siteSettings = SiteHelper.ParseSettings("sucos.yaml", options, parser, _fs); + Site = new Site(options, siteSettings, parser, LoggerMock, null); // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - _ = site.OutputReferences.TryGetValue(url, out var output); + _ = Site.OutputReferences.TryGetValue(url, out var output); var page = output as IPage; Assert.NotNull(page); Assert.Equal(string.Empty, page.Content); @@ -305,25 +309,25 @@ public class SiteTests : TestSetup "

Test Content 1

\n", "SINGLE-

Test Content 1

\n", "BASEOF-SINGLE-

Test Content 1

\n")] - public void Page_Content_ShouldReturnThemeContent(string url, string expectedContentPreRendered, string expectedContent, string expectedOutputfile) + public void Page_Content_ShouldReturnThemeContent(string url, string expectedContentPreRendered, string expectedContent, string expectedOutputFile) { GenerateOptions options = new() { - SourceArgument = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST06)) + SourceArgument = Path.GetFullPath(Path.Combine(TestSitesPath, TestSitePathConst06)) }; - var parser = new SuCoS.Parser.YAMLParser(); - var siteSettings = SiteHelper.ParseSettings("sucos.yaml", options, parser, fs); - site = new Site(options, siteSettings, parser, loggerMock, null); + var parser = new YamlParser(); + var siteSettings = SiteHelper.ParseSettings("sucos.yaml", options, parser, _fs); + Site = new Site(options, siteSettings, parser, LoggerMock, null); // Act - site.ParseAndScanSourceFiles(fs, null); + Site.ParseAndScanSourceFiles(_fs, null); // Assert - _ = site.OutputReferences.TryGetValue(url, out var output); + _ = Site.OutputReferences.TryGetValue(url, out var output); var page = output as IPage; Assert.NotNull(page); Assert.Equal(expectedContentPreRendered, page.ContentPreRendered); Assert.Equal(expectedContent, page.Content); - Assert.Equal(expectedOutputfile, page.CompleteContent); + Assert.Equal(expectedOutputFile, page.CompleteContent); } } diff --git a/Tests/Parser/YAMLParserTests.cs b/Tests/Parser/YAMLParserTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..c63fdd37a171bb88ac918de569ea4e642c99cd7e --- /dev/null +++ b/Tests/Parser/YAMLParserTests.cs @@ -0,0 +1,285 @@ +using SuCoS.Helpers; +using SuCoS.Models; +using System.Globalization; +using SuCoS.Parsers; +using Xunit; + +namespace Tests.YAMLParser; + +public class YamlParserTests : TestSetup +{ + private readonly YamlParser _parser = new(); + + private const string PageFrontMatterConst = """ + Title: Test Title + Type: post + Date: 2023-07-01 + LastMod: 2023-06-01 + PublishDate: 2023-06-01 + ExpiryDate: 2024-06-01 + Tags: + - Test + - Real Data + Categories: + - Test + - Real Data + NestedData: + Level2: + - Test + - Real Data + customParam: Custom Value + Params: + ParamsCustomParam: Custom Value + ParamsNestedData: + Level2: + - Test + - Real Data + + """; + private const string PageMarkdownConst = """ + + # Real Data Test + + This is a test using real data. Real Data Test + + """; + private const string SiteContentConst = """ + + Title: My Site + BaseURL: https://www.example.com/ + Description: Tastiest C# Static Site Generator of the World + Copyright: Copyright message + customParam: Custom Value + NestedData: + Level2: + - Test + - Real Data + Params: + ParamsCustomParam: Custom Value + ParamsNestedData: + Level2: + - Test + - Real Data + + """; + private const string FileFullPathConst = "test.md"; + private const string FileRelativePathConst = "test.md"; + private const string PageContent = $""" + --- + {PageFrontMatterConst} + --- + {PageMarkdownConst} + """; + + private static readonly string[] Expected = ["Test", "Real Data"]; + + [Fact] + public void GetSection_ShouldReturnFirstFolderName() + { + // Arrange + var filePath = Path.Combine("folder1", "folder2", "file.md"); + + // Act + var section = SiteHelper.GetSection(filePath); + + // Assert + Assert.Equal("folder1", section); + } + + [Theory] + [InlineData(""" + --- + Title: Test Title + --- + + """, "Test Title")] + [InlineData(""" + --- + Date: 2023-04-01 + --- + + """, "")] + public void ParseFrontMatter_ShouldParseTitleCorrectly(string fileContent, string expectedTitle) + { + // Arrange + var frontMatter = FrontMatter.Parse(FileRelativePathConst, FileFullPathConst, _parser, fileContent); + + // Assert + Assert.Equal(expectedTitle, frontMatter.Title); + } + + [Theory] + [InlineData(""" + --- + Date: 2023-01-01 + --- + + """, "2023-01-01")] + [InlineData(""" + --- + Date: 2023/01/01 + --- + + """, "2023-01-01")] + public void ParseFrontMatter_ShouldParseDateCorrectly(string fileContent, string expectedDateString) + { + // Arrange + var expectedDate = DateTime.Parse(expectedDateString, CultureInfo.InvariantCulture); + + // Act + var frontMatter = FrontMatter.Parse(FileRelativePathConst, FileFullPathConst, _parser, fileContent); + + // Assert + Assert.Equal(expectedDate, frontMatter.Date); + } + + [Fact] + public void ParseFrontMatter_ShouldParseOtherFieldsCorrectly() + { + // Arrange + var expectedDate = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); + var expectedLastMod = DateTime.Parse("2023-06-01", CultureInfo.InvariantCulture); + var expectedPublishDate = DateTime.Parse("2023-06-01", CultureInfo.InvariantCulture); + var expectedExpiryDate = DateTime.Parse("2024-06-01", CultureInfo.InvariantCulture); + + // Act + var frontMatter = FrontMatter.Parse(FileRelativePathConst, FileFullPathConst, _parser, PageContent); + + // Assert + Assert.Equal("Test Title", frontMatter.Title); + Assert.Equal("post", frontMatter.Type); + Assert.Equal(expectedDate, frontMatter.Date); + Assert.Equal(expectedLastMod, frontMatter.LastMod); + Assert.Equal(expectedPublishDate, frontMatter.PublishDate); + Assert.Equal(expectedExpiryDate, frontMatter.ExpiryDate); + } + + [Fact] + public void ParseFrontMatter_ShouldThrowFormatException_WhenInvalidYAMLSyntax() + { + // Arrange + const string fileContent = """ + --- + Title + --- + + """; + + // Assert + Assert.Throws(() => + FrontMatter.Parse(FileRelativePathConst, FileFullPathConst, _parser, fileContent)); + } + + [Fact] + public void ParseSiteSettings_ShouldReturnSiteWithCorrectSettings() + { + // Act + var siteSettings = _parser.Parse(SiteContentConst); + + + // Assert + Assert.Equal("My Site", siteSettings.Title); + Assert.Equal("https://www.example.com/", siteSettings.BaseURL); + Assert.Equal("Tastiest C# Static Site Generator of the World", siteSettings.Description); + Assert.Equal("Copyright message", siteSettings.Copyright); + } + + [Fact] + public void ParseParams_ShouldFillParamsWithNonMatchingFields() + { + // Arrange + var frontMatter = FrontMatter.Parse(string.Empty, string.Empty, _parser, PageContent); + var page = new Page(frontMatter, Site); + + // Assert + Assert.False(page.Params.ContainsKey("customParam")); + Assert.Equal("Custom Value", page.Params["ParamsCustomParam"]); + } + + [Fact] + public void ParseFrontMatter_ShouldParseContentInSiteFolder() + { + // Arrange + var date = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); + var frontMatter = FrontMatter.Parse(string.Empty, string.Empty, _parser, PageContent); + Page page = new(frontMatter, Site); + + // Act + Site.PostProcessPage(page); + + // Assert + Assert.Equal(date, frontMatter.Date); + } + + [Fact] + public void ParseFrontMatter_ShouldCreateTags() + { + // Arrange + var frontMatter = FrontMatter.Parse(string.Empty, string.Empty, _parser, PageContent); + Page page = new(frontMatter, Site); + + // Act + Site.PostProcessPage(page); + + // Assert + Assert.Equal(2, page.TagsReference.Count); + } + + [Fact] + public void FrontMatterParse_RawContentNull() + { + _ = Assert.Throws(() => FrontMatter.Parse("invalidFrontMatter", "", "fakePath", "fakePath", FrontMatterParser)); + } + + [Fact] + public void ParseYAML_ShouldThrowExceptionWhenFrontMatterIsInvalid() + { + _ = Assert.Throws(() => _parser.Parse("invalidFrontMatter")); + } + + [Fact] + public void ParseYAML_ShouldSplitTheMetadata() + { + // Act + var (metadata, rawContent) = _parser.SplitFrontMatter(PageContent); + + // Assert + Assert.Equal(PageFrontMatterConst.TrimEnd(), metadata); + Assert.Equal(PageMarkdownConst, rawContent); + } + + [Fact] + public void ParseSiteSettings_ShouldReturnSiteSettings() + { + // Arrange + var siteSettings = _parser.Parse(SiteContentConst); + + // Assert + Assert.NotNull(siteSettings); + Assert.Equal("My Site", siteSettings.Title); + Assert.Equal("https://www.example.com/", siteSettings.BaseURL); + } + + + [Fact] + public void SiteParams_ShouldHandleEmptyContent() + { + Assert.Empty(Site.Params); + } + + [Fact] + public void SiteParams_ShouldPopulateParamsWithExtraFields() + { + // Arrange + var siteSettings = _parser.Parse(SiteContentConst); + Site = new Site(GenerateOptionsMock, siteSettings, FrontMatterParser, LoggerMock, SystemClockMock); + + // Assert + Assert.NotEmpty(siteSettings.Params); + Assert.DoesNotContain("customParam", Site.Params); + Assert.Contains("ParamsCustomParam", Site.Params); + Assert.Equal("Custom Value", Site.Params["ParamsCustomParam"]); + Assert.Equal(Expected, ((Dictionary)siteSettings.Params["ParamsNestedData"])["Level2"]); + Assert.Equal("Test", ((siteSettings.Params["ParamsNestedData"] as Dictionary)?["Level2"] as List)?[0]); + } +} diff --git a/test/ProgramTest.cs b/Tests/ProgramTest.cs similarity index 100% rename from test/ProgramTest.cs rename to Tests/ProgramTest.cs diff --git a/test/ServerHandlers/PingRequestHandlerTests.cs b/Tests/ServerHandlers/PingRequestHandlerTests.cs similarity index 90% rename from test/ServerHandlers/PingRequestHandlerTests.cs rename to Tests/ServerHandlers/PingRequestHandlerTests.cs index d707d6908d5e494712782004375397550d346e86..a254fbb86b0a3285cd2f7d29f2245660af4a07b2 100644 --- a/test/ServerHandlers/PingRequestHandlerTests.cs +++ b/Tests/ServerHandlers/PingRequestHandlerTests.cs @@ -17,14 +17,14 @@ public class PingRequestHandlerTests : TestSetup var pingRequests = new PingRequests(); // Act - var code = await pingRequests.Handle(response, "ping", todayDate).ConfigureAwait(true); + var code = await pingRequests.Handle(response, "ping", TodayDate).ConfigureAwait(true); // Assert _ = stream.Seek(0, SeekOrigin.Begin); using var reader = new StreamReader(stream); var content = await reader.ReadToEndAsync().ConfigureAwait(true); - Assert.Equal(todayDate.ToString("o"), content); + Assert.Equal(TodayDate.ToString("o"), content); Assert.Equal("ping", code); } diff --git a/test/ServerHandlers/RegisteredPageRequestHandlerTests.cs b/Tests/ServerHandlers/RegisteredPageRequestHandlerTests.cs similarity index 68% rename from test/ServerHandlers/RegisteredPageRequestHandlerTests.cs rename to Tests/ServerHandlers/RegisteredPageRequestHandlerTests.cs index 89925db4e4f5d734d98332f8a20e39b638951acd..ea788ec72fce93f553f65c7c36c16b68acdfbc95 100644 --- a/test/ServerHandlers/RegisteredPageRequestHandlerTests.cs +++ b/Tests/ServerHandlers/RegisteredPageRequestHandlerTests.cs @@ -1,8 +1,8 @@ using NSubstitute; -using SuCoS; using SuCoS.Helpers; using SuCoS.Models; using SuCoS.Models.CommandLineOptions; +using SuCoS.Parsers; using SuCoS.ServerHandlers; using Xunit; @@ -10,12 +10,7 @@ namespace Tests.ServerHandlers; public class RegisteredPageRequestHandlerTests : TestSetup { - readonly IFileSystem fs; - - public RegisteredPageRequestHandlerTests() - { - fs = new FileSystem(); - } + private readonly IFileSystem _fs = new FileSystem(); [Theory] [InlineData("/", true)] @@ -23,43 +18,43 @@ public class RegisteredPageRequestHandlerTests : TestSetup public void Check_ReturnsTrueForRegisteredPage(string requestPath, bool exist) { // Arrange - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST06)); - site.Options = new GenerateOptions + var siteFullPath = Path.GetFullPath(Path.Combine(TestSitesPath, TestSitePathConst06)); + Site.Options = new GenerateOptions { SourceArgument = siteFullPath }; - var registeredPageRequest = new RegisteredPageRequest(site); + var registeredPageRequest = new RegisteredPageRequest(Site); // Act - site.ParseAndScanSourceFiles(fs, Path.Combine(siteFullPath, "content")); + Site.ParseAndScanSourceFiles(_fs, Path.Combine(siteFullPath, "content")); // Assert Assert.Equal(exist, registeredPageRequest.Check(requestPath)); } [Theory] - [InlineData("/", testSitePathCONST06, false)] - [InlineData("/", testSitePathCONST08, true)] + [InlineData("/", TestSitePathConst06, false)] + [InlineData("/", TestSitePathConst08, true)] public async Task Handle_ReturnsExpectedContent2(string requestPath, string testSitePath, bool contains) { // Arrange - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePath)); + var siteFullPath = Path.GetFullPath(Path.Combine(TestSitesPath, testSitePath)); GenerateOptions options = new() { SourceArgument = siteFullPath }; - var parser = new SuCoS.Parser.YAMLParser(); - var siteSettings = SiteHelper.ParseSettings("sucos.yaml", options, parser, fs); - site = new Site(options, siteSettings, parser, loggerMock, null); + var parser = new YamlParser(); + var siteSettings = SiteHelper.ParseSettings("sucos.yaml", options, parser, _fs); + Site = new Site(options, siteSettings, parser, LoggerMock, null); - var registeredPageRequest = new RegisteredPageRequest(site); + var registeredPageRequest = new RegisteredPageRequest(Site); var response = Substitute.For(); var stream = new MemoryStream(); _ = response.OutputStream.Returns(stream); // Act - site.ParseAndScanSourceFiles(fs, Path.Combine(siteFullPath, "content")); + Site.ParseAndScanSourceFiles(_fs, Path.Combine(siteFullPath, "content")); _ = registeredPageRequest.Check(requestPath); var code = await registeredPageRequest.Handle(response, requestPath, DateTime.Now).ConfigureAwait(true); @@ -79,7 +74,7 @@ public class RegisteredPageRequestHandlerTests : TestSetup } else { - Assert.DoesNotContain("", content, StringComparison.InvariantCulture); + Assert.DoesNotContain("", content, StringComparison.InvariantCulture); Assert.DoesNotContain("", content, StringComparison.InvariantCulture); } Assert.Contains("Index Content", content, StringComparison.InvariantCulture); diff --git a/test/ServerHandlers/StaticFileRequestHandlerTests.cs b/Tests/ServerHandlers/StaticFileRequestHandlerTests.cs similarity index 78% rename from test/ServerHandlers/StaticFileRequestHandlerTests.cs rename to Tests/ServerHandlers/StaticFileRequestHandlerTests.cs index 88a58a3f1ea738e7e25bd489ac8842cc2d1ce403..1a0fd3ab7248802350bf526f93a6880b9955a928 100644 --- a/test/ServerHandlers/StaticFileRequestHandlerTests.cs +++ b/Tests/ServerHandlers/StaticFileRequestHandlerTests.cs @@ -6,21 +6,21 @@ namespace Tests.ServerHandlers; public class StaticFileRequestHandlerTests : TestSetup, IDisposable { - private readonly string tempFilePath; + private readonly string _tempFilePath; public StaticFileRequestHandlerTests() { // Creating a temporary file for testing purposes - tempFilePath = Path.GetTempFileName(); - File.WriteAllText(tempFilePath, "test"); + _tempFilePath = Path.GetTempFileName(); + File.WriteAllText(_tempFilePath, "test"); } [Fact] public void Check_ReturnsTrueForExistingFile() { // Arrange - var requestPath = Path.GetFileName(tempFilePath); - var basePath = Path.GetDirectoryName(tempFilePath) + var requestPath = Path.GetFileName(_tempFilePath); + var basePath = Path.GetDirectoryName(_tempFilePath) ?? throw new InvalidOperationException("Unable to determine directory of temporary file."); var staticFileRequest = new StaticFileRequest(basePath, false); @@ -36,8 +36,8 @@ public class StaticFileRequestHandlerTests : TestSetup, IDisposable public async Task Handle_ReturnsExpectedContent() { // Arrange - var requestPath = Path.GetFileName(tempFilePath); - var basePath = Path.GetDirectoryName(tempFilePath) + var requestPath = Path.GetFileName(_tempFilePath); + var basePath = Path.GetDirectoryName(_tempFilePath) ?? throw new InvalidOperationException("Unable to determine directory of temporary file."); var staticFileRequest = new StaticFileRequest(basePath, true); @@ -63,9 +63,9 @@ public class StaticFileRequestHandlerTests : TestSetup, IDisposable public void Dispose() { // Cleaning up the temporary file after tests run - if (File.Exists(tempFilePath)) + if (File.Exists(_tempFilePath)) { - File.Delete(tempFilePath); + File.Delete(_tempFilePath); } GC.SuppressFinalize(this); diff --git a/Tests/TestSetup.cs b/Tests/TestSetup.cs new file mode 100644 index 0000000000000000000000000000000000000000..a56529441013008bea4906f70c77cc4a00eed788 --- /dev/null +++ b/Tests/TestSetup.cs @@ -0,0 +1,54 @@ +using NSubstitute; +using Serilog; +using SuCoS.Models; +using SuCoS.Models.CommandLineOptions; +using SuCoS.Parsers; +using System.Globalization; + +namespace Tests; + +public class TestSetup +{ + protected const string TitleConst = "Test Title"; + protected const string SourcePathConst = "/path/to/file.md"; + protected readonly DateTime TodayDate = DateTime.Parse("2023-04-01", CultureInfo.InvariantCulture); + protected readonly DateTime FutureDate = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); + + protected const string TestSitePathConst01 = ".TestSites/01"; + protected const string TestSitePathConst02 = ".TestSites/02-have-index"; + protected const string TestSitePathConst03 = ".TestSites/03-section"; + protected const string TestSitePathConst04 = ".TestSites/04-tags"; + protected const string TestSitePathConst05 = ".TestSites/05-theme-no-baseof"; + protected const string TestSitePathConst06 = ".TestSites/06-theme"; + protected const string TestSitePathConst07 = ".TestSites/07-theme-no-baseof-error"; + protected const string TestSitePathConst08 = ".TestSites/08-theme-html"; + + protected readonly IMetadataParser FrontMatterParser = new YamlParser(); + protected readonly IGenerateOptions GenerateOptionsMock = Substitute.For(); + private readonly SiteSettings _siteSettingsMock = Substitute.For(); + protected readonly ILogger LoggerMock = Substitute.For(); + protected readonly ISystemClock SystemClockMock = Substitute.For(); + protected readonly IFrontMatter FrontMatterMock = new FrontMatter + { + Title = TitleConst, + SourceRelativePath = SourcePathConst + }; + + protected ISite Site; + + // based on the compiled Tests.dll path + // that is typically "bin/Debug/netX.0/Tests.dll" + protected const string TestSitesPath = "../../.."; + + protected TestSetup() + { + _ = SystemClockMock.Now.Returns(TodayDate); + Site = new Site(GenerateOptionsMock, _siteSettingsMock, FrontMatterParser, LoggerMock, SystemClockMock); + } + + public TestSetup(SiteSettings siteSettings) + { + _ = SystemClockMock.Now.Returns(TodayDate); + Site = new Site(GenerateOptionsMock, siteSettings, FrontMatterParser, LoggerMock, SystemClockMock); + } +} diff --git a/test/test.csproj b/Tests/Tests.csproj similarity index 93% rename from test/test.csproj rename to Tests/Tests.csproj index c5574130304a048a358b08472833cab7b7ea9ed1..75fa4578e648548c46af3eb04b8994ad4153beaa 100644 --- a/test/test.csproj +++ b/Tests/Tests.csproj @@ -6,6 +6,8 @@ enable false + + test diff --git a/source/AssemblyInfo.cs b/source/AssemblyInfo.cs index 52f99c758e3bb4e1b0f5a3dc95f691d51c897051..9ead8d2ec9dd109d5f255978d68c36649d0a2a69 100644 --- a/source/AssemblyInfo.cs +++ b/source/AssemblyInfo.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("test")] +[assembly: InternalsVisibleTo("Tests")] diff --git a/source/Commands/BaseGeneratorCommand.cs b/source/Commands/BaseGeneratorCommand.cs index 0c4a8e836ca86bd36d7c53eb304001be759b836b..d49e150267bd222dd7d079166bcee2c48d6f0d37 100644 --- a/source/Commands/BaseGeneratorCommand.cs +++ b/source/Commands/BaseGeneratorCommand.cs @@ -2,9 +2,9 @@ using Serilog; using SuCoS.Helpers; using SuCoS.Models; using SuCoS.Models.CommandLineOptions; -using SuCoS.Parser; +using SuCoS.Parsers; -namespace SuCoS; +namespace SuCoS.Commands; /// /// Base class for build and serve commands. @@ -14,32 +14,32 @@ public abstract class BaseGeneratorCommand /// /// The configuration file name. /// - protected const string configFile = "sucos.yaml"; + protected const string ConfigFile = "sucos.yaml"; /// /// The site configuration. /// - protected Site site { get; set; } + protected Site Site { get; set; } /// /// The front matter parser instance. The default is YAML. /// - protected IMetadataParser Parser { get; } = new YAMLParser(); + protected IMetadataParser Parser { get; } = new YamlParser(); /// /// The stopwatch reporter. /// - protected StopwatchReporter stopwatch { get; } + protected StopwatchReporter Stopwatch { get; } /// /// The logger (Serilog). /// - protected ILogger logger { get; } + protected ILogger Logger { get; } /// /// File system functions (file and directory) /// - protected readonly IFileSystem fs; + protected readonly IFileSystem Fs; /// /// Initializes a new instance of the class. @@ -51,12 +51,12 @@ public abstract class BaseGeneratorCommand { ArgumentNullException.ThrowIfNull(options); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - stopwatch = new(logger); - this.fs = fs; + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + Stopwatch = new(logger); + Fs = fs; logger.Information("Source path: {source}", propertyValue: options.Source); - site = SiteHelper.Init(configFile, options, Parser, logger, stopwatch, fs); + Site = SiteHelper.Init(ConfigFile, options, Parser, logger, Stopwatch, fs); } } diff --git a/source/Commands/BuildCommand.cs b/source/Commands/BuildCommand.cs index 92983d6edc9573c910ba2983b17a1b27b84ce97a..1ad4bb141434088d65eeacec3b9672c5e49bb3ab 100644 --- a/source/Commands/BuildCommand.cs +++ b/source/Commands/BuildCommand.cs @@ -1,15 +1,16 @@ using Serilog; +using SuCoS.Helpers; using SuCoS.Models; using SuCoS.Models.CommandLineOptions; -namespace SuCoS; +namespace SuCoS.Commands; /// /// Build Command will build the site based on the source files. /// public class BuildCommand : BaseGeneratorCommand { - private readonly BuildOptions options; + private readonly BuildOptions _options; /// /// Entry point of the build command. It will be called by the main program /// in case the build command is invoked (which is by default). @@ -20,78 +21,78 @@ public class BuildCommand : BaseGeneratorCommand public BuildCommand(BuildOptions options, ILogger logger, IFileSystem fs) : base(options, logger, fs) { - this.options = options ?? throw new ArgumentNullException(nameof(options)); + _options = options ?? throw new ArgumentNullException(nameof(options)); } /// - /// Run the commmand + /// Run the command /// public int Run() { - logger.Information("Output path: {output}", options.Output); + Logger.Information("Output path: {output}", _options.Output); // Generate the site pages CreateOutputFiles(); // Copy theme static folder files into the root of the output folder - if (site.Theme is not null) + if (Site.Theme is not null) { - CopyFolder(site.Theme.StaticFolder, options.Output); + CopyFolder(Site.Theme.StaticFolder, _options.Output); } // Copy static folder files into the root of the output folder - CopyFolder(site.SourceStaticPath, options.Output); + CopyFolder(Site.SourceStaticPath, _options.Output); // Generate the build report - stopwatch.LogReport(site.Title); + Stopwatch.LogReport(Site.Title); return 0; } private void CreateOutputFiles() { - stopwatch.Start("Create"); + Stopwatch.Start("Create"); // Print each page var pagesCreated = 0; // counter to keep track of the number of pages created - _ = Parallel.ForEach(site.OutputReferences, pair => + _ = Parallel.ForEach(Site.OutputReferences, pair => { var (url, output) = pair; if (output is IPage page) { - var path = (url + (site.UglyURLs ? string.Empty : "/index.html")).TrimStart('/'); + var path = (url + (Site.UglyURLs ? string.Empty : "/index.html")).TrimStart('/'); // Generate the output path - var outputAbsolutePath = Path.Combine(options.Output, path); + var outputAbsolutePath = Path.Combine(_options.Output, path); var outputDirectory = Path.GetDirectoryName(outputAbsolutePath); - fs.DirectoryCreateDirectory(outputDirectory!); + Fs.DirectoryCreateDirectory(outputDirectory!); // Save the processed output to the final file var result = page.CompleteContent; - fs.FileWriteAllText(outputAbsolutePath, result); + Fs.FileWriteAllText(outputAbsolutePath, result); // Log - logger.Debug("Page created: {Permalink}", outputAbsolutePath); + Logger.Debug("Page created: {Permalink}", outputAbsolutePath); - // Use interlocked to safely increment the counter in a multi-threaded environment + // Use interlocked to safely increment the counter in a multithreaded environment _ = Interlocked.Increment(ref pagesCreated); } else if (output is IResource resource) { - var outputAbsolutePath = Path.Combine(options.Output, resource.Permalink!.TrimStart('/')); + var outputAbsolutePath = Path.Combine(_options.Output, resource.Permalink!.TrimStart('/')); var outputDirectory = Path.GetDirectoryName(outputAbsolutePath); - fs.DirectoryCreateDirectory(outputDirectory!); + Fs.DirectoryCreateDirectory(outputDirectory!); // Copy the file to the output folder - fs.FileCopy(resource.SourceFullPath, outputAbsolutePath, overwrite: true); + Fs.FileCopy(resource.SourceFullPath, outputAbsolutePath, overwrite: true); } }); // Stop the stopwatch - stopwatch.Stop("Create", pagesCreated); + Stopwatch.Stop("Create", pagesCreated); } /// @@ -102,16 +103,16 @@ public class BuildCommand : BaseGeneratorCommand public void CopyFolder(string source, string output) { // Check if the source folder even exists - if (!fs.DirectoryExists(source)) + if (!Fs.DirectoryExists(source)) { return; } // Create the output folder if it doesn't exist - fs.DirectoryCreateDirectory(output); + Fs.DirectoryCreateDirectory(output); // Get all files in the source folder - var files = fs.DirectoryGetFiles(source); + var files = Fs.DirectoryGetFiles(source); foreach (var fileFullPath in files) { @@ -122,7 +123,7 @@ public class BuildCommand : BaseGeneratorCommand var destinationFullPath = Path.Combine(output, fileName); // Copy the file to the output folder - fs.FileCopy(fileFullPath, destinationFullPath, overwrite: true); + Fs.FileCopy(fileFullPath, destinationFullPath, overwrite: true); } } } diff --git a/source/Commands/CheckLinkCommand.cs b/source/Commands/CheckLinkCommand.cs index 2e9943053ffaa3aa06bb27354dcba748fd6472a2..2c189005442b96e9d0d755328b28140507c1f507 100644 --- a/source/Commands/CheckLinkCommand.cs +++ b/source/Commands/CheckLinkCommand.cs @@ -5,7 +5,7 @@ using System.Text.RegularExpressions; using Serilog; using SuCoS.Models.CommandLineOptions; -namespace SuCoS; +namespace SuCoS.Commands; /// /// Check links of a given site. @@ -13,14 +13,14 @@ namespace SuCoS; public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger logger) { [GeneratedRegex(@"https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9@:%_\+.~#?&\/=]*)")] - private static partial Regex URLRegex(); - private static readonly Regex urlRegex = URLRegex(); - private const int retriesCount = 3; - private readonly TimeSpan retryInterval = TimeSpan.FromSeconds(1); - private HttpClient httpClient = null!; - private readonly ConcurrentBag checkedLinks = []; - private readonly ConcurrentDictionary> linkToFilesMap = []; - private readonly ConcurrentBag failedLinks = []; + private static partial Regex UrlGeneratedRegex(); + private static readonly Regex UrlRegex = UrlGeneratedRegex(); + private const int RetriesCount = 3; + private readonly TimeSpan _retryInterval = TimeSpan.FromSeconds(1); + private HttpClient _httpClient = null!; + private readonly ConcurrentBag _checkedLinks = []; + private readonly ConcurrentDictionary> _linkToFilesMap = []; + private readonly ConcurrentBag _failedLinks = []; /// /// Run the app @@ -36,21 +36,21 @@ public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger return 1; } - httpClient = GetHttpClient(); + _httpClient = GetHttpClient(); var files = GetFiles(directoryPath, settings.Filters); - var linksAreValid = await CheckLinks(directoryPath, files, httpClient).ConfigureAwait(false); + var linksAreValid = await CheckLinks(directoryPath, files, _httpClient).ConfigureAwait(false); if (!linksAreValid) { logger.Error("There are failed checks."); - foreach (var (link, linkfiles) in linkToFilesMap) + foreach (var (link, linkFiles) in _linkToFilesMap) { - if (failedLinks.Contains(link)) + if (_failedLinks.Contains(link)) { - linkfiles.Sort(); - logger.Error("Link {link} failed and are in these files:\n{files}", link, string.Join("\n", linkfiles)); + linkFiles.Sort(); + logger.Error("Link {link} failed and are in these files:\n{files}", link, string.Join("\n", linkFiles)); } } return 1; @@ -68,15 +68,15 @@ public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger private async Task CheckLinks(string directoryPath, string[] files, HttpClient httpClient) { - var filesCount = files.Length; + // var filesCount = files.Length; var result = true; var options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; await Parallel.ForEachAsync(files, options, async (filePath, token) => { var fileNameSanitized = filePath[directoryPath.Length..].Trim('/', '\\'); - var fileText = File.ReadAllText(filePath); - var matches = urlRegex.Matches(fileText); + var fileText = await File.ReadAllTextAsync(filePath, token); + var matches = UrlRegex.Matches(fileText); if (matches.Count == 0) { LogInformation("{fileName}: no links found", fileNameSanitized); @@ -88,21 +88,21 @@ public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger { var link = match.Value.Trim('.'); - if (!linkToFilesMap.TryGetValue(link, out var value)) + if (!_linkToFilesMap.TryGetValue(link, out var value)) { value = []; - linkToFilesMap[link] = value; + _linkToFilesMap[link] = value; } if (!value.Contains(fileNameSanitized)) { value.Add(fileNameSanitized); } - if (checkedLinks.Contains(link)) + if (_checkedLinks.Contains(link)) { continue; } - checkedLinks.Add(link); + _checkedLinks.Add(link); if (settings.Ignore.Contains(link)) { @@ -117,13 +117,13 @@ public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger LogInformation("{fileName}: {link} found", fileNameSanitized, link); var linkIsValid = false; - for (var j = 0; j < retriesCount && !linkIsValid; j++) + for (var j = 0; j < RetriesCount && !linkIsValid; j++) { linkIsValid |= await CheckLink(fileNameSanitized, link, httpClient).ConfigureAwait(false); - if (!linkIsValid && j < retriesCount - 1) + if (!linkIsValid && j < RetriesCount - 1) { LogInformation("{fileName}: {link} retrying...", fileNameSanitized, link); - Thread.Sleep(retryInterval); + Thread.Sleep(_retryInterval); } } @@ -134,7 +134,7 @@ public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger else { LogError("{fileName}: {link} FAIL", fileNameSanitized, link); - failedLinks.Add(link); + _failedLinks.Add(link); } result &= linkIsValid; @@ -144,15 +144,15 @@ public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger return result; } - private bool TryLocalFile(CheckLinkOptions settings, string directoryPath, string fileNameSanitized, string link) + private bool TryLocalFile(CheckLinkOptions options, string directoryPath, string fileNameSanitized, string link) { - if (string.IsNullOrEmpty(settings.InternalURL) || !link.StartsWith(settings.InternalURL)) + if (string.IsNullOrEmpty(options.InternalUrl) || !link.StartsWith(options.InternalUrl)) { return false; } // Strip the InternalURL from the link - link = link[settings.InternalURL.Length..]; + link = link[options.InternalUrl.Length..]; // Handle the link as a local file var localFilePath = Path.Combine(directoryPath, link); @@ -163,9 +163,9 @@ public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger else { LogError("{fileName}: {link} is a local file but does not exist", fileNameSanitized, link); - failedLinks.Add(link); + _failedLinks.Add(link); } - checkedLinks.Add(link); + _checkedLinks.Add(link); return true; } @@ -185,7 +185,7 @@ public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger catch (Exception ex) { LogError("{fileName}: {link} failed with: {exMessage}", fileName, link, ex.Message); - failedLinks.Add(link); + _failedLinks.Add(link); return false; } } @@ -215,7 +215,7 @@ public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger logger.Error(message, fileName, link, arg); } } - + private void LogError(string message, string fileName, string? link, HttpStatusCode arg) { if (settings.Verbose) diff --git a/source/Commands/NewSiteCommand.cs b/source/Commands/NewSiteCommand.cs index 1f2c73a196684dc3af48e51a6a6a7f5185c9fd18..0d7dd1eedc4e45c4771b3b481c4d14f8724017da 100644 --- a/source/Commands/NewSiteCommand.cs +++ b/source/Commands/NewSiteCommand.cs @@ -1,16 +1,17 @@ using Serilog; +using SuCoS.Helpers; using SuCoS.Models; using SuCoS.Models.CommandLineOptions; -using SuCoS.Parser; +using SuCoS.Parsers; -namespace SuCoS; +namespace SuCoS.Commands; /// /// Check links of a given site. /// -public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logger, IFileSystem fileSystem, ISite site) +public sealed class NewSiteCommand(NewSiteOptions options, ILogger logger, IFileSystem fileSystem, ISite site) { - private static SiteSettings siteSettings = null!; + private static SiteSettings _siteSettings = null!; /// /// Generate the needed data for the class @@ -23,14 +24,14 @@ public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logge { ArgumentNullException.ThrowIfNull(options); - siteSettings = new SiteSettings() + _siteSettings = new SiteSettings() { Title = options.Title, Description = options.Description, - BaseURL = options.BaseURL + BaseURL = options.BaseUrl }; - var site = new Site(new GenerateOptions() { SourceOption = options.Output }, siteSettings, new YAMLParser(), null!, null); + var site = new Site(new GenerateOptions() { SourceOption = options.Output }, _siteSettings, new YamlParser(), null!, null); return new NewSiteCommand(options, logger, fileSystem, site); } @@ -54,7 +55,7 @@ public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logge try { CreateFolders(site.SourceFolders); - site.Parser.Export(siteSettings, siteSettingsPath); + site.Parser.Export(_siteSettings, siteSettingsPath); } catch (Exception ex) { @@ -78,4 +79,4 @@ public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logge fileSystem.DirectoryCreateDirectory(folder); } } -} \ No newline at end of file +} diff --git a/source/Commands/NewThemeCommand.cs b/source/Commands/NewThemeCommand.cs index d6b2bc6d828abe49bc781e97b52b7809e8bc27e6..ddb61f5882293da9a05c477057b817750ae31161 100644 --- a/source/Commands/NewThemeCommand.cs +++ b/source/Commands/NewThemeCommand.cs @@ -1,14 +1,14 @@ using Serilog; using SuCoS.Models; using SuCoS.Models.CommandLineOptions; -using SuCoS.Parser; +using SuCoS.Parsers; -namespace SuCoS; +namespace SuCoS.Commands; /// /// Check links of a given site. /// -public sealed partial class NewThemeCommand(NewThemeOptions options, ILogger logger) +public sealed class NewThemeCommand(NewThemeOptions options, ILogger logger) { /// /// Run the app @@ -36,7 +36,7 @@ public sealed partial class NewThemeCommand(NewThemeOptions options, ILogger log try { - new YAMLParser().Export(theme, themePath); + new YamlParser().Export(theme, themePath); } catch (Exception ex) { @@ -62,4 +62,4 @@ public sealed partial class NewThemeCommand(NewThemeOptions options, ILogger log } -} \ No newline at end of file +} diff --git a/source/Commands/ServeCommand.cs b/source/Commands/ServeCommand.cs index 7c9c0abce5d326ad027e11900a145a031867efe8..f62f451b75885aea80c63372cbcc4a722df53dd4 100644 --- a/source/Commands/ServeCommand.cs +++ b/source/Commands/ServeCommand.cs @@ -1,34 +1,34 @@ +using System.Net; using Serilog; using SuCoS.Helpers; using SuCoS.Models.CommandLineOptions; using SuCoS.ServerHandlers; -using System.Net; -namespace SuCoS; +namespace SuCoS.Commands; /// /// Serve Command will live serve the site and watch any changes. /// public sealed class ServeCommand : BaseGeneratorCommand, IDisposable { - private const string baseURLDefault = "http://localhost"; - private const int portDefault = 1122; + private const string BaseUrlDefault = "http://localhost"; + private const int PortDefault = 1122; /// /// The ServeOptions object containing the configuration parameters for the server. - /// This includes settings such as the source directory to watch for file changes, - /// verbosity of the logs, etc. These options are passed into the ServeCommand at construction + /// This includes settings such as the source directory to watch for file changes, + /// verbosity of the logs, etc. These options are passed into the ServeCommand at construction /// and are used throughout its operation. /// - private readonly ServeOptions options; + private readonly ServeOptions _options; /// /// The FileSystemWatcher object that monitors the source directory for file changes. - /// When a change is detected, this triggers a server restart to ensure the served content - /// remains up-to-date. The FileSystemWatcher is configured with the source directory + /// When a change is detected, this triggers a server restart to ensure the served content + /// remains up-to-date. The FileSystemWatcher is configured with the source directory /// at construction and starts watching immediately. /// - private readonly IFileWatcher fileWatcher; + private readonly IFileWatcher _fileWatcher; /// /// A Timer that helps to manage the frequency of server restarts. @@ -36,23 +36,23 @@ public sealed class ServeCommand : BaseGeneratorCommand, IDisposable /// when the timer expires, helping to ensure that rapid consecutive file changes result /// in a single server restart, not multiple. /// - private Timer? debounceTimer; + private Timer? _debounceTimer; /// - /// A SemaphoreSlim used to ensure that server restarts due to file changes occur sequentially, + /// A SemaphoreSlim used to ensure that server restarts due to file changes occur sequentially, /// not concurrently. This is necessary because a restart involves stopping the current server /// and starting a new one, which would not be thread-safe without some form of synchronization. /// // private readonly SemaphoreSlim restartServerLock = new(1, 1); - private Task lastRestartTask = Task.CompletedTask; + private Task _lastRestartTask = Task.CompletedTask; - private HttpListener? listener; + private HttpListener? _listener; - private IServerHandlers[]? handlers; + private IServerHandlers[]? _handlers; - private DateTime serverStartTime; + private DateTime _serverStartTime; - private Task? loop; + private Task? _loop; /// /// Constructor for the ServeCommand class. @@ -64,13 +64,13 @@ public sealed class ServeCommand : BaseGeneratorCommand, IDisposable public ServeCommand(ServeOptions options, ILogger logger, IFileWatcher fileWatcher, IFileSystem fs) : base(options, logger, fs) { - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.fileWatcher = fileWatcher ?? throw new ArgumentNullException(nameof(fileWatcher)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + _fileWatcher = fileWatcher ?? throw new ArgumentNullException(nameof(fileWatcher)); // Watch for file changes in the specified path - var SourceAbsolutePath = Path.GetFullPath(options.Source); - logger.Information("Watching for file changes in {SourceAbsolutePath}", SourceAbsolutePath); - fileWatcher.Start(SourceAbsolutePath, OnSourceFileChanged); + var sourceAbsolutePath = Path.GetFullPath(options.Source); + logger.Information("Watching for file changes in {SourceAbsolutePath}", sourceAbsolutePath); + fileWatcher.Start(sourceAbsolutePath, OnSourceFileChanged); } /// @@ -78,68 +78,68 @@ public sealed class ServeCommand : BaseGeneratorCommand, IDisposable /// public void StartServer() { - StartServer(baseURLDefault, portDefault); + StartServer(BaseUrlDefault, PortDefault); } /// /// Starts the server asynchronously. /// - /// - public void StartServer(string baseURL) + /// + public void StartServer(string baseUrl) { - StartServer(baseURL, portDefault); + StartServer(baseUrl, PortDefault); } /// /// Starts the server asynchronously. /// - /// The base URL for the server. + /// The base URL for the server. /// The port number for the server. /// A Task representing the asynchronous operation. - public void StartServer(string baseURL, int port) + public void StartServer(string baseUrl, int port) { - logger.Information("Starting server..."); + Logger.Information("Starting server..."); // Generate the build report - stopwatch.LogReport(site.Title); + Stopwatch.LogReport(Site.Title); - serverStartTime = DateTime.UtcNow; + _serverStartTime = DateTime.UtcNow; - handlers = [ + _handlers = [ new PingRequests(), - new StaticFileRequest(site.SourceStaticPath, false), - new StaticFileRequest(site.Theme?.StaticFolder, true), - new RegisteredPageRequest(site), - new RegisteredPageResourceRequest(site) + new StaticFileRequest(Site.SourceStaticPath, false), + new StaticFileRequest(Site.Theme?.StaticFolder, true), + new RegisteredPageRequest(Site), + new RegisteredPageResourceRequest(Site) ]; - listener = new HttpListener(); - listener.Prefixes.Add($"{baseURL}:{port}/"); - listener.Start(); + _listener = new HttpListener(); + _listener.Prefixes.Add($"{baseUrl}:{port}/"); + _listener.Start(); - logger.Information("You site is live: {baseURL}:{port}", baseURL, port); + Logger.Information("You site is live: {baseURL}:{port}", baseUrl, port); - loop = Task.Run(async () => + _loop = Task.Run(async () => { - while (listener is not null && listener.IsListening) + while (_listener is not null && _listener.IsListening) { try { - var context = await listener.GetContextAsync().ConfigureAwait(false); + var context = await _listener.GetContextAsync().ConfigureAwait(false); await HandleRequest(context).ConfigureAwait(false); } catch (HttpListenerException ex) { - if (listener.IsListening) + if (_listener.IsListening) { - logger.Error(ex, "Unexpected listener error."); + Logger.Error(ex, "Unexpected listener error."); } break; } catch (Exception ex) { - if (listener.IsListening) + if (_listener.IsListening) { - logger.Error(ex, "Error processing request."); + Logger.Error(ex, "Error processing request."); } break; } @@ -150,10 +150,10 @@ public sealed class ServeCommand : BaseGeneratorCommand, IDisposable /// public void Dispose() { - listener?.Stop(); - listener?.Close(); - fileWatcher.Stop(); - debounceTimer?.Dispose(); + _listener?.Stop(); + _listener?.Close(); + _fileWatcher.Stop(); + _debounceTimer?.Dispose(); GC.SuppressFinalize(this); } @@ -162,30 +162,30 @@ public sealed class ServeCommand : BaseGeneratorCommand, IDisposable /// private async Task RestartServer() { - _ = await lastRestartTask.ContinueWith(async _ => + _ = await _lastRestartTask.ContinueWith(async _ => { - logger.Information($"Restarting server..."); + Logger.Information("Restarting server..."); - if (listener != null && listener.IsListening) + if (_listener is { IsListening: true }) { - listener.Stop(); - listener.Close(); + _listener.Stop(); + _listener.Close(); - if (loop is not null) + if (_loop is not null) { // Wait for the loop to finish processing any ongoing requests. - await loop.ConfigureAwait(false); - loop.Dispose(); + await _loop.ConfigureAwait(false); + _loop.Dispose(); } } // Reinitialize the site - site = SiteHelper.Init(configFile, options, Parser, logger, stopwatch, fs); + Site = SiteHelper.Init(ConfigFile, _options, Parser, Logger, Stopwatch, Fs); StartServer(); }).ConfigureAwait(false); - lastRestartTask = lastRestartTask.ContinueWith(t => t.Exception != null + _lastRestartTask = _lastRestartTask.ContinueWith(t => t.Exception != null ? throw t.Exception : Task.CompletedTask); } @@ -199,10 +199,10 @@ public sealed class ServeCommand : BaseGeneratorCommand, IDisposable var requestPath = context.Request.Url?.AbsolutePath ?? string.Empty; string? resultType = null; - if (handlers is not null) + if (_handlers is not null) { var response = new HttpListenerResponseWrapper(context.Response); - foreach (var item in handlers) + foreach (var item in _handlers) { if (!item.Check(requestPath)) { @@ -211,12 +211,12 @@ public sealed class ServeCommand : BaseGeneratorCommand, IDisposable try { - resultType = await item.Handle(response, requestPath, serverStartTime).ConfigureAwait(false); + resultType = await item.Handle(response, requestPath, _serverStartTime).ConfigureAwait(false); break; } catch (Exception ex) { - logger.Debug(ex, "Error handling the request."); + Logger.Debug(ex, "Error handling the request."); } } } @@ -230,13 +230,13 @@ public sealed class ServeCommand : BaseGeneratorCommand, IDisposable { context.Response.OutputStream.Close(); } - logger.Debug("Request {type}\tfor {RequestPath}", resultType, requestPath); + Logger.Debug("Request {type}\tfor {RequestPath}", resultType, requestPath); } private static async Task HandleNotFoundRequest(HttpListenerContext context) { context.Response.StatusCode = 404; - using var writer = new StreamWriter(context.Response.OutputStream); + await using var writer = new StreamWriter(context.Response.OutputStream); await writer.WriteAsync("404 - File Not Found").ConfigureAwait(false); } @@ -254,8 +254,8 @@ public sealed class ServeCommand : BaseGeneratorCommand, IDisposable // File changes are firing multiple events in a short time. // Debounce the event handler to prevent multiple events from firing in a short time - debounceTimer?.Dispose(); - debounceTimer = new Timer(DebounceCallback, e, TimeSpan.FromMilliseconds(1), Timeout.InfiniteTimeSpan); + _debounceTimer?.Dispose(); + _debounceTimer = new Timer(DebounceCallback, e, TimeSpan.FromMilliseconds(1), Timeout.InfiniteTimeSpan); } private async void DebounceCallback(object? state) diff --git a/source/Helpers/FileUtils.cs b/source/Helpers/FileUtils.cs index fd1ae1ded53540518a40158e45290160dbb0a4a6..7d7e7df921b5f8caadce3141872b04a94c0d5ee1 100644 --- a/source/Helpers/FileUtils.cs +++ b/source/Helpers/FileUtils.cs @@ -22,7 +22,7 @@ public static class FileUtils var index = (page.Section, page.Kind, page.Type); - var cache = isBaseTemplate ? cacheManager.baseTemplateCache : cacheManager.contentTemplateCache; + var cache = isBaseTemplate ? cacheManager.BaseTemplateCache : cacheManager.ContentTemplateCache; // Check if the template content is already cached if (cache.TryGetValue(index, out var content)) @@ -72,11 +72,11 @@ public static class FileUtils ArgumentNullException.ThrowIfNull(page); // Generate the lookup order for template files based on the theme path, page section, type, and kind - var sections = page.Section is not null ? new[] { page.Section, string.Empty } : new[] { string.Empty }; + string[] sections = page.Section is not null ? [page.Section, string.Empty] : [string.Empty]; var types = new[] { page.Type, "_default" }; - var kinds = isBaseTemplate - ? new[] { page.Kind + "-baseof", "baseof" } - : new[] { page.Kind.ToString() }; + string[] kinds = isBaseTemplate + ? [page.Kind.ToString().ToLower(System.Globalization.CultureInfo.CurrentCulture) + "-baseof", "baseof"] + : [page.Kind.ToString().ToLower(System.Globalization.CultureInfo.CurrentCulture)]; // for each section, each type and each kind return (from section in sections diff --git a/source/Helpers/IFileSystem.cs b/source/Helpers/IFileSystem.cs index 1100c9069771ffe9775c525335c716eb5cbd9206..d469a49e90ed1e3622a6ec3570e917397d753853 100644 --- a/source/Helpers/IFileSystem.cs +++ b/source/Helpers/IFileSystem.cs @@ -1,4 +1,4 @@ -namespace SuCoS; +namespace SuCoS.Helpers; /// /// Interface for the System.File class diff --git a/source/Helpers/IFileWatcher.cs b/source/Helpers/IFileWatcher.cs index 75e2045ce2e54099c97281fbf73122d9fb28bf30..19ae65802568244015606e7c1d13d3f212ef1cb2 100644 --- a/source/Helpers/IFileWatcher.cs +++ b/source/Helpers/IFileWatcher.cs @@ -8,10 +8,10 @@ public interface IFileWatcher /// /// Starts the file watcher to monitor file changes in the specified source path. /// - /// The path to the source directory. - /// + /// The path to the source directory. + /// /// The created FileSystemWatcher object. - void Start(string SourceAbsolutePath, Action OnSourceFileChanged); + void Start(string sourceAbsolutePath, Action onSourceFileChanged); /// /// Disposes the file watcher diff --git a/source/Helpers/SiteCacheManager.cs b/source/Helpers/SiteCacheManager.cs index e722bd799df9f8f2b1a9e2a71cebc39dad133ebb..05839d06d4ef190d0e1b68e2ebf5f7c6b67c750a 100644 --- a/source/Helpers/SiteCacheManager.cs +++ b/source/Helpers/SiteCacheManager.cs @@ -11,25 +11,25 @@ public class SiteCacheManager /// /// Cache for content templates. /// - public Dictionary<(string?, Kind?, string?), string> contentTemplateCache { get; } = []; + public Dictionary<(string?, Kind?, string?), string> ContentTemplateCache { get; } = []; /// /// Cache for base templates. /// - public Dictionary<(string?, Kind?, string?), string> baseTemplateCache { get; } = []; + public Dictionary<(string?, Kind?, string?), string> BaseTemplateCache { get; } = []; /// /// Cache for tag page. /// - public ConcurrentDictionary> automaticContentCache { get; } = new(); + public ConcurrentDictionary> AutomaticContentCache { get; } = new(); /// /// Resets the template cache to force a reload of all templates. /// public void ResetCache() { - baseTemplateCache.Clear(); - contentTemplateCache.Clear(); - automaticContentCache.Clear(); + BaseTemplateCache.Clear(); + ContentTemplateCache.Clear(); + AutomaticContentCache.Clear(); } } diff --git a/source/Helpers/SiteHelper.cs b/source/Helpers/SiteHelper.cs index 6a16724faa494f5a2d0d8b9d3847da376d7ef382..6c69822c752adac2fe46cfdab865db7a700010c1 100644 --- a/source/Helpers/SiteHelper.cs +++ b/source/Helpers/SiteHelper.cs @@ -2,7 +2,7 @@ using Markdig; using Serilog; using SuCoS.Models; using SuCoS.Models.CommandLineOptions; -using SuCoS.Parser; +using SuCoS.Parsers; namespace SuCoS.Helpers; @@ -20,7 +20,7 @@ public static class SiteHelper .Build(); /// - /// Creates the pages dictionary. + /// Creates the pages' dictionary. /// /// public static Site Init(string configFile, IGenerateOptions options, IMetadataParser parser, ILogger logger, StopwatchReporter stopwatch, IFileSystem fs) @@ -28,8 +28,7 @@ public static class SiteHelper ArgumentNullException.ThrowIfNull(stopwatch); ArgumentNullException.ThrowIfNull(fs); - SiteSettings siteSettings; - siteSettings = ParseSettings(configFile, options, parser, fs); + var siteSettings = ParseSettings(configFile, options, parser, fs); var site = new Site(options, siteSettings, parser, logger, null); @@ -90,7 +89,7 @@ public static class SiteHelper ArgumentNullException.ThrowIfNull(parser); ArgumentNullException.ThrowIfNull(fs); - // Read the main configation + // Read the main configuration var filePath = Path.Combine(options.Source, configFile); if (!fs.FileExists(filePath)) { diff --git a/source/Helpers/SourceFileWatcher.cs b/source/Helpers/SourceFileWatcher.cs index 2a8e2156dfabbca6bab55b7b9b4744223bf1dc85..794223eac640b26f153b78b14962da90a71a1e6e 100644 --- a/source/Helpers/SourceFileWatcher.cs +++ b/source/Helpers/SourceFileWatcher.cs @@ -11,32 +11,32 @@ public sealed class SourceFileWatcher : IFileWatcher, IDisposable /// remains up-to-date. The FileSystemWatcher is configured with the source directory /// at construction and starts watching immediately. /// - private FileSystemWatcher? fileWatcher; + private FileSystemWatcher? _fileWatcher; /// - public void Start(string SourceAbsolutePath, Action OnSourceFileChanged) + public void Start(string sourceAbsolutePath, Action onSourceFileChanged) { - ArgumentNullException.ThrowIfNull(OnSourceFileChanged); + ArgumentNullException.ThrowIfNull(onSourceFileChanged); - fileWatcher = new FileSystemWatcher + _fileWatcher = new FileSystemWatcher { - Path = SourceAbsolutePath, + Path = sourceAbsolutePath, NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName, IncludeSubdirectories = true, EnableRaisingEvents = true }; // Subscribe to the desired events - fileWatcher.Changed += new FileSystemEventHandler(OnSourceFileChanged.Invoke); - fileWatcher.Created += new FileSystemEventHandler(OnSourceFileChanged.Invoke); - fileWatcher.Deleted += new FileSystemEventHandler(OnSourceFileChanged.Invoke); - fileWatcher.Renamed += new RenamedEventHandler(OnSourceFileChanged); + _fileWatcher.Changed += new FileSystemEventHandler(onSourceFileChanged.Invoke); + _fileWatcher.Created += new FileSystemEventHandler(onSourceFileChanged.Invoke); + _fileWatcher.Deleted += new FileSystemEventHandler(onSourceFileChanged.Invoke); + _fileWatcher.Renamed += new RenamedEventHandler(onSourceFileChanged); } /// public void Stop() { - fileWatcher?.Dispose(); + _fileWatcher?.Dispose(); } /// @@ -50,7 +50,7 @@ public sealed class SourceFileWatcher : IFileWatcher, IDisposable { if (disposing) { - fileWatcher?.Dispose(); + _fileWatcher?.Dispose(); } } } diff --git a/source/Helpers/StopwatchReporter.cs b/source/Helpers/StopwatchReporter.cs index 62d8941940aae5c5823c2f3cc0731aaad5daa6d9..033e8180c5d859512ffa6dbda6ab6fc0711cbb63 100644 --- a/source/Helpers/StopwatchReporter.cs +++ b/source/Helpers/StopwatchReporter.cs @@ -12,18 +12,18 @@ namespace SuCoS.Helpers; /// public class StopwatchReporter { - private readonly ILogger logger; - private readonly Dictionary stopwatches; - private readonly Dictionary itemCounts; + private readonly ILogger _logger; + private readonly Dictionary _stopwatches; + private readonly Dictionary _itemCounts; /// /// Constructor /// public StopwatchReporter(ILogger logger) { - this.logger = logger; - stopwatches = []; - itemCounts = []; + _logger = logger; + _stopwatches = []; + _itemCounts = []; } /// @@ -32,10 +32,10 @@ public class StopwatchReporter /// public void Start(string stepName) { - if (!stopwatches.TryGetValue(stepName, out var stopwatch)) + if (!_stopwatches.TryGetValue(stepName, out var stopwatch)) { stopwatch = new Stopwatch(); - stopwatches[stepName] = stopwatch; + _stopwatches[stepName] = stopwatch; } stopwatch.Restart(); @@ -49,13 +49,13 @@ public class StopwatchReporter /// public void Stop(string stepName, int itemCount) { - if (!stopwatches.TryGetValue(stepName, out var stopwatch)) + if (!_stopwatches.TryGetValue(stepName, out var stopwatch)) { throw new ArgumentException($"Step '{stepName}' has not been started."); } stopwatch.Stop(); - itemCounts[stepName] = itemCount; + _itemCounts[stepName] = itemCount; } /// @@ -69,9 +69,9 @@ public class StopwatchReporter ("Step", "Status", "Duration", 0) }; - foreach (var (stepName, stopwatch) in stopwatches) + foreach (var (stepName, stopwatch) in _stopwatches) { - _ = itemCounts.TryGetValue(stepName, out var itemCount); + _ = _itemCounts.TryGetValue(stepName, out var itemCount); var duration = stopwatch.ElapsedMilliseconds; var durationString = $"{duration} ms"; var status = itemCount > 0 ? itemCount.ToString(CultureInfo.InvariantCulture) : string.Empty; @@ -79,7 +79,7 @@ public class StopwatchReporter reportData.Add((Step: stepName, Status: status, DurationString: durationString, Duration: duration)); } - var totalDurationAllSteps = stopwatches.Values.Sum(sw => sw.ElapsedMilliseconds); + var totalDurationAllSteps = _stopwatches.Values.Sum(sw => sw.ElapsedMilliseconds); var report = new StringBuilder($@"Site '{siteTitle}' created! ═════════════════════════════════════════════"); @@ -102,6 +102,6 @@ Total {totalDurationAllSteps} ms ═════════════════════════════════════════════"); // Log the report - logger.Information(report.ToString(), siteTitle); + _logger.Information(report.ToString(), siteTitle); } } diff --git a/source/Helpers/Urlizer.cs b/source/Helpers/Urlizer.cs index 1e783a5164bb607ce78f731cf14211884e6abaf1..abd80132723082e40bf31659ea4fa9bdb2043727 100644 --- a/source/Helpers/Urlizer.cs +++ b/source/Helpers/Urlizer.cs @@ -8,10 +8,10 @@ namespace SuCoS.Helpers; /// public static partial class Urlizer { - [GeneratedRegex(@"[^a-zA-Z0-9]+")] + [GeneratedRegex("[^a-zA-Z0-9]+")] private static partial Regex UrlizeRegexAlpha(); - [GeneratedRegex(@"[^a-zA-Z0-9.]+")] + [GeneratedRegex("[^a-zA-Z0-9.]+")] private static partial Regex UrlizeRegexAlphaDot(); /// diff --git a/source/Models/Bundle.cs b/source/Models/Bundle.cs index 1a8d7db4105f5d945f26745b79e20c9dee475526..8517754f40a542e0a87f0e05f6feabc70be06fd8 100644 --- a/source/Models/Bundle.cs +++ b/source/Models/Bundle.cs @@ -8,15 +8,15 @@ public enum BundleType /// /// Regular page. Not a bundle. /// - none, + None, /// - /// Bundle with no childre + /// Bundle with no children /// - leaf, + Leaf, /// - /// Bundle with children embeded, like a home page, taxonomy term, taxonomy list + /// Bundle with children embedded, like a home page, taxonomy term, taxonomy list /// - branch + Branch } diff --git a/source/Models/CommandLineOptions/CheckLinkOptions.cs b/source/Models/CommandLineOptions/CheckLinkOptions.cs index a99caec531f7510ee5444e1a574f98e67b7c391f..10307ddb59872252ffac87185724e4b09e8b9deb 100644 --- a/source/Models/CommandLineOptions/CheckLinkOptions.cs +++ b/source/Models/CommandLineOptions/CheckLinkOptions.cs @@ -3,9 +3,9 @@ using CommandLine; namespace SuCoS.Models.CommandLineOptions; /// -/// Command line options for the checklinks command. +/// Command line options for the check-links command. /// -[Verb("checklinks", HelpText = "Checks links of a given site")] +[Verb("check-links", HelpText = "Checks links of a given site")] public class CheckLinkOptions { /// @@ -36,5 +36,5 @@ public class CheckLinkOptions /// Site URL, so it can be checked as local path files. /// [Option('u', "url", Required = false, HelpText = "Site URL, so it can be checked as local path files.")] - public string? InternalURL { get; init; } + public string? InternalUrl { get; init; } } diff --git a/source/Models/CommandLineOptions/NewSiteOptions.cs b/source/Models/CommandLineOptions/NewSiteOptions.cs index 6584a1ef0ac61bd22c52c2a75650ab4673d63d5d..b8e53e4b886ec439cd2715455c260771bea4bef4 100644 --- a/source/Models/CommandLineOptions/NewSiteOptions.cs +++ b/source/Models/CommandLineOptions/NewSiteOptions.cs @@ -5,7 +5,7 @@ namespace SuCoS.Models.CommandLineOptions; /// /// Command line options to generate a simple site from scratch. /// -[Verb("new-site", false, HelpText = "Generate a simple site from scratch")] +[Verb("new-site", HelpText = "Generate a simple site from scratch")] public class NewSiteOptions { /// @@ -36,5 +36,5 @@ public class NewSiteOptions /// Site base url. /// [Option("url", Required = false, HelpText = "Site base url")] - public string BaseURL { get; init; } = "https://example.org/"; + public string BaseUrl { get; init; } = "https://example.org/"; } diff --git a/source/Models/CommandLineOptions/NewThemeOptions.cs b/source/Models/CommandLineOptions/NewThemeOptions.cs index f512afe27d456369d4a258c45ad5b50e0c1996c0..33db805a63dcdfce0ffcab45403cde509d45a2db 100644 --- a/source/Models/CommandLineOptions/NewThemeOptions.cs +++ b/source/Models/CommandLineOptions/NewThemeOptions.cs @@ -5,7 +5,7 @@ namespace SuCoS.Models.CommandLineOptions; /// /// Command line options to generate a simple site from scratch. /// -[Verb("new-theme", false, HelpText = "Generate a simple theme from scratch")] +[Verb("new-theme", HelpText = "Generate a simple theme from scratch")] public class NewThemeOptions { /// diff --git a/source/Models/FrontMatter.cs b/source/Models/FrontMatter.cs index 2895bd8bf9e76205a850132cfcf530e7b6859be0..942b958c2c7b4354db0bdb77f1456cba1a5a9693 100644 --- a/source/Models/FrontMatter.cs +++ b/source/Models/FrontMatter.cs @@ -1,12 +1,11 @@ -using Serilog; using SuCoS.Helpers; -using SuCoS.Parser; +using SuCoS.Parsers; using YamlDotNet.Serialization; namespace SuCoS.Models; /// -/// A scafold structure to help creating system-generated content, like +/// A scaffold structure to help creating system-generated content, like /// tag, section or index pages /// [YamlSerializable] @@ -59,7 +58,7 @@ public class FrontMatter : IFrontMatter /// [YamlIgnore] - public Kind Kind { get; set; } = Kind.single; + public Kind Kind { get; set; } = Kind.Single; /// [YamlIgnore] diff --git a/source/Models/IFrontMatter.cs b/source/Models/IFrontMatter.cs index 2a6f176b397e91d7866c0ae4b313b444b5a0e6a1..3618a4e19dc3a34e09ece3c26ce7f447c93987d8 100644 --- a/source/Models/IFrontMatter.cs +++ b/source/Models/IFrontMatter.cs @@ -15,15 +15,15 @@ public interface IFrontMatter : IParams, IFile /// /// The first directory where the content is located, inside content. /// - /// + /// /// - /// If the content is located at content/blog/2021-01-01-Hello-World.md, + /// If the content is located at content/blog/2021-01-01-Hello-World.md, /// then the value of this property will be blog. /// string? Section { get; } /// - /// The type of content. It's the will be "page", if not specified. + /// The type of content. It will be "page", if not specified. /// string? Type { get; } @@ -36,7 +36,7 @@ public interface IFrontMatter : IParams, IFile /// /// URL: my-page /// - /// will be converted to /my-page, independetly of the page title. + /// will be converted to /my-page, independently of the page title. /// /// /// @@ -47,8 +47,8 @@ public interface IFrontMatter : IParams, IFile string? URL { get; } /// - /// True for draft content. It will not be rendered unless - /// a option is set to true. + /// True for draft content. It will not be rendered unless + /// an option is set to true. /// bool? Draft { get; } @@ -111,7 +111,7 @@ public interface IFrontMatter : IParams, IFile Kind Kind { get; } /// - /// The date to be considered as the publish date. + /// The date to be considered as the publishing date. /// DateTime? GetPublishDate => PublishDate ?? Date; } diff --git a/source/Models/IPage.cs b/source/Models/IPage.cs index 83b7e0e22d3db379be2a5d930c06bcf1ec73e45f..efe55cc41b4a7ab32294035641721acef217aac2 100644 --- a/source/Models/IPage.cs +++ b/source/Models/IPage.cs @@ -75,14 +75,14 @@ public interface IPage : IFrontMatter, IOutput /// /// Just a simple check if the current page is a "page" /// - public bool IsPage => Kind == Kind.single; + public bool IsPage => Kind == Kind.Single; /// /// The number of words in the main content /// - public int WordCount => Plain.Split(nonWords, StringSplitOptions.RemoveEmptyEntries).Length; + public int WordCount => Plain.Split(NonWords, StringSplitOptions.RemoveEmptyEntries).Length; - private static readonly char[] nonWords = [' ', ',', ';', '.', '!', '"', '(', ')', '?', '\n', '\r']; + private static readonly char[] NonWords = [' ', ',', ';', '.', '!', '"', '(', ')', '?', '\n', '\r']; /// /// The markdown content converted to HTML @@ -114,14 +114,14 @@ public interface IPage : IFrontMatter, IOutput /// /// Get all URLs related to this content. /// - public Dictionary AllOutputURLs { get; } + public Dictionary AllOutputUrLs { get; } /// /// Gets the Permalink path for the file. /// - /// The URL to consider. If null use the predefined URL + /// The URL to consider. If null use the predefined URL /// The output path. - public string CreatePermalink(string? URLforce = null); + public string CreatePermalink(string? urlForce = null); /// /// Final steps of parsing the content. diff --git a/source/Models/IResource.cs b/source/Models/IResource.cs index 003191f9770688120e71badee448a6a25e56c8e4..e8ad67cd291fd57add3442023c0d1aa098a2e139 100644 --- a/source/Models/IResource.cs +++ b/source/Models/IResource.cs @@ -5,9 +5,13 @@ namespace SuCoS.Models; /// public interface IResource : IFile, IOutput, IParams { - /// + /// + /// Resource name. + /// public string? Title { get; set; } - /// + /// + /// Resource file name. + /// public string? FileName { get; set; } } diff --git a/source/Models/ISite.cs b/source/Models/ISite.cs index 3c92051a6ab983cb897d46928c2e8c1c671b2692..f2084ec11d2a2c8317c7374dba941e003209dea9 100644 --- a/source/Models/ISite.cs +++ b/source/Models/ISite.cs @@ -1,7 +1,7 @@ using Serilog; using SuCoS.Helpers; using SuCoS.Models.CommandLineOptions; -using SuCoS.Parser; +using SuCoS.Parsers; using SuCoS.TemplateEngine; using System.Collections.Concurrent; @@ -10,7 +10,7 @@ namespace SuCoS.Models; /// /// The main configuration of the program, primarily extracted from the app.yaml file. /// -public interface ISite : ISiteSettings, IParams +public interface ISite : ISiteSettings { /// /// Command line options @@ -84,7 +84,7 @@ public interface ISite : ISiteSettings, IParams /// /// Search recursively for all markdown files in the content folder, then - /// parse their content for front matter meta data and markdown. + /// parse their content for front matter metadata and markdown. /// /// /// Folder to scan diff --git a/source/Models/Kind.cs b/source/Models/Kind.cs index 799009fc269d881cced8cbb13f4d864de765cbcd..99625fc853e7abf76118d9b5383e115a0f90bf98 100644 --- a/source/Models/Kind.cs +++ b/source/Models/Kind.cs @@ -8,15 +8,15 @@ public enum Kind /// /// A single content page. /// - single, + Single, /// /// List of contents /// - list, + List, /// /// Special page, like the home page. It will be rendered as index.html. /// - index + Index } diff --git a/source/Models/Page.cs b/source/Models/Page.cs index aaa16e5cd3d23f2f8e3217e29ca9849c866c5dd3..a11c1c4dc67be1594470b180c274623ec6763bc2 100644 --- a/source/Models/Page.cs +++ b/source/Models/Page.cs @@ -11,83 +11,83 @@ namespace SuCoS.Models; /// public class Page : IPage { - private readonly IFrontMatter frontMatter; + private readonly IFrontMatter _frontMatter; #region IFrontMatter /// - public string? Title => frontMatter.Title; + public string? Title => _frontMatter.Title; /// - public string? Type => frontMatter.Type; + public string? Type => _frontMatter.Type; /// - public string? URL => frontMatter.URL; + public string? URL => _frontMatter.URL; /// - public bool? Draft => frontMatter.Draft; + public bool? Draft => _frontMatter.Draft; /// - public List? Aliases => frontMatter.Aliases; + public List? Aliases => _frontMatter.Aliases; /// - public string? Section => frontMatter.Section; + public string? Section => _frontMatter.Section; /// - public DateTime? Date => frontMatter.Date; + public DateTime? Date => _frontMatter.Date; /// - public DateTime? LastMod => frontMatter.LastMod; + public DateTime? LastMod => _frontMatter.LastMod; /// - public DateTime? PublishDate => frontMatter.PublishDate; + public DateTime? PublishDate => _frontMatter.PublishDate; /// - public DateTime? ExpiryDate => frontMatter.ExpiryDate; + public DateTime? ExpiryDate => _frontMatter.ExpiryDate; /// - public int Weight => frontMatter.Weight; + public int Weight => _frontMatter.Weight; /// - public List? Tags => frontMatter.Tags; + public List? Tags => _frontMatter.Tags; /// public List? ResourceDefinitions { - get => frontMatter.ResourceDefinitions; - set => frontMatter.ResourceDefinitions = value; + get => _frontMatter.ResourceDefinitions; + set => _frontMatter.ResourceDefinitions = value; } /// - public string RawContent => frontMatter.RawContent; + public string RawContent => _frontMatter.RawContent; /// public Kind Kind { - get => frontMatter.Kind; - set => (frontMatter as FrontMatter)!.Kind = value; + get => _frontMatter.Kind; + set => (_frontMatter as FrontMatter)!.Kind = value; } /// - public string? SourceRelativePath => frontMatter.SourceRelativePath; + public string? SourceRelativePath => _frontMatter.SourceRelativePath; /// - public string? SourceRelativePathDirectory => frontMatter.SourceRelativePathDirectory; + public string? SourceRelativePathDirectory => _frontMatter.SourceRelativePathDirectory; /// - public string SourceFullPath => frontMatter.SourceFullPath; + public string SourceFullPath => _frontMatter.SourceFullPath; /// - public string? SourceFullPathDirectory => frontMatter.SourceFullPathDirectory; + public string? SourceFullPathDirectory => _frontMatter.SourceFullPathDirectory; /// - public string? SourceFileNameWithoutExtension => frontMatter.SourceFileNameWithoutExtension; + public string? SourceFileNameWithoutExtension => _frontMatter.SourceFileNameWithoutExtension; /// public Dictionary Params { - get => frontMatter.Params; - set => frontMatter.Params = value; + get => _frontMatter.Params; + set => _frontMatter.Params = value; } #endregion IFrontMatter @@ -122,7 +122,7 @@ public class Page : IPage public IPage? Parent { get; set; } /// - public BundleType BundleType { get; set; } = BundleType.none; + public BundleType BundleType { get; set; } = BundleType.None; /// public Collection? Resources { get; set; } @@ -150,19 +150,19 @@ public class Page : IPage /// /// Just a simple check if the current page is a "page" /// - public bool IsPage => Kind == Kind.single; + public bool IsPage => Kind == Kind.Single; /// /// The number of words in the main content /// - public int WordCount => Plain.Split(nonWords, StringSplitOptions.RemoveEmptyEntries).Length; + public int WordCount => Plain.Split(NonWords, StringSplitOptions.RemoveEmptyEntries).Length; - private static readonly char[] nonWords = [' ', ',', ';', '.', '!', '"', '(', ')', '?', '\n', '\r']; + private static readonly char[] NonWords = [' ', ',', ';', '.', '!', '"', '(', ')', '?', '\n', '\r']; /// /// The markdown content converted to HTML /// - public string ContentPreRendered => contentPreRenderedCached.Value; + public string ContentPreRendered => ContentPreRenderedCached.Value; /// /// The processed content. @@ -171,8 +171,8 @@ public class Page : IPage { get { - contentCache = ParseAndRenderTemplate(false, "Error rendering theme template: {Error}"); - return contentCache!; + ContentCache = ParseAndRenderTemplate(false, "Error rendering theme template: {Error}"); + return ContentCache!; } } @@ -190,21 +190,20 @@ public class Page : IPage { get { - if (pagesCached is not null) + if (PagesCached is not null) { - return pagesCached; + return PagesCached; } - pagesCached = []; + PagesCached = []; foreach (var permalink in PagesReferences) { - var page = Site.OutputReferences[permalink] as IPage; - if (page is not null) + if (Site.OutputReferences[permalink] is IPage page) { - pagesCached.Add(page); + PagesCached.Add(page); } } - return pagesCached; + return PagesCached; } } @@ -215,17 +214,17 @@ public class Page : IPage { get { - regularPagesCache ??= Pages - .Where(page => page.Kind == Kind.single) + _regularPagesCache ??= Pages + .Where(page => page.Kind == Kind.Single) .ToList(); - return regularPagesCache; + return _regularPagesCache; } } /// /// Get all URLs related to this content. /// - public Dictionary AllOutputURLs + public Dictionary AllOutputUrLs { get { @@ -247,14 +246,15 @@ public class Page : IPage } } - if (Resources is not null) + if (Resources is null) { - foreach (var resource in Resources) + return urls; + } + foreach (var resource in Resources) + { + if (resource.Permalink is not null) { - if (resource.Permalink is not null && !urls.ContainsKey(resource.Permalink)) - { - urls.Add(resource.Permalink, resource); - } + urls.TryAdd(resource.Permalink, resource); } } @@ -266,14 +266,14 @@ public class Page : IPage /// /// The markdown content. /// - private Lazy contentPreRenderedCached => new(() => Markdown.ToHtml(RawContent, SiteHelper.MarkdownPipeline)); + private Lazy ContentPreRenderedCached => new(() => Markdown.ToHtml(RawContent, SiteHelper.MarkdownPipeline)); /// /// The cached content. /// - private string? contentCache { get; set; } + private string? ContentCache { get; set; } - private const string urlForIndex = @"{%- liquid + private const string UrlForIndex = @"{%- liquid if page.Parent echo page.Parent.Permalink echo '/' @@ -284,7 +284,7 @@ else echo page.SourcePathLastDirectory endif -%}"; - private const string urlForNonIndex = @"{%- liquid + private const string UrlForNonIndex = @"{%- liquid if page.Parent echo page.Parent.Permalink echo '/' @@ -296,49 +296,49 @@ echo page.SourceFileNameWithoutExtension endif -%}"; - private List? regularPagesCache; + private List? _regularPagesCache; - private List? pagesCached { get; set; } + private List? PagesCached { get; set; } /// /// Constructor /// public Page(in IFrontMatter frontMatter, in ISite site) { - this.frontMatter = frontMatter; + _frontMatter = frontMatter; Site = site; } /// /// Gets the Permalink path for the file. /// - /// The URL to consider. If null use the predefined URL + /// The URL to consider. If null use the predefined URL /// The output path. - /// - /// - public string CreatePermalink(string? URLforce = null) + /// + /// + public string CreatePermalink(string? urlForce = null) { var isIndex = SourceFileNameWithoutExtension == "index"; - var permaLink = string.Empty; + var permalink = string.Empty; - URLforce ??= URL ?? (isIndex ? urlForIndex : urlForNonIndex); + urlForce ??= URL ?? (isIndex ? UrlForIndex : UrlForNonIndex); try { - permaLink = Site.TemplateEngine.Parse(URLforce, Site, this); + permalink = Site.TemplateEngine.Parse(urlForce, Site, this); } catch (Exception ex) { - Site.Logger.Error(ex, "Error converting URL: {URLforce}", URLforce); + Site.Logger.Error(ex, "Error converting URL: {UrlForce}", urlForce); } - if (!Path.IsPathRooted(permaLink) && !permaLink.StartsWith('/')) + if (!Path.IsPathRooted(permalink) && !permalink.StartsWith('/')) { - permaLink = $"/{permaLink}"; + permalink = $"/{permalink}"; } - return Urlizer.UrlizePath(permaLink); + return Urlizer.UrlizePath(permalink); } /// @@ -366,17 +366,17 @@ endif ScanForResources(); } - private int counterInternal; - private bool counterInternalLock; - private int counter + private int _counterInternal; + private bool _counterInternalLock; + private int Counter { get { - if (!counterInternalLock) + if (!_counterInternalLock) { - counterInternalLock = true; + _counterInternalLock = true; } - return counterInternal; + return _counterInternal; } } @@ -386,7 +386,12 @@ endif { return; } - if (BundleType == BundleType.none) + if (BundleType == BundleType.None) + { + return; + } + + if (!Directory.Exists(SourceFullPathDirectory)) { return; } @@ -396,7 +401,7 @@ endif var resourceFiles = Directory.GetFiles(SourceFullPathDirectory) .Where(file => file != SourceFullPath && - (BundleType == BundleType.leaf || !file.EndsWith(".md", StringComparison.OrdinalIgnoreCase)) + (BundleType == BundleType.Leaf || !file.EndsWith(".md", StringComparison.OrdinalIgnoreCase)) ); foreach (var resourceFilename in resourceFiles) @@ -404,16 +409,16 @@ endif Resources ??= []; var filenameOriginal = Path.GetFileName(resourceFilename); var filename = filenameOriginal; - var extention = Path.GetExtension(resourceFilename); + var extension = Path.GetExtension(resourceFilename); var title = filename; Dictionary resourceParams = []; if (ResourceDefinitions is not null) { - if (counterInternalLock) + if (_counterInternalLock) { - counterInternalLock = false; - ++counterInternal; + _counterInternalLock = false; + ++_counterInternal; } foreach (var resourceDefinition in ResourceDefinitions) { @@ -422,14 +427,14 @@ endif var file = new InMemoryDirectoryInfo("./", new[] { filenameOriginal }); if (resourceDefinition.GlobMatcher.Execute(file).HasMatches) { - filename = Site.TemplateEngine.ParseResource(resourceDefinition.Name, Site, this, counter) ?? filename; - title = Site.TemplateEngine.ParseResource(resourceDefinition.Title, Site, this, counter) ?? filename; - resourceParams = resourceDefinition.Params ?? []; + filename = Site.TemplateEngine.ParseResource(resourceDefinition.Name, Site, this, Counter) ?? filename; + title = Site.TemplateEngine.ParseResource(resourceDefinition.Title, Site, this, Counter) ?? filename; + resourceParams = resourceDefinition.Params; } } } - filename = Path.GetFileNameWithoutExtension(filename) + extention; + filename = Path.GetFileNameWithoutExtension(filename) + extension; var resource = new Resource() { Title = title, @@ -443,7 +448,7 @@ endif } catch { - return; + // ignored } } diff --git a/source/Models/Resource.cs b/source/Models/Resource.cs index 0bae647079d84993c1ad6e7d700477a0c4b4af44..fdb863f74f6e15e0e86704fc890b3137fcf50bdf 100644 --- a/source/Models/Resource.cs +++ b/source/Models/Resource.cs @@ -12,7 +12,7 @@ public class Resource : IResource public string? FileName { get; set; } /// - public required string SourceFullPath { get; set; } + public required string SourceFullPath { get; init; } /// public string? SourceRelativePath => null; diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 16338f3f94947def9a03fe343d2140f8f57496f8..ee19533d3c834ef4bc88f3b54481fdd75f4b66a0 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -1,7 +1,7 @@ using Serilog; using SuCoS.Helpers; using SuCoS.Models.CommandLineOptions; -using SuCoS.Parser; +using SuCoS.Parsers; using SuCoS.TemplateEngine; using System.Collections.Concurrent; @@ -17,8 +17,8 @@ public class Site : ISite /// public Dictionary Params { - get => settings.Params; - set => settings.Params = value; + get => _settings.Params; + set => _settings.Params = value; } #endregion IParams @@ -31,19 +31,19 @@ public class Site : ISite #region SiteSettings /// - public string Title => settings.Title; + public string Title => _settings.Title; /// - public string? Description => settings.Description; + public string? Description => _settings.Description; /// - public string? Copyright => settings.Copyright; + public string? Copyright => _settings.Copyright; /// - public string BaseURL => settings.BaseURL; + public string BaseURL => _settings.BaseURL; /// - public bool UglyURLs => settings.UglyURLs; + public bool UglyURLs => _settings.UglyURLs; #endregion SiteSettings @@ -60,7 +60,7 @@ public class Site : ISite /// /// The path theme. /// - public string SourceThemePath => Path.Combine(Options.Source, settings.ThemeDir, settings.Theme ?? string.Empty); + public string SourceThemePath => Path.Combine(Options.Source, _settings.ThemeDir, _settings.Theme ?? string.Empty); /// public IEnumerable SourceFolders => [ @@ -72,7 +72,7 @@ public class Site : ISite /// /// Theme used. /// - public Theme? Theme { get; set; } + public Theme? Theme { get; } /// /// List of all pages, including generated. @@ -81,11 +81,11 @@ public class Site : ISite { get { - pagesCache ??= OutputReferences.Values - .Where(output => output is IPage page) + _pagesCache ??= OutputReferences.Values + .Where(output => output is IPage) .Select(output => (output as IPage)!) .OrderBy(page => -page.Weight); - return pagesCache!; + return _pagesCache!; } } @@ -101,11 +101,11 @@ public class Site : ISite { get { - regularPagesCache ??= OutputReferences - .Where(pair => pair.Value is IPage page && page.IsPage && pair.Key == page.Permalink) + _regularPagesCache ??= OutputReferences + .Where(pair => pair.Value is IPage { IsPage: true } page && pair.Key == page.Permalink) .Select(pair => (pair.Value as IPage)!) .OrderBy(page => -page.Weight); - return regularPagesCache; + return _regularPagesCache; } } @@ -127,35 +127,35 @@ public class Site : ISite /// /// Number of files parsed, used in the report. /// - public int FilesParsedToReport => filesParsedToReport; + public int FilesParsedToReport => _filesParsedToReport; /// - public IMetadataParser Parser { get; init; } = new YAMLParser(); + public IMetadataParser Parser { get; } - private int filesParsedToReport; + private int _filesParsedToReport; - private const string indexLeafFileConst = "index.md"; + private const string IndexLeafFileConst = "index.md"; - private const string indexBranchFileConst = "_index.md"; + private const string IndexBranchFileConst = "_index.md"; /// - /// The synchronization lock object during ProstProcess. + /// The synchronization lock object during PostProcess. /// - private readonly object syncLockPostProcess = new(); + private readonly object _syncLockPostProcess = new(); - private IEnumerable? pagesCache; + private IEnumerable? _pagesCache; - private IEnumerable? regularPagesCache; + private IEnumerable? _regularPagesCache; - private readonly SiteSettings settings; + private readonly SiteSettings _settings; /// /// Datetime wrapper /// - private readonly ISystemClock clock; + private readonly ISystemClock _clock; /// - public ITemplateEngine TemplateEngine { get; set; } + public ITemplateEngine TemplateEngine { get; } /// /// Constructor @@ -167,12 +167,12 @@ public class Site : ISite in ILogger logger, ISystemClock? clock) { Options = options; - this.settings = settings; + _settings = settings; Logger = logger; Parser = parser; TemplateEngine = new FluidTemplateEngine(); - this.clock = clock ?? new SystemClock(); + _clock = clock ?? new SystemClock(); Theme = Theme.CreateFromSite(this); } @@ -224,10 +224,10 @@ public class Site : ISite relativePath = Urlizer.Path(relativePath); FrontMatter frontMatter = new() { - Kind = isIndex ? Kind.index : Kind.list, + Kind = isIndex ? Kind.Index : Kind.List, Section = isIndex ? "index" : sectionName, - SourceRelativePath = Urlizer.Path(Path.Combine(relativePath, indexLeafFileConst)), - SourceFullPath = Urlizer.Path(Path.Combine(SourceContentPath, relativePath, indexLeafFileConst)), + SourceRelativePath = Urlizer.Path(Path.Combine(relativePath, IndexLeafFileConst)), + SourceFullPath = Urlizer.Path(Path.Combine(SourceContentPath, relativePath, IndexLeafFileConst)), Title = title, Type = isIndex ? "index" : sectionName, URL = relativePath @@ -236,7 +236,7 @@ public class Site : ISite var id = frontMatter.URL; // Get or create the page - var lazyPage = CacheManager.automaticContentCache.GetOrAdd(id, new Lazy(() => + var lazyPage = CacheManager.AutomaticContentCache.GetOrAdd(id, new Lazy(() => { IPage? parent = null; // Check if we need to create a section, even @@ -248,7 +248,7 @@ public class Site : ISite var newPage = new Page(frontMatter, this) { - BundleType = BundleType.branch + BundleType = BundleType.Branch }; PostProcessPage(newPage, parent); return newPage; @@ -262,7 +262,7 @@ public class Site : ISite return page; } - if (page.Kind != Kind.index) + if (page.Kind != Kind.Index) { page.PagesReferences.Add(originalPage.Permalink); } @@ -278,23 +278,21 @@ public class Site : ISite private void ParseIndexPage(string? directory, int level, ref IPage? parent, ref string[] markdownFiles) { - var indexLeafBundlePage = markdownFiles.FirstOrDefault(file => Path.GetFileName(file) == indexLeafFileConst); + var indexLeafBundlePage = markdownFiles.FirstOrDefault(file => Path.GetFileName(file) == IndexLeafFileConst); - var indexBranchBundlePage = markdownFiles.FirstOrDefault(file => Path.GetFileName(file) == indexBranchFileConst); + var indexBranchBundlePage = markdownFiles.FirstOrDefault(file => Path.GetFileName(file) == IndexBranchFileConst); - IPage? page = null; if (indexLeafBundlePage is not null || indexBranchBundlePage is not null) { // Determine the file to use and the bundle type var selectedFile = indexLeafBundlePage ?? indexBranchBundlePage; - var bundleType = selectedFile == indexLeafBundlePage ? BundleType.leaf : BundleType.branch; + var bundleType = selectedFile == indexLeafBundlePage ? BundleType.Leaf : BundleType.Branch; // Remove the selected file from markdownFiles - markdownFiles = bundleType == BundleType.leaf - ? Array.Empty() - : markdownFiles.Where(file => file != selectedFile).ToArray(); + markdownFiles = bundleType == BundleType.Leaf + ? [] : markdownFiles.Where(file => file != selectedFile).ToArray(); - page = ParseSourceFile(selectedFile!, parent, bundleType); + IPage? page = ParseSourceFile(selectedFile!, parent, bundleType); if (page is null) { return; @@ -302,9 +300,9 @@ public class Site : ISite if (level == 0) { - _ = OutputReferences.TryRemove(page!.Permalink!, out _); + _ = OutputReferences.TryRemove(page.Permalink!, out _); page.Permalink = "/"; - page.Kind = Kind.index; + page.Kind = Kind.Index; _ = OutputReferences.GetOrAdd(page.Permalink, page); Home = page; @@ -325,18 +323,16 @@ public class Site : ISite } } - private Page? ParseSourceFile(in string fileFullPath, in IPage? parent, BundleType bundleType = BundleType.none) + private Page? ParseSourceFile(in string fileFullPath, in IPage? parent, BundleType bundleType = BundleType.None) { Page? page = null; try { var fileContent = File.ReadAllText(fileFullPath); var fileRelativePath = Path.GetRelativePath( - SourceContentPath ?? string.Empty, + SourceContentPath, fileFullPath ); - // var frontMatter = Parser.ParseFrontmatterAndMarkdown(fileFullPath, fileRelativePath, fileContent) - // ?? throw new FormatException($"Error parsing front matter for {fileFullPath}"); var frontMatter = FrontMatter.Parse(fileFullPath, fileRelativePath, Parser, fileContent) ?? throw new FormatException($"Error parsing front matter for {fileFullPath}"); @@ -354,8 +350,8 @@ public class Site : ISite Logger.Error(ex, "Error parsing file {file}", fileFullPath); } - // Use interlocked to safely increment the counter in a multi-threaded environment - _ = Interlocked.Increment(ref filesParsedToReport); + // Use interlocked to safely increment the counter in a multithreaded environment + _ = Interlocked.Increment(ref _filesParsedToReport); return page; } @@ -372,23 +368,23 @@ public class Site : ISite page.Parent = parent; page.Permalink = page.CreatePermalink(); - lock (syncLockPostProcess) + lock (_syncLockPostProcess) { if (!OutputReferences.TryGetValue(page.Permalink, out var oldOutput) || overwrite) { page.PostProcess(); // Replace the old page with the newly created one - if (oldOutput is IPage oldpage && oldpage.PagesReferences is not null) + if (oldOutput is IPage oldPage) { - foreach (var pageOld in oldpage.PagesReferences) + foreach (var pageOld in oldPage.PagesReferences) { page.PagesReferences.Add(pageOld); } } // Register the page for all urls - foreach (var pageOutput in page.AllOutputURLs) + foreach (var pageOutput in page.AllOutputUrLs) { _ = OutputReferences.TryAdd(pageOutput.Key, pageOutput.Value); } @@ -430,7 +426,7 @@ public class Site : ISite { ArgumentNullException.ThrowIfNull(frontMatter); - return frontMatter.ExpiryDate is not null && frontMatter.ExpiryDate <= clock.Now; + return frontMatter.ExpiryDate is not null && frontMatter.ExpiryDate <= _clock.Now; } /// @@ -440,6 +436,6 @@ public class Site : ISite { ArgumentNullException.ThrowIfNull(frontMatter); - return frontMatter.GetPublishDate is null || frontMatter.GetPublishDate <= clock.Now; + return frontMatter.GetPublishDate is null || frontMatter.GetPublishDate <= _clock.Now; } } diff --git a/source/Parsers/IMetadataParser.cs b/source/Parsers/IMetadataParser.cs index 21974a94e426209de83929f6a9f238f3e75e826b..c83de158c0dbe5de8a10c13988079ba0498e3cce 100644 --- a/source/Parsers/IMetadataParser.cs +++ b/source/Parsers/IMetadataParser.cs @@ -1,6 +1,4 @@ -using SuCoS.Models; - -namespace SuCoS.Parser; +namespace SuCoS.Parsers; /// /// Responsible for parsing the content metadata @@ -22,9 +20,9 @@ public interface IMetadataParser T Parse(string content); /// - /// Deserialized a object. + /// Deserialized an object. /// /// /// void Export(T data, string path); -} \ No newline at end of file +} diff --git a/source/Parsers/ParamsConverter.cs b/source/Parsers/ParamsConverter.cs index 952b3eaf21dc69e37327144538f30fc8b116054a..d77d238afb31040356919b3fa5fd034fb485309b 100644 --- a/source/Parsers/ParamsConverter.cs +++ b/source/Parsers/ParamsConverter.cs @@ -2,7 +2,7 @@ using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; -namespace SuCoS.Parser; +namespace SuCoS.Parsers; /// /// A custom YAML type converter for dictionaries with string keys and object values. diff --git a/source/Parsers/StaticContext.cs b/source/Parsers/StaticAotContext.cs similarity index 64% rename from source/Parsers/StaticContext.cs rename to source/Parsers/StaticAotContext.cs index c0bcf0c6a8e599a8ca577ca8223af83872a95d09..80e656deb18b6536975a5d5d314f8691551b4044 100644 --- a/source/Parsers/StaticContext.cs +++ b/source/Parsers/StaticAotContext.cs @@ -1,11 +1,9 @@ using YamlDotNet.Serialization; -namespace SuCoS.Parser; +namespace SuCoS.Parsers; /// /// The rest of this partial class gets generated at build time /// [YamlStaticContext] -public partial class StaticAOTContext : StaticContext -{ -} +public partial class StaticAotContext : StaticContext; diff --git a/source/Parsers/YAMLParser.cs b/source/Parsers/YAMLParser.cs index f2f212cdc5f028aeb604574c965bcdce7416262d..133d978febb7a1b7e085d876a435d8c045e334ba 100644 --- a/source/Parsers/YAMLParser.cs +++ b/source/Parsers/YAMLParser.cs @@ -2,24 +2,24 @@ using System.Text; using FolkerKinzel.Strings; using YamlDotNet.Serialization; -namespace SuCoS.Parser; +namespace SuCoS.Parsers; /// /// Responsible for parsing the content front matter using YAML /// -public class YAMLParser : IMetadataParser +public class YamlParser : IMetadataParser { /// /// YamlDotNet parser, strictly set to allow automatically parse only known fields /// - private readonly IDeserializer deserializer; + private readonly IDeserializer _deserializer; /// /// ctor /// - public YAMLParser() + public YamlParser() { - deserializer = new StaticDeserializerBuilder(new StaticAOTContext()) + _deserializer = new StaticDeserializerBuilder(new StaticAotContext()) .WithTypeConverter(new ParamsConverter()) .IgnoreUnmatchedProperties() .Build(); @@ -30,7 +30,7 @@ public class YAMLParser : IMetadataParser { try { - return deserializer.Deserialize(content); + return _deserializer.Deserialize(content); } catch { diff --git a/source/Program.cs b/source/Program.cs index 4a5e3b4e185850880ed58730fa8b976f9853c486..02cf05cedc58f0578fa37f0301bd0566693c292e 100644 --- a/source/Program.cs +++ b/source/Program.cs @@ -5,6 +5,7 @@ using SuCoS.Models.CommandLineOptions; using System.Reflection; using CommandLine; using System.Diagnostics.CodeAnalysis; +using SuCoS.Commands; namespace SuCoS; @@ -19,7 +20,7 @@ public class Program(ILogger logger) /// /// Basic logo of the program, for fun /// - private static readonly string helloWorld = @" + private static readonly string HelloWorld = @" ░█▀▀░░░░░█▀▀░░░░░█▀▀ ░▀▀█░█░█░█░░░█▀█░▀▀█ ░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀"; @@ -50,7 +51,7 @@ public class Program(ILogger logger) { OutputLogo(); OutputWelcome(); - return await CommandLine.Parser.Default.ParseArguments(args) + return await Parser.Default.ParseArguments(args) .WithParsed(options => { logger = CreateLogger(options.Verbose); @@ -112,12 +113,12 @@ public class Program(ILogger logger) var command = new NewThemeCommand(options, logger); return Task.FromResult(command.Run()); }, - errs => Task.FromResult(0) + _ => Task.FromResult(0) ).ConfigureAwait(false); } /// - /// Create a log (normally from Serilog), depending the verbose option + /// Create a log (normally from Serilog), depending on the verbose option /// /// /// @@ -144,6 +145,6 @@ public class Program(ILogger logger) /// public void OutputLogo() { - logger.Information(helloWorld); + logger.Information(HelloWorld); } } diff --git a/source/ServerHandlers/HttpListenerResponseWrapper.cs b/source/ServerHandlers/HttpListenerResponseWrapper.cs index 22485e78c6a26e132a9a1fff5cac17874ecc5cd4..05f1f0ea48c0683bfab4a5bb329dae1b1c9618ff 100644 --- a/source/ServerHandlers/HttpListenerResponseWrapper.cs +++ b/source/ServerHandlers/HttpListenerResponseWrapper.cs @@ -11,8 +11,6 @@ namespace SuCoS.ServerHandlers; /// public class HttpListenerResponseWrapper(HttpListenerResponse response) : IHttpListenerResponse { - private readonly HttpListenerResponse response = response; - /// public Stream OutputStream => response.OutputStream; diff --git a/source/ServerHandlers/PingRequests.cs b/source/ServerHandlers/PingRequests.cs index 3d2f57130c40b55c45a9f79add04c413463b4ec0..bf9003d450cea2a36e72a7fab2b978efa1f07a40 100644 --- a/source/ServerHandlers/PingRequests.cs +++ b/source/ServerHandlers/PingRequests.cs @@ -18,7 +18,7 @@ public class PingRequests : IServerHandlers var content = serverStartTime.ToString("o"); - using var writer = new StreamWriter(response.OutputStream, leaveOpen: true); + await using var writer = new StreamWriter(response.OutputStream, leaveOpen: true); await writer.WriteAsync(content).ConfigureAwait(false); await writer.FlushAsync().ConfigureAwait(false); diff --git a/source/ServerHandlers/RegisteredPageRequest.cs b/source/ServerHandlers/RegisteredPageRequest.cs index 12e06c7b03ebbe8babd3d4850e85d5561a806a8c..0d53fe4e2bc69fa537f551d348ddd013aabfb212 100644 --- a/source/ServerHandlers/RegisteredPageRequest.cs +++ b/source/ServerHandlers/RegisteredPageRequest.cs @@ -8,7 +8,7 @@ namespace SuCoS.ServerHandlers; /// public class RegisteredPageRequest : IServerHandlers { - private readonly ISite site; + private readonly ISite _site; /// /// Constructor @@ -16,7 +16,7 @@ public class RegisteredPageRequest : IServerHandlers /// public RegisteredPageRequest(ISite site) { - this.site = site; + _site = site; } /// @@ -24,7 +24,7 @@ public class RegisteredPageRequest : IServerHandlers { ArgumentNullException.ThrowIfNull(requestPath); - return site.OutputReferences.TryGetValue(requestPath, out var item) && item is IPage _; + return _site.OutputReferences.TryGetValue(requestPath, out var item) && item is IPage; } /// @@ -32,18 +32,17 @@ public class RegisteredPageRequest : IServerHandlers { ArgumentNullException.ThrowIfNull(response); - if (site.OutputReferences.TryGetValue(requestPath, out var output) && output is IPage page) - { - var content = page.CompleteContent; - content = InjectReloadScript(content); - using var writer = new StreamWriter(response.OutputStream, leaveOpen: true); - await writer.WriteAsync(content).ConfigureAwait(false); - return "dict"; - } - else + if (!_site.OutputReferences.TryGetValue(requestPath, out var output) || + output is not IPage page) { return "404"; } + var content = page.CompleteContent; + content = InjectReloadScript(content); + await using var writer = new StreamWriter(response.OutputStream, leaveOpen: true); + await writer.WriteAsync(content).ConfigureAwait(false); + return "dict"; + } /// @@ -52,15 +51,14 @@ public class RegisteredPageRequest : IServerHandlers /// /// The content to inject the reload script into. /// The content with the reload script injected. - private string InjectReloadScript(string content) + private static string InjectReloadScript(string content) { // Read the content of the JavaScript file - string scriptContent; var assembly = Assembly.GetExecutingAssembly(); using var stream = assembly.GetManifestResourceStream("SuCoS.wwwroot.js.reload.js") ?? throw new FileNotFoundException("Could not find the embedded JavaScript resource."); using var reader = new StreamReader(stream); - scriptContent = reader.ReadToEnd(); + var scriptContent = reader.ReadToEnd(); // Inject the JavaScript content var reloadScript = $""; diff --git a/source/ServerHandlers/RegisteredPageResourceRequest.cs b/source/ServerHandlers/RegisteredPageResourceRequest.cs index 4d117e326747a75aa1f0c4973624d475524580e2..0f27243aca98032135d717b1231ff2570208d5c2 100644 --- a/source/ServerHandlers/RegisteredPageResourceRequest.cs +++ b/source/ServerHandlers/RegisteredPageResourceRequest.cs @@ -1,5 +1,4 @@ using SuCoS.Models; -using System.Reflection; namespace SuCoS.ServerHandlers; @@ -8,7 +7,7 @@ namespace SuCoS.ServerHandlers; /// public class RegisteredPageResourceRequest : IServerHandlers { - readonly ISite site; + private readonly ISite _site; /// /// Constructor @@ -16,7 +15,7 @@ public class RegisteredPageResourceRequest : IServerHandlers /// public RegisteredPageResourceRequest(ISite site) { - this.site = site; + _site = site; } /// @@ -24,7 +23,7 @@ public class RegisteredPageResourceRequest : IServerHandlers { ArgumentNullException.ThrowIfNull(requestPath); - return site.OutputReferences.TryGetValue(requestPath, out var item) && item is IResource _; + return _site.OutputReferences.TryGetValue(requestPath, out var item) && item is IResource; } /// @@ -32,41 +31,15 @@ public class RegisteredPageResourceRequest : IServerHandlers { ArgumentNullException.ThrowIfNull(response); - if (site.OutputReferences.TryGetValue(requestPath, out var output) && output is IResource resource) - { - response.ContentType = resource.MimeType; - await using var fileStream = new FileStream(resource.SourceFullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - await fileStream.CopyToAsync(response.OutputStream).ConfigureAwait(false); - return "resource"; - } - else + if (!_site.OutputReferences.TryGetValue(requestPath, out var output) || + output is not IResource resource) { return "404"; } - } - - /// - /// Injects a reload script into the provided content. - /// The script is read from a JavaScript file and injected before the closing "body" tag. - /// - /// The content to inject the reload script into. - /// The content with the reload script injected. - private static string InjectReloadScript(string content) - { - // Read the content of the JavaScript file - string scriptContent; - var assembly = Assembly.GetExecutingAssembly(); - using var stream = assembly.GetManifestResourceStream("SuCoS.wwwroot.js.reload.js") - ?? throw new FileNotFoundException("Could not find the embedded JavaScript resource."); - using var reader = new StreamReader(stream); - scriptContent = reader.ReadToEnd(); - - // Inject the JavaScript content - var reloadScript = $""; - - const string bodyClosingTag = ""; - content = content.Replace(bodyClosingTag, $"{reloadScript}{bodyClosingTag}", StringComparison.InvariantCulture); + response.ContentType = resource.MimeType; + await using var fileStream = new FileStream(resource.SourceFullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + await fileStream.CopyToAsync(response.OutputStream).ConfigureAwait(false); + return "resource"; - return content; } } diff --git a/source/ServerHandlers/StaticFileRequest.cs b/source/ServerHandlers/StaticFileRequest.cs index 6703a150fe9b50beb58129502a1c727a3aaf87a3..67abd35a50b3e5d871a2a1e5e730785911b2d28c 100644 --- a/source/ServerHandlers/StaticFileRequest.cs +++ b/source/ServerHandlers/StaticFileRequest.cs @@ -7,8 +7,8 @@ namespace SuCoS.ServerHandlers; /// public class StaticFileRequest : IServerHandlers { - private readonly string? basePath; - private readonly bool inTheme; + private readonly string? _basePath; + private readonly bool _inTheme; /// /// Constructor @@ -17,8 +17,8 @@ public class StaticFileRequest : IServerHandlers /// public StaticFileRequest(string? basePath, bool inTheme) { - this.basePath = basePath; - this.inTheme = inTheme; + _basePath = basePath; + _inTheme = inTheme; } /// @@ -26,12 +26,12 @@ public class StaticFileRequest : IServerHandlers { ArgumentNullException.ThrowIfNull(requestPath); - if (string.IsNullOrEmpty(basePath)) + if (string.IsNullOrEmpty(_basePath)) { return false; } - var fileAbsolutePath = Path.Combine(basePath, requestPath.TrimStart('/')); + var fileAbsolutePath = Path.Combine(_basePath, requestPath.TrimStart('/')); return File.Exists(fileAbsolutePath); } @@ -41,16 +41,16 @@ public class StaticFileRequest : IServerHandlers ArgumentNullException.ThrowIfNull(requestPath); ArgumentNullException.ThrowIfNull(response); - var fileAbsolutePath = Path.Combine(basePath!, requestPath.TrimStart('/')); - response.ContentType = GetContentType(fileAbsolutePath!); - await using var fileStream = new FileStream(fileAbsolutePath!, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + var fileAbsolutePath = Path.Combine(_basePath!, requestPath.TrimStart('/')); + response.ContentType = GetContentType(fileAbsolutePath); + await using var fileStream = new FileStream(fileAbsolutePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); response.ContentLength64 = fileStream.Length; await fileStream.CopyToAsync(response.OutputStream).ConfigureAwait(false); - return inTheme ? "themeSt" : "static"; + return _inTheme ? "themeSt" : "static"; } /// - /// Retrieves the content type of a file based on its extension. + /// Retrieves the content type of file based on its extension. /// If the content type cannot be determined, the default value "application/octet-stream" is returned. /// /// The path of the file. diff --git a/source/TemplateEngine/FluidTemplateEngine.cs b/source/TemplateEngine/FluidTemplateEngine.cs index 800a2d5701f4eb3669976a0fb69208dd5432a036..bd65ba845d3d2be6cbc3c5859dedaceb767c101a 100644 --- a/source/TemplateEngine/FluidTemplateEngine.cs +++ b/source/TemplateEngine/FluidTemplateEngine.cs @@ -64,7 +64,7 @@ public class FluidTemplateEngine : ITemplateEngine /// public string? ParseResource(string? data, ISite site, IPage page, int counter) { - if (string.IsNullOrEmpty(data) || !FluidParser.TryParse(data, out var templateFileName, out var errorFileName)) + if (string.IsNullOrEmpty(data) || !FluidParser.TryParse(data, out var templateFileName, out _)) { return null; } @@ -83,7 +83,7 @@ public class FluidTemplateEngine : ITemplateEngine /// /// /// - protected static ValueTask WhereParamsFilter(FluidValue input, FilterArguments arguments, TemplateContext context) + private static ValueTask WhereParamsFilter(FluidValue input, FilterArguments arguments, TemplateContext context) { ArgumentNullException.ThrowIfNull(input); ArgumentNullException.ThrowIfNull(arguments); diff --git a/source/TemplateEngine/ITemplateEngine.cs b/source/TemplateEngine/ITemplateEngine.cs index 639ea2b766b702d92b4ff777091ffc2f05b0874f..486f01030ec2d98fd9f388627c265309d56a89d6 100644 --- a/source/TemplateEngine/ITemplateEngine.cs +++ b/source/TemplateEngine/ITemplateEngine.cs @@ -3,7 +3,7 @@ using SuCoS.Models; namespace SuCoS.TemplateEngine; /// -/// Interface for all Templace Engines (Liquid) +/// Interface for all Template Engines (Liquid) /// public interface ITemplateEngine { diff --git a/test/Parser/YAMLParserTests.cs b/test/Parser/YAMLParserTests.cs deleted file mode 100644 index 2b24f24227546d04a00e3fec24786b6aa95aa3f1..0000000000000000000000000000000000000000 --- a/test/Parser/YAMLParserTests.cs +++ /dev/null @@ -1,270 +0,0 @@ -using SuCoS.Helpers; -using SuCoS.Models; -using System.Globalization; -using Xunit; - -namespace Tests.YAMLParser; - -public class YAMLParserTests : TestSetup -{ - private readonly SuCoS.Parser.YAMLParser parser; - - private const string pageFrontmaterCONST = @"Title: Test Title -Type: post -Date: 2023-07-01 -LastMod: 2023-06-01 -PublishDate: 2023-06-01 -ExpiryDate: 2024-06-01 -Tags: - - Test - - Real Data -Categories: - - Test - - Real Data -NestedData: - Level2: - - Test - - Real Data -customParam: Custom Value -Params: - ParamsCustomParam: Custom Value - ParamsNestedData: - Level2: - - Test - - Real Data -"; - private const string pageMarkdownCONST = @" -# Real Data Test - -This is a test using real data. Real Data Test -"; - private const string siteContentCONST = @" -Title: My Site -BaseURL: https://www.example.com/ -Description: Tastiest C# Static Site Generator of the World -Copyright: Copyright message -customParam: Custom Value -NestedData: - Level2: - - Test - - Real Data -Params: - ParamsCustomParam: Custom Value - ParamsNestedData: - Level2: - - Test - - Real Data -"; - private const string fileFullPathCONST = "test.md"; - private const string fileRelativePathCONST = "test.md"; - private readonly string pageContent; - - public YAMLParserTests() - { - parser = new SuCoS.Parser.YAMLParser(); - pageContent = @$"--- -{pageFrontmaterCONST} ---- -{pageMarkdownCONST}"; - } - - [Fact] - public void GetSection_ShouldReturnFirstFolderName() - { - // Arrange - var filePath = Path.Combine("folder1", "folder2", "file.md"); - - // Act - var section = SiteHelper.GetSection(filePath); - - // Assert - Assert.Equal("folder1", section); - } - - [Theory] - [InlineData(@"--- -Title: Test Title ---- -", "Test Title")] - [InlineData(@"--- -Date: 2023-04-01 ---- -", "")] - public void ParseFrontmatter_ShouldParseTitleCorrectly(string fileContent, string expectedTitle) - { - // Arrange - var frontMatter = FrontMatter.Parse(fileRelativePathCONST, fileFullPathCONST, parser, fileContent); - - // Assert - Assert.Equal(expectedTitle, frontMatter.Title); - } - - [Theory] - [InlineData(@"--- -Date: 2023-01-01 ---- -", "2023-01-01")] - [InlineData(@"--- -Date: 2023/01/01 ---- -", "2023-01-01")] - public void ParseFrontmatter_ShouldParseDateCorrectly(string fileContent, string expectedDateString) - { - // Arrange - var expectedDate = DateTime.Parse(expectedDateString, CultureInfo.InvariantCulture); - - // Act - var frontMatter = FrontMatter.Parse(fileRelativePathCONST, fileFullPathCONST, parser, fileContent); - - // Assert - Assert.Equal(expectedDate, frontMatter.Date); - } - - [Fact] - public void ParseFrontmatter_ShouldParseOtherFieldsCorrectly() - { - // Arrange - var expectedDate = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); - var expectedLastMod = DateTime.Parse("2023-06-01", CultureInfo.InvariantCulture); - var expectedPublishDate = DateTime.Parse("2023-06-01", CultureInfo.InvariantCulture); - var expectedExpiryDate = DateTime.Parse("2024-06-01", CultureInfo.InvariantCulture); - - // Act - var frontMatter = FrontMatter.Parse(fileRelativePathCONST, fileFullPathCONST, parser, pageContent); - - // Assert - Assert.Equal("Test Title", frontMatter.Title); - Assert.Equal("post", frontMatter.Type); - Assert.Equal(expectedDate, frontMatter.Date); - Assert.Equal(expectedLastMod, frontMatter.LastMod); - Assert.Equal(expectedPublishDate, frontMatter.PublishDate); - Assert.Equal(expectedExpiryDate, frontMatter.ExpiryDate); - } - - [Fact] - public void ParseFrontmatter_ShouldThrowFormatException_WhenInvalidYAMLSyntax() - { - // Arrange - const string fileContent = @"--- -Title ---- -"; - - // Assert - Assert.Throws(() => - FrontMatter.Parse(fileRelativePathCONST, fileFullPathCONST, parser, fileContent)); - } - - [Fact] - public void ParseSiteSettings_ShouldReturnSiteWithCorrectSettings() - { - // Act - var siteSettings = parser.Parse(siteContentCONST); - - - // Assert - Assert.Equal("My Site", siteSettings.Title); - Assert.Equal("https://www.example.com/", siteSettings.BaseURL); - Assert.Equal("Tastiest C# Static Site Generator of the World", siteSettings.Description); - Assert.Equal("Copyright message", siteSettings.Copyright); - } - - [Fact] - public void ParseParams_ShouldFillParamsWithNonMatchingFields() - { - // Arrange - var frontMatter = FrontMatter.Parse(string.Empty, string.Empty, parser, pageContent); - var page = new Page(frontMatter, site); - - // Assert - Assert.False(page.Params.ContainsKey("customParam")); - Assert.Equal("Custom Value", page.Params["ParamsCustomParam"]); - } - - [Fact] - public void ParseFrontmatter_ShouldParseContentInSiteFolder() - { - // Arrange - var date = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); - var frontMatter = FrontMatter.Parse(string.Empty, string.Empty, parser, pageContent); - Page page = new(frontMatter, site); - - // Act - site.PostProcessPage(page); - - // Assert - Assert.Equal(date, frontMatter.Date); - } - - [Fact] - public void ParseFrontmatter_ShouldCreateTags() - { - // Arrange - var frontMatter = FrontMatter.Parse(string.Empty, string.Empty, parser, pageContent); - Page page = new(frontMatter, site); - - // Act - site.PostProcessPage(page); - - // Assert - Assert.Equal(2, page.TagsReference.Count); - } - - [Fact] - public void FrontMatterParse_RawContentNull() - { - _ = Assert.Throws(() => FrontMatter.Parse("invalidFrontmatter", "", "fakePath", "fakePath", frontMatterParser)); - } - - [Fact] - public void ParseYAML_ShouldThrowExceptionWhenFrontmatterIsInvalid() - { - _ = Assert.Throws(() => parser.Parse("invalidFrontmatter")); - } - - [Fact] - public void ParseYAML_ShouldSplitTheMetadata() - { - // Act - var (metadata, rawContent) = parser.SplitFrontMatter(pageContent); - - // Assert - Assert.Equal(pageFrontmaterCONST.TrimEnd(), metadata); - Assert.Equal(pageMarkdownCONST, rawContent); - } - - [Fact] - public void ParseSiteSettings_ShouldReturnSiteSettings() - { - // Arrange - var siteSettings = parser.Parse(siteContentCONST); - - // Assert - Assert.NotNull(siteSettings); - Assert.Equal("My Site", siteSettings.Title); - Assert.Equal("https://www.example.com/", siteSettings.BaseURL); - } - - - [Fact] - public void SiteParams_ShouldHandleEmptyContent() - { - Assert.Empty(site.Params); - } - - [Fact] - public void SiteParams_ShouldPopulateParamsWithExtraFields() - { - // Arrange - var siteSettings = parser.Parse(siteContentCONST); - site = new Site(generateOptionsMock, siteSettings, frontMatterParser, loggerMock, systemClockMock); - - // Assert - Assert.NotEmpty(siteSettings.Params); - Assert.DoesNotContain("customParam", site.Params); - Assert.Contains("ParamsCustomParam", site.Params); - Assert.Equal("Custom Value", site.Params["ParamsCustomParam"]); - Assert.Equal(new[] { "Test", "Real Data" }, ((Dictionary)siteSettings.Params["ParamsNestedData"])["Level2"]); - Assert.Equal("Test", ((siteSettings.Params["ParamsNestedData"] as Dictionary)?["Level2"] as List)?[0]); - } -} diff --git a/test/TestSetup.cs b/test/TestSetup.cs deleted file mode 100644 index c43fa1dfc1535e57d7c4500c18c6e485b70b3126..0000000000000000000000000000000000000000 --- a/test/TestSetup.cs +++ /dev/null @@ -1,54 +0,0 @@ -using NSubstitute; -using Serilog; -using SuCoS.Models; -using SuCoS.Models.CommandLineOptions; -using SuCoS.Parser; -using System.Globalization; - -namespace Tests; - -public class TestSetup -{ - protected const string titleCONST = "Test Title"; - protected const string sourcePathCONST = "/path/to/file.md"; - protected readonly DateTime todayDate = DateTime.Parse("2023-04-01", CultureInfo.InvariantCulture); - protected readonly DateTime futureDate = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); - - protected const string testSitePathCONST01 = ".TestSites/01"; - protected const string testSitePathCONST02 = ".TestSites/02-have-index"; - protected const string testSitePathCONST03 = ".TestSites/03-section"; - protected const string testSitePathCONST04 = ".TestSites/04-tags"; - protected const string testSitePathCONST05 = ".TestSites/05-theme-no-baseof"; - protected const string testSitePathCONST06 = ".TestSites/06-theme"; - protected const string testSitePathCONST07 = ".TestSites/07-theme-no-baseof-error"; - protected const string testSitePathCONST08 = ".TestSites/08-theme-html"; - - protected readonly IMetadataParser frontMatterParser = new SuCoS.Parser.YAMLParser(); - protected readonly IGenerateOptions generateOptionsMock = Substitute.For(); - protected readonly SiteSettings siteSettingsMock = Substitute.For(); - protected readonly ILogger loggerMock = Substitute.For(); - protected readonly ISystemClock systemClockMock = Substitute.For(); - protected readonly IFrontMatter frontMatterMock = new FrontMatter() - { - Title = titleCONST, - SourceRelativePath = sourcePathCONST - }; - - protected ISite site; - - // based on the compiled test.dll path - // that is typically "bin/Debug/netX.0/test.dll" - protected const string testSitesPath = "../../.."; - - public TestSetup() - { - _ = systemClockMock.Now.Returns(todayDate); - site = new Site(generateOptionsMock, siteSettingsMock, frontMatterParser, loggerMock, systemClockMock); - } - - public TestSetup(SiteSettings siteSettings) - { - _ = systemClockMock.Now.Returns(todayDate); - site = new Site(generateOptionsMock, siteSettings, frontMatterParser, loggerMock, systemClockMock); - } -}