From e1ef957330ea32cebb4ab10a30c133fc60b478fb Mon Sep 17 00:00:00 2001 From: Dave Glick Date: Wed, 13 Oct 2021 20:33:47 -0400 Subject: [PATCH] Makes LinkGenerator an instance class and exposes it via an interface through IExecutionState --- RELEASE.md | 4 + .../Documents/IDocumentGetLinkExtensions.cs | 4 +- .../Execution/EmptyExecutionContext.cs | 4 +- .../Execution/IExecutionState.cs | 7 +- .../IExecutionStateGetLinkExtensions.cs | 36 ++-- .../Settings/IReadOnlySettingsExtensions.cs | 4 +- src/core/Statiq.Common/Util/ILinkGenerator.cs | 68 +++++++ src/core/Statiq.Common/Util/LinkGenerator.cs | 169 ++++++++++++------ src/core/Statiq.Core/Execution/Engine.cs | 4 + .../Statiq.Core/Execution/ExecutionContext.cs | 5 +- .../Statiq.Testing/Execution/TestEngine.cs | 5 +- .../Execution/TestExecutionContext.cs | 9 +- .../Statiq.Markdown/MarkdownHelper.cs | 3 +- .../Statiq.Markdown/MarkdownShortcode.cs | 2 +- .../Statiq.Markdown/RenderMarkdown.cs | 2 +- .../Util/LinkGeneratorFixture.cs | 86 ++++++--- .../IHtmlHelperExtensionsFixture.cs | 1 + 17 files changed, 299 insertions(+), 114 deletions(-) create mode 100644 src/core/Statiq.Common/Util/ILinkGenerator.cs diff --git a/RELEASE.md b/RELEASE.md index 4ed5766b0..456d5382c 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,7 +1,11 @@ # 1.0.0-beta.49 +- **Breaking change:** The `LinkGenerator` class is no longer static and now needs to be accessed through a new `IExecutionState.LinkGenerator` or `IExecutionContext.LinkGenerator` property. - Added the `cache` directory to the excluded list in `Statiq.App.props`. - Fixed a bug with `DocumentFileProvider` and documents with a null `Destination`. +- Fixed a bug in the Razor engine when run under .NET 6 RC runtimes (#204, thanks @phil-scott-78). +- Updated several dependencies (#199, #201, #202, thanks @devlead). +- Added the ability to cache Razor partials using new `CachedPartial()` and `CachedPartialAsync()` HTML helpers. (#205) # 1.0.0-beta.48 diff --git a/src/core/Statiq.Common/Documents/IDocumentGetLinkExtensions.cs b/src/core/Statiq.Common/Documents/IDocumentGetLinkExtensions.cs index db23e8da8..46065a5b4 100644 --- a/src/core/Statiq.Common/Documents/IDocumentGetLinkExtensions.cs +++ b/src/core/Statiq.Common/Documents/IDocumentGetLinkExtensions.cs @@ -45,6 +45,6 @@ public static string GetLink(this IDocument document, bool includeHost = false) /// A string representation of the path suitable for a web link. /// public static string GetLink(this IDocument document, string queryAndFragment, bool includeHost = false) => - IExecutionContext.Current.GetLink(LinkGenerator.AddQueryAndFragment(document.Destination, queryAndFragment), includeHost); + IExecutionContext.Current.GetLink(IExecutionContext.Current.LinkGenerator.AddQueryAndFragment(document.Destination, queryAndFragment), includeHost); } -} +} \ No newline at end of file diff --git a/src/core/Statiq.Common/Execution/EmptyExecutionContext.cs b/src/core/Statiq.Common/Execution/EmptyExecutionContext.cs index 95311e53d..7b5eda3a3 100644 --- a/src/core/Statiq.Common/Execution/EmptyExecutionContext.cs +++ b/src/core/Statiq.Common/Execution/EmptyExecutionContext.cs @@ -66,6 +66,8 @@ public EmptyExecutionContext(IExecutionState executionState) public IReadOnlyPipelineCollection ExecutingPipelines => ExecutionState.ExecutingPipelines; + public ILinkGenerator LinkGenerator => ExecutionState.LinkGenerator; + public IDocument CreateDocument(NormalizedPath source, NormalizedPath destination, IEnumerable> items, IContentProvider contentProvider = null) => ExecutionState.CreateDocument(source, destination, items, contentProvider); @@ -104,4 +106,4 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public IDisposable BeginScope(TState state) => Logger.BeginScope(state); } -} +} \ No newline at end of file diff --git a/src/core/Statiq.Common/Execution/IExecutionState.cs b/src/core/Statiq.Common/Execution/IExecutionState.cs index 86b107b78..f694a0c08 100644 --- a/src/core/Statiq.Common/Execution/IExecutionState.cs +++ b/src/core/Statiq.Common/Execution/IExecutionState.cs @@ -131,6 +131,11 @@ internal set /// IReadOnlyPipelineCollection ExecutingPipelines { get; } + /// + /// Helps generate normalized links. + /// + ILinkGenerator LinkGenerator { get; } + /// /// Gets a that can be used for document content. If /// is not null, the stream is initialized with the specified content. It is preferred to use @@ -194,4 +199,4 @@ IJavaScriptEnginePool GetJavaScriptEnginePool( int maxUsagesPerEngine = 100, TimeSpan? engineTimeout = null); } -} +} \ No newline at end of file diff --git a/src/core/Statiq.Common/Execution/IExecutionStateGetLinkExtensions.cs b/src/core/Statiq.Common/Execution/IExecutionStateGetLinkExtensions.cs index 3a29115bf..fb80c1b4b 100644 --- a/src/core/Statiq.Common/Execution/IExecutionStateGetLinkExtensions.cs +++ b/src/core/Statiq.Common/Execution/IExecutionStateGetLinkExtensions.cs @@ -97,7 +97,9 @@ public static string GetLink( { // Return the actual URI if it's absolute string path = metadata.GetString(key); - return path is null ? null : executionState.GetLink(LinkGenerator.AddQueryAndFragment(path, queryAndFragment), includeHost); + return path is null + ? null + : executionState.GetLink(executionState.LinkGenerator.AddQueryAndFragment(path, queryAndFragment), includeHost); } return null; } @@ -122,7 +124,7 @@ public static string GetLink( bool includeHost = false) { // Return the actual URI if it's absolute - if (path is object && LinkGenerator.TryGetAbsoluteHttpUri(path, out string absoluteUri)) + if (path is object && executionState.LinkGenerator.TryGetAbsoluteHttpUri(path, out string absoluteUri)) { return absoluteUri; } @@ -163,7 +165,7 @@ public static string GetLink( bool hideExtensions) { // Return the actual URI if it's absolute - if (path is object && LinkGenerator.TryGetAbsoluteHttpUri(path, out string absoluteUri)) + if (path is object && executionState.LinkGenerator.TryGetAbsoluteHttpUri(path, out string absoluteUri)) { return absoluteUri; } @@ -178,10 +180,6 @@ public static string GetLink( hideExtensions); } - // This is a frequently used hot path so cache the results - private static readonly ConcurrentCache<(NormalizedPath, bool), string> _links = - new ConcurrentCache<(NormalizedPath, bool), string>(true); - /// /// Converts the specified path into a string appropriate for use as a link using default settings from the /// configuration. This version should be used inside modules to ensure @@ -200,18 +198,14 @@ public static string GetLink( this IExecutionState executionState, in NormalizedPath path, bool includeHost = false) => - _links.GetOrAdd( - (path, includeHost), - (key, es) => - es.GetLink( - key.Item1, - key.Item2 ? es.Settings.GetString(Keys.Host) : null, - es.Settings.GetPath(Keys.LinkRoot), - es.Settings.GetBool(Keys.LinksUseHttps), - es.Settings.GetBool(Keys.LinkHideIndexPages), - es.Settings.GetBool(Keys.LinkHideExtensions), - es.Settings.GetBool(Keys.LinkLowercase)), - executionState); + executionState.GetLink( + path, + includeHost ? executionState.Settings.GetString(Keys.Host) : null, + executionState.Settings.GetPath(Keys.LinkRoot), + executionState.Settings.GetBool(Keys.LinksUseHttps), + executionState.Settings.GetBool(Keys.LinkHideIndexPages), + executionState.Settings.GetBool(Keys.LinkHideExtensions), + executionState.Settings.GetBool(Keys.LinkLowercase)); /// /// Converts the path into a string appropriate for use as a link, overriding one or more @@ -270,7 +264,7 @@ public static string GetLink( bool hideIndexPages, bool hideExtensions, bool lowercase) => - LinkGenerator.GetLink( + executionState.LinkGenerator.GetLink( path, host, root, @@ -312,7 +306,7 @@ public static string GetLink( bool hideExtensions, bool lowercase, bool makeAbsolute) => - LinkGenerator.GetLink( + executionState.LinkGenerator.GetLink( path, host, root, diff --git a/src/core/Statiq.Common/Settings/IReadOnlySettingsExtensions.cs b/src/core/Statiq.Common/Settings/IReadOnlySettingsExtensions.cs index 8c43ee346..e4861248d 100644 --- a/src/core/Statiq.Common/Settings/IReadOnlySettingsExtensions.cs +++ b/src/core/Statiq.Common/Settings/IReadOnlySettingsExtensions.cs @@ -28,7 +28,7 @@ public static string GetIndexFileName(this IReadOnlySettings settings) /// /// The settings. /// The page file extensions. - public static IReadOnlyList GetPageFileExtensions(this IReadOnlySettings settings) + public static string[] GetPageFileExtensions(this IReadOnlySettings settings) { settings.ThrowIfNull(nameof(settings)); IReadOnlyList pageFileExtensions = settings.GetList(Keys.PageFileExtensions); @@ -37,4 +37,4 @@ public static IReadOnlyList GetPageFileExtensions(this IReadOnlySettings : pageFileExtensions.Select(x => x.StartsWith('.') ? x : "." + x).ToArray(); } } -} +} \ No newline at end of file diff --git a/src/core/Statiq.Common/Util/ILinkGenerator.cs b/src/core/Statiq.Common/Util/ILinkGenerator.cs new file mode 100644 index 000000000..11816dbfc --- /dev/null +++ b/src/core/Statiq.Common/Util/ILinkGenerator.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; + +namespace Statiq.Common +{ + /// + /// Helps generate normalized links. + /// + public interface ILinkGenerator + { + /// + /// Generates a normalized link given a path and other conditions. + /// + /// The path to get a link for. + /// The host for the link (or null to omit the host). + /// The root path for the link (or null for no root path). + /// The scheme for the link (or null for "http"). + /// An array of file names to hide (or null to not hide any files). + /// An array of file extensions to hide (or null to not hide extensions or an empty array to hide all file extensions). + /// Indicates that the link should be rendered in all lowercase. + /// + /// If is relative, setting this to true (the default value) will assume the path relative from the root of the site + /// and make it absolute by prepending a slash and to the path. Otherwise, false will leave relative paths as relative + /// and won't prepend a slash (but , , and will have no effect). + /// If is absolute, this value has no effect and , , and + /// will be applied as appropriate. + /// + /// A generated link. + string GetLink( + NormalizedPath path, + string host, + in NormalizedPath root, + string scheme, + string[] hidePages, + string[] hideExtensions, + bool lowercase, + bool makeAbsolute = true); + + /// + /// Checks if a string contains an absolute URI with a "http" or "https" scheme and returns it if it does. + /// + /// The string to check. + /// The resulting absolute URI. + /// true if the string contains an absolute URI, false otherwise. + bool TryGetAbsoluteHttpUri(string str, out string absoluteUri); + + /// + /// Adds a query and/or fragment to a URL or path. + /// + /// The path or URL. + /// + /// The query and/or fragment to add. If a value is provided for this parameter + /// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed. + /// + /// The path or URL with an appended query and/or fragment. + string AddQueryAndFragment(string path, string queryAndFragment); + + /// + /// Adds a query and/or fragment to a path. + /// + /// The path. + /// + /// The query and/or fragment to add. If a value is provided for this parameter + /// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed. + /// + /// The path with an appended query and/or fragment. + NormalizedPath AddQueryAndFragment(NormalizedPath path, string queryAndFragment); + } +} \ No newline at end of file diff --git a/src/core/Statiq.Common/Util/LinkGenerator.cs b/src/core/Statiq.Common/Util/LinkGenerator.cs index 3b92cb037..0fa86f626 100644 --- a/src/core/Statiq.Common/Util/LinkGenerator.cs +++ b/src/core/Statiq.Common/Util/LinkGenerator.cs @@ -8,35 +8,117 @@ namespace Statiq.Common /// /// Helps generate normalized links. /// - public static class LinkGenerator + public class LinkGenerator : ILinkGenerator { - /// - /// Generates a normalized link given a path and other conditions. - /// - /// The path to get a link for. - /// The host for the link (or null to omit the host). - /// The root path for the link (or null for no root path). - /// The scheme for the link (or null for "http"). - /// An array of file names to hide (or null to not hide any files). - /// An array of file extensions to hide (or null to not hide extensions or an empty array to hide all file extensions). - /// Indicates that the link should be rendered in all lowercase. - /// - /// If is relative, setting this to true (the default value) will assume the path relative from the root of the site - /// and make it absolute by prepending a slash and to the path. Otherwise, false will leave relative paths as relative - /// and won't prepend a slash (but , , and will have no effect). - /// If is absolute, this value has no effect and , , and - /// will be applied as appropriate. - /// - /// A generated link. - public static string GetLink( + // Cache link generator results + private static readonly ConcurrentCache _links = + new ConcurrentCache(false); + + private class GetLinkCacheKey : IEquatable + { + public NormalizedPath Path { get; set; } + + public string Host { get; set; } + + public NormalizedPath Root { get; set; } + + public string Scheme { get; set; } + + public string[] HidePages { get; set; } + + public string[] HideExtensions { get; set; } + + public bool Lowercase { get; set; } + + public bool MakeAbsolute { get; set; } + + public bool Equals(GetLinkCacheKey other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return Path.Equals(other.Path) + && Host == other.Host + && Root.Equals(other.Root) + && Scheme == other.Scheme + && Equals(HidePages, other.HidePages) + && Equals(HideExtensions, other.HideExtensions) + && Lowercase == other.Lowercase + && MakeAbsolute == other.MakeAbsolute; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((GetLinkCacheKey)obj); + } + + public override int GetHashCode() => + HashCode.Combine(Path, Host, Root, Scheme, HidePages, HideExtensions, Lowercase, MakeAbsolute); + } + + /// + public string GetLink( + NormalizedPath path, + string host, + in NormalizedPath root, + string scheme, + string[] hidePages, + string[] hideExtensions, + bool lowercase, + bool makeAbsolute = true) => + _links.GetOrAdd( + new GetLinkCacheKey + { + Path = path, + Host = host, + Root = root, + Scheme = scheme, + HidePages = hidePages, + HideExtensions = hideExtensions, + Lowercase = lowercase, + MakeAbsolute = makeAbsolute + }, + key => GetLinkImplementation( + key.Path, + key.Host, + key.Root, + key.Scheme, + key.HidePages, + key.HideExtensions, + key.Lowercase, + key.MakeAbsolute)); + + private static string GetLinkImplementation( NormalizedPath path, string host, in NormalizedPath root, string scheme, - IReadOnlyList hidePages, - IReadOnlyList hideExtensions, + string[] hidePages, + string[] hideExtensions, bool lowercase, - bool makeAbsolute = true) + bool makeAbsolute) { string link = string.Empty; if (!path.IsNull) @@ -57,7 +139,11 @@ public static string GetLink( // Hide extensions if (hideExtensions is object - && (hideExtensions.Count == 0 || hideExtensions.Where(x => x is object).Select(x => x.StartsWith(NormalizedPath.Dot) ? x : NormalizedPath.Dot + x).Contains(path.Extension))) + && (hideExtensions.Length == 0 + || hideExtensions + .Where(x => x is object) + .Select(x => x.StartsWith(NormalizedPath.Dot) ? x : NormalizedPath.Dot + x) + .Contains(path.Extension))) { path = path.ChangeExtension(null); } @@ -148,13 +234,8 @@ public static string GetLink( return lowercase ? renderedLink.ToLowerInvariant() : renderedLink; } - /// - /// Checks if a string contains an absolute URI with a "http" or "https" scheme and returns it if it does. - /// - /// The string to check. - /// The resulting absolute URI. - /// true if the string contains an absolute URI, false otherwise. - public static bool TryGetAbsoluteHttpUri(string str, out string absoluteUri) + /// + public bool TryGetAbsoluteHttpUri(string str, out string absoluteUri) { if (!string.IsNullOrWhiteSpace(str)) { @@ -171,16 +252,8 @@ public static bool TryGetAbsoluteHttpUri(string str, out string absoluteUri) return false; } - /// - /// Adds a query and/or fragment to a URL or path. - /// - /// The path or URL. - /// - /// The query and/or fragment to add. If a value is provided for this parameter - /// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed. - /// - /// The path or URL with an appended query and/or fragment. - public static string AddQueryAndFragment(string path, string queryAndFragment) + /// + public string AddQueryAndFragment(string path, string queryAndFragment) { if (!string.IsNullOrEmpty(queryAndFragment)) { @@ -194,16 +267,8 @@ public static string AddQueryAndFragment(string path, string queryAndFragment) return path; } - /// - /// Adds a query and/or fragment to a path. - /// - /// The path. - /// - /// The query and/or fragment to add. If a value is provided for this parameter - /// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed. - /// - /// The path with an appended query and/or fragment. - public static NormalizedPath AddQueryAndFragment(NormalizedPath path, string queryAndFragment) + /// + public NormalizedPath AddQueryAndFragment(NormalizedPath path, string queryAndFragment) { if (!string.IsNullOrEmpty(queryAndFragment)) { @@ -217,4 +282,4 @@ public static NormalizedPath AddQueryAndFragment(NormalizedPath path, string que return path; } } -} +} \ No newline at end of file diff --git a/src/core/Statiq.Core/Execution/Engine.cs b/src/core/Statiq.Core/Execution/Engine.cs index abbe2b89f..20851287a 100644 --- a/src/core/Statiq.Core/Execution/Engine.cs +++ b/src/core/Statiq.Core/Execution/Engine.cs @@ -154,6 +154,7 @@ private IServiceScope GetServiceScope(IServiceCollection serviceCollection, ICon serviceCollection.TryAddSingleton(Namespaces); serviceCollection.TryAddSingleton(ScriptHelper); serviceCollection.TryAddSingleton(ClassCatalog); + serviceCollection.TryAddSingleton(LinkGenerator); // Add the failure logger LogLevel failureLogLevel = Settings.Get(Keys.FailureLogLevel, LogLevel.Error); @@ -252,6 +253,9 @@ private void RunInitializers() /// public IPipelineOutputs Outputs { get; private set; } + /// + public ILinkGenerator LinkGenerator { get; } = new LinkGenerator(); + /// public IAnalyzerCollection Analyzers => AnalyzerCollection; diff --git a/src/core/Statiq.Core/Execution/ExecutionContext.cs b/src/core/Statiq.Core/Execution/ExecutionContext.cs index 2922edeed..1f7b0800d 100644 --- a/src/core/Statiq.Core/Execution/ExecutionContext.cs +++ b/src/core/Statiq.Core/Execution/ExecutionContext.cs @@ -123,6 +123,9 @@ private static string GetLogPrefix(IExecutionContext parent, IModule module, Pip /// public IReadOnlyPipelineCollection ExecutingPipelines => _contextData.Engine.ExecutingPipelines; + /// + public ILinkGenerator LinkGenerator => _contextData.Engine.LinkGenerator; + /// public CancellationToken CancellationToken => _contextData.Engine.CancellationToken; @@ -197,4 +200,4 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public IDisposable BeginScope(TState state) => Logger.BeginScope(state); } -} +} \ No newline at end of file diff --git a/src/core/Statiq.Testing/Execution/TestEngine.cs b/src/core/Statiq.Testing/Execution/TestEngine.cs index 2f83345c7..60db3eb41 100644 --- a/src/core/Statiq.Testing/Execution/TestEngine.cs +++ b/src/core/Statiq.Testing/Execution/TestEngine.cs @@ -138,6 +138,9 @@ public TestEngine() /// public IFileCleaner FileCleaner { get; set; } = new TestFileCleaner(); + /// + public ILinkGenerator LinkGenerator { get; set; } = new LinkGenerator(); + /// public FilteredDocumentList OutputPages => new FilteredDocumentList( @@ -225,4 +228,4 @@ public TDocument CreateDocument( where TDocument : FactoryDocument, IDocument, new() => _documentFactory.CreateDocument(source, destination, items, contentProvider); } -} +} \ No newline at end of file diff --git a/src/core/Statiq.Testing/Execution/TestExecutionContext.cs b/src/core/Statiq.Testing/Execution/TestExecutionContext.cs index 5534348b5..63868a0ed 100644 --- a/src/core/Statiq.Testing/Execution/TestExecutionContext.cs +++ b/src/core/Statiq.Testing/Execution/TestExecutionContext.cs @@ -97,6 +97,13 @@ public TestNamespacesCollection Namespaces /// INamespacesCollection IExecutionState.Namespaces => Namespaces; + /// + public ILinkGenerator LinkGenerator + { + get => Engine.LinkGenerator; + set => Engine.LinkGenerator = value; + } + /// public TestEventCollection Events { @@ -346,4 +353,4 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public IDisposable BeginScope(TState state) => _logger.BeginScope(state); } -} +} \ No newline at end of file diff --git a/src/extensions/Statiq.Markdown/MarkdownHelper.cs b/src/extensions/Statiq.Markdown/MarkdownHelper.cs index 041421799..a7abb7677 100644 --- a/src/extensions/Statiq.Markdown/MarkdownHelper.cs +++ b/src/extensions/Statiq.Markdown/MarkdownHelper.cs @@ -18,6 +18,7 @@ public static class MarkdownHelper public const string DefaultConfiguration = "common"; public static MarkdownDocument RenderMarkdown( + IExecutionState executionState, IDocument document, string content, TextWriter writer, @@ -53,7 +54,7 @@ public static MarkdownDocument RenderMarkdown( if (!RelativeUrl.IsRelative(link)) { - return LinkGenerator.TryGetAbsoluteHttpUri(link, out string absoluteUri) ? absoluteUri : link; + return executionState.LinkGenerator.TryGetAbsoluteHttpUri(link, out string absoluteUri) ? absoluteUri : link; } // TODO: Remove when RenderMarkdown.PrependLinkRoot goes away. diff --git a/src/extensions/Statiq.Markdown/MarkdownShortcode.cs b/src/extensions/Statiq.Markdown/MarkdownShortcode.cs index ee44da15a..d87d23da5 100644 --- a/src/extensions/Statiq.Markdown/MarkdownShortcode.cs +++ b/src/extensions/Statiq.Markdown/MarkdownShortcode.cs @@ -37,7 +37,7 @@ public override ShortcodeResult Execute(KeyValuePair[] args, str IMetadataDictionary dictionary = args.ToDictionary(Configuration, PrependLinkRoot); using (StringWriter writer = new StringWriter()) { - MarkdownHelper.RenderMarkdown(document, content, writer, dictionary.GetBool(PrependLinkRoot), dictionary.GetString(Configuration, MarkdownHelper.DefaultConfiguration), null); + MarkdownHelper.RenderMarkdown(context, document, content, writer, dictionary.GetBool(PrependLinkRoot), dictionary.GetString(Configuration, MarkdownHelper.DefaultConfiguration), null); return writer.ToString(); } } diff --git a/src/extensions/Statiq.Markdown/RenderMarkdown.cs b/src/extensions/Statiq.Markdown/RenderMarkdown.cs index c59625bad..d45aee9db 100644 --- a/src/extensions/Statiq.Markdown/RenderMarkdown.cs +++ b/src/extensions/Statiq.Markdown/RenderMarkdown.cs @@ -208,7 +208,7 @@ protected override async Task> ExecuteInputAsync(IDocumen MarkdownDocument markdownDocument; using (StringWriter writer = new StringWriter()) { - markdownDocument = MarkdownHelper.RenderMarkdown(input, content, writer, _prependLinkRoot, _configuration, _extensions); + markdownDocument = MarkdownHelper.RenderMarkdown(context, input, content, writer, _prependLinkRoot, _configuration, _extensions); if (markdownDocument is null) { return input.Yield(); diff --git a/tests/core/Statiq.Common.Tests/Util/LinkGeneratorFixture.cs b/tests/core/Statiq.Common.Tests/Util/LinkGeneratorFixture.cs index e01cf1391..f96e318e5 100644 --- a/tests/core/Statiq.Common.Tests/Util/LinkGeneratorFixture.cs +++ b/tests/core/Statiq.Common.Tests/Util/LinkGeneratorFixture.cs @@ -39,9 +39,10 @@ public void ShouldReturnLinkForFilePath(string path, bool makeAbsolute, string e { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -88,9 +89,10 @@ public void ShouldJoinHostAndRootForFilePath(string host, string root, string pa { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -115,9 +117,10 @@ public void ShouldHideIndexPagesForFilePath(string path, bool makeAbsolute, stri { // Given NormalizedPath filePath = new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, null, null, null, new[] { "index.html" }, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, null, null, null, new[] { "index.html" }, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -158,9 +161,10 @@ public void ShouldHideExtensionsForFilePath(string path, bool makeAbsolute, stri { // Given NormalizedPath filePath = new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, null, null, null, null, Array.Empty(), false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, null, null, null, null, Array.Empty(), false, makeAbsolute); // Then link.ShouldBe(expected); @@ -185,9 +189,10 @@ public void ShouldHideSpecificExtensionsForFilePath(string path, bool makeAbsolu { // Given NormalizedPath filePath = new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, null, null, null, null, new[] { "html", ".htm" }, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, null, null, null, null, new[] { "html", ".htm" }, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -212,9 +217,10 @@ public void ShouldJoinHostAndRootForDirectoryPath(string host, string root, stri { // Given NormalizedPath directoryPath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(directoryPath, host, root is null ? null : new NormalizedPath(root), null, null, null, false); + string link = linkGenerator.GetLink(directoryPath, host, root is null ? null : new NormalizedPath(root), null, null, null, false); // Then link.ShouldBe(expected); @@ -225,9 +231,10 @@ public void ShouldUseSpecifiedScheme() { // Given NormalizedPath directoryPath = new NormalizedPath("/foo/bar"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(directoryPath, "www.google.com", null, "https", null, null, false); + string link = linkGenerator.GetLink(directoryPath, "www.google.com", null, "https", null, null, false); // Then link.ShouldBe("https://www.google.com/foo/bar"); @@ -238,9 +245,10 @@ public void SupportsSingleSlash() { // Given NormalizedPath path = new NormalizedPath("/"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(path, null, null, null, null, null, false); + string link = linkGenerator.GetLink(path, null, null, null, null, null, false); // Then link.ShouldBe("/"); @@ -251,9 +259,10 @@ public void SupportsSingleSlashWithRoot() { // Given NormalizedPath path = new NormalizedPath("/"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(path, null, "root", null, null, null, false); + string link = linkGenerator.GetLink(path, null, "root", null, null, null, false); // Then link.ShouldBe("/root/"); @@ -264,9 +273,10 @@ public void SupportsSingleSlashWithHidePages() { // Given NormalizedPath path = new NormalizedPath("/"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(path, null, null, null, new[] { "index" }, null, false); + string link = linkGenerator.GetLink(path, null, null, null, new[] { "index" }, null, false); // Then link.ShouldBe("/"); @@ -277,9 +287,10 @@ public void SupportsSingleSlashWithHideExtensions() { // Given NormalizedPath path = new NormalizedPath("/"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(path, null, null, null, null, new[] { "html" }, false); + string link = linkGenerator.GetLink(path, null, null, null, null, new[] { "html" }, false); // Then link.ShouldBe("/"); @@ -290,9 +301,10 @@ public void ShouldGenerateMixedCaseLinks() { // Given NormalizedPath directoryPath = new NormalizedPath("/Foo/Bar"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(directoryPath, "www.google.com", null, "http", null, null, false); + string link = linkGenerator.GetLink(directoryPath, "www.google.com", null, "http", null, null, false); // Then link.ShouldBe("http://www.google.com/Foo/Bar"); @@ -303,9 +315,10 @@ public void ShouldGenerateLowercaseLinks() { // Given NormalizedPath directoryPath = new NormalizedPath("/Foo/Bar"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(directoryPath, "www.google.com", null, "http", null, null, true); + string link = linkGenerator.GetLink(directoryPath, "www.google.com", null, "http", null, null, true); // Then link.ShouldBe("http://www.google.com/foo/bar"); @@ -316,9 +329,10 @@ public void EscapesSpecialCharacters() { // Given NormalizedPath directoryPath = new NormalizedPath("/a/b/c%d"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(directoryPath, "www.google.com", null, "http", null, null, false, true); + string link = linkGenerator.GetLink(directoryPath, "www.google.com", null, "http", null, null, false, true); // Then link.ShouldBe("http://www.google.com/a/b/c%25d"); @@ -329,9 +343,10 @@ public void EscapesSpecialCharactersForRealtivePath() { // Given NormalizedPath directoryPath = new NormalizedPath("a/b/c%d"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(directoryPath, null, null, null, null, null, false, false); + string link = linkGenerator.GetLink(directoryPath, null, null, null, null, null, false, false); // Then link.ShouldBe("a/b/c%25d"); @@ -342,9 +357,10 @@ public void ConvertsSlashes() { // Given NormalizedPath directoryPath = new NormalizedPath("/a/b/c\\d"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(directoryPath, "www.google.com", null, "http", null, null, true); + string link = linkGenerator.GetLink(directoryPath, "www.google.com", null, "http", null, null, true); // Then link.ShouldBe("http://www.google.com/a/b/c/d"); @@ -377,9 +393,10 @@ public void FragmentForFilePath(string path, bool makeAbsolute, string expected) { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -412,9 +429,10 @@ public void EmptyFragment(string path, bool makeAbsolute, string expected) { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -461,9 +479,10 @@ public void FragmentForFilePathWithHostAndRoot(string host, string root, string { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -510,9 +529,10 @@ public void EmptyFragmentForFilePathWithHostAndRoot(string host, string root, st { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -545,9 +565,10 @@ public void QueryForFilePath(string path, bool makeAbsolute, string expected) { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -594,9 +615,10 @@ public void QueryForFilePathWithHostAndRoot(string host, string root, string pat { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -629,9 +651,10 @@ public void QueryAndFragmentForFilePath(string path, bool makeAbsolute, string e { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -664,9 +687,10 @@ public void QueryAndEmptyFragment(string path, bool makeAbsolute, string expecte { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, null, null, null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -713,9 +737,10 @@ public void QueryAndFragmentForFilePathWithHostAndRoot(string host, string root, { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -762,9 +787,10 @@ public void QueryAndEmptyFragmentForFilePathWithHostAndRoot(string host, string { // Given NormalizedPath filePath = path is null ? null : new NormalizedPath(path); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); + string link = linkGenerator.GetLink(filePath, host, root is null ? null : new NormalizedPath(root), null, null, null, false, makeAbsolute); // Then link.ShouldBe(expected); @@ -775,9 +801,10 @@ public void EscapesFragmentIdentifierInRootPath() { // Given NormalizedPath filePath = new NormalizedPath("/a/b/c?foo=bar#buzz"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, "www.google.com", new NormalizedPath("x#y/z/"), null, null, null, false, true); + string link = linkGenerator.GetLink(filePath, "www.google.com", new NormalizedPath("x#y/z/"), null, null, null, false, true); // Then link.ShouldBe("http://www.google.com/x%23y/z/a/b/c?foo=bar#buzz"); @@ -788,13 +815,14 @@ public void EscapesQueryIdentifierInRootPath() { // Given NormalizedPath filePath = new NormalizedPath("/a/b/c?foo=bar#buzz"); + LinkGenerator linkGenerator = new LinkGenerator(); // When - string link = LinkGenerator.GetLink(filePath, "www.google.com", new NormalizedPath("x?y/z/"), null, null, null, false, true); + string link = linkGenerator.GetLink(filePath, "www.google.com", new NormalizedPath("x?y/z/"), null, null, null, false, true); // Then link.ShouldBe("http://www.google.com/x%3Fy/z/a/b/c?foo=bar#buzz"); } } } -} +} \ No newline at end of file diff --git a/tests/extensions/Statiq.Razor.Tests/IHtmlHelperExtensionsFixture.cs b/tests/extensions/Statiq.Razor.Tests/IHtmlHelperExtensionsFixture.cs index badde2b4f..c44cf8b69 100644 --- a/tests/extensions/Statiq.Razor.Tests/IHtmlHelperExtensionsFixture.cs +++ b/tests/extensions/Statiq.Razor.Tests/IHtmlHelperExtensionsFixture.cs @@ -17,6 +17,7 @@ namespace Statiq.Razor.Tests { [TestFixture] + [NonParallelizable] public class IHtmlHelperExtensionsFixture : BaseFixture { public class DocumentLinkTests : IHtmlHelperExtensionsFixture