public static IEnumerable <object[]> FlexiIncludeBlockOptions_CanBePopulated_Data() { const string dummySource = "dummySource"; const FlexiIncludeType dummyType = FlexiIncludeType.Markdown; const bool dummyCache = false; const string dummyCacheDirectory = "dummyCacheDirectory"; var dummyClipping1 = new Clipping(10, 15); var dummyClippings1 = new List <Clipping> { dummyClipping1 }; var dummyClipping2 = new Clipping(2, 21); var dummyClippings2 = new List <Clipping> { dummyClipping2 }; return(new object[][] { // Populating FlexiIncludeBlockOptions containing default values new object[] { new SerializableWrapper <FlexiIncludeBlockOptions>(new FlexiIncludeBlockOptions()), new SerializableWrapper <FlexiIncludeBlockOptions>(new FlexiIncludeBlockOptions(dummySource, dummyClippings1, dummyType, dummyCache, dummyCacheDirectory)), $@"{{ ""{nameof(FlexiIncludeBlockOptions.Source)}"": ""{dummySource}"", ""{nameof(FlexiIncludeBlockOptions.Type)}"": ""{dummyType}"", ""{nameof(FlexiIncludeBlockOptions.Cache)}"": ""{dummyCache}"", ""{nameof(FlexiIncludeBlockOptions.CacheDirectory)}"": ""{dummyCacheDirectory}"", ""{nameof(FlexiIncludeBlockOptions.Clippings)}"": [ {{ ""{nameof(Clipping.StartLine)}"": ""{dummyClipping1.StartLine}"", ""{nameof(Clipping.EndLine)}"": ""{dummyClipping1.EndLine}"" }} ] }}" }, // Populating FlexiIncludeBlockOptions with an existing clippings collection (should be replaced instead of appended to) new object[] { new SerializableWrapper <FlexiIncludeBlockOptions>(new FlexiIncludeBlockOptions(clippings: dummyClippings1)), new SerializableWrapper <FlexiIncludeBlockOptions>(new FlexiIncludeBlockOptions(clippings: dummyClippings2)), $@"{{ ""{nameof(FlexiIncludeBlockOptions.Clippings)}"": [ {{ ""{nameof(Clipping.StartLine)}"": ""{dummyClipping2.StartLine}"", ""{nameof(Clipping.EndLine)}"": ""{dummyClipping2.EndLine}"" }} ] }}" } }); }
internal virtual void ValidateType(FlexiIncludeType type) { if (!Enum.IsDefined(typeof(FlexiIncludeType), type)) { throw new OptionsException(nameof(IFlexiIncludeBlockOptions.Type), string.Format(Strings.OptionsException_Shared_ValueMustBeAValidEnumValue, type, nameof(FlexiIncludeType))); } }
/// <summary> /// Creates a <see cref="FlexiIncludeBlockOptions"/>. /// </summary> /// <param name="source"> /// <para>The <see cref="FlexiIncludeBlock"/>'s source.</para> /// <para>This value must either be a relative URI or an absolute URI with scheme file, http or https.</para> /// <para>If this value is a relative URI and the <see cref="FlexiIncludeBlock"/> is in root content, <see cref="IFlexiIncludeBlocksExtensionOptions.BaseUri"/> /// is used as the base URI.</para> /// <para>If this value is a relative URI and the <see cref="FlexiIncludeBlock"/> is in non-root content, the absolute URI of its containing source is used as the base URI.</para> /// <para>For example, consider standard Markdig usage: <c>string html = Markdown.ToHtml(rootContent, yourMarkdownPipeline);</c>.</para> /// <para>To Markdig, root content has no associated source, it is just a string containing markup. /// To work around this limitation, if the root content contains a <see cref="FlexiIncludeBlock"/> with a relative URI source like "../my/path/file1.md", <see cref="FlexiIncludeBlocksExtension"/> /// uses <see cref="IFlexiIncludeBlocksExtensionOptions.BaseUri"/> to resolve the absolute URI of "file1.md".</para> /// <para>As such, <see cref="IFlexiIncludeBlocksExtensionOptions.BaseUri"/> is typically configured as the absolute URI of the root source.</para> /// <para>If "file1.md" contains a FlexiIncludeBlock with source "../my/path/file2.md", we use the previously resolved absolute URI of "file1.md" as the base URI to /// resolve the absolute URI of "file2.md".</para> /// <para>Note that retrieving content from remote sources can introduce security issues. As far as possible, retrieve remote content only from trusted or permanent links. For example, /// from <a href="https://help.github.com/articles/getting-permanent-links-to-files/">Github permalink</a>s. Additionally, consider sanitizing generated HTML.</para> /// <para>Defaults to <see cref="string.Empty"/>.</para> /// </param> /// <param name="clippings"> /// <para>The <see cref="Clipping"/>s specifying content from the source to include.</para> /// <para>If this value is <c>null</c> or empty, all content from the source is included.</para> /// <para>Defaults to <c>null</c>.</para> /// </param> /// <param name="type"> /// <para>The <see cref="FlexiIncludeBlock"/>'s type.</para> /// <para>If this value is <see cref="FlexiIncludeType.Code"/>, the included content is rendered in a code block. /// If this value is <see cref="FlexiIncludeType.Markdown"/>, the included content is processed as markdown.</para> /// <para>Defaults to <see cref="FlexiIncludeType.Code"/>.</para> /// </param> /// <param name="cache"> /// <para>The value specifying whether to cache the <see cref="FlexiIncludeBlock"/>'s content on disk.</para> /// <para>If this value is true and the <see cref="FlexiIncludeBlock"/>'s source is remote, the source's content is cached on disk.</para> /// <para>Caching-on-disk slows down the first markdown-to-HTML run on a system, but significantly speeds up subsequent runs:</para> /// <para>If on-disk caching is enabled and content from remote source "x" is included, on the first run, all content in "x" is retrieved from a server and /// cached in memory as well as on disk.</para> /// <para>Subsequent requests to retrieve content from "x" during the same run will retrieve content from the in-memory cache.</para> /// <para>At the end of the first run, the in-memory cache is discarded when the process dies.</para> /// <para>For subsequent runs on the system, if content from "x" is included again, all content from "x" is retrieved from the on-disk cache, avoiding /// round trips to a server.</para> /// <para>If you are only going to execute one run on a system (e.g when doing CI/CD), the run will take less time if on-disk caching is disabled. /// If you are doing multiple runs on a system, on-disk caching should be enabled.</para> /// <para>Defaults to <c>true</c>.</para> /// </param> /// <param name="cacheDirectory"> /// <para>The directory to cache the <see cref="FlexiIncludeBlock"/>'s content in.</para> /// <para>This option is only relevant if caching on disk is enabled.</para> /// <para>If this value is <c>null</c>, whitespace or an empty string, a folder named "ContentCache" in the application's working directory is used instead. /// Note that the application's working directory is what <see cref="Directory.GetCurrentDirectory"/> returns.</para> /// <para>Defaults to <c>null</c>.</para> /// </param> public FlexiIncludeBlockOptions(string source = "", IList <Clipping> clippings = default, FlexiIncludeType type = FlexiIncludeType.Code, bool cache = true, string cacheDirectory = default) { Source = source; Clippings = clippings == null ? null : clippings is ReadOnlyCollection <Clipping> clippingsAsReadOnlyCollection ? clippingsAsReadOnlyCollection : new ReadOnlyCollection <Clipping>(clippings); Type = type; Cache = cache; CacheDirectory = cacheDirectory; }
/// <summary> /// Creates a <see cref="FlexiIncludeBlock"/>. /// </summary> /// <param name="source">The <see cref="FlexiIncludeBlock"/>'s source.</param> /// <param name="clippings">The <see cref="Clipping"/>s specifying content from the source to include.</param> /// <param name="type">The <see cref="FlexiIncludeBlock"/>'s type.</param> /// <param name="cacheDirectory">The directory to cache the <see cref="FlexiIncludeBlock"/>'s content in.</param> /// <param name="parentFlexiIncludeBlock">The <see cref="FlexiIncludeBlock"/>'s parent <see cref="FlexiIncludeBlock"/>.</param> /// <param name="containingSource">The URI of the source that contains the <see cref="FlexiIncludeBlock"/>.</param> /// <param name="blockParser">The <see cref="BlockParser"/> parsing the <see cref="FlexiIncludeBlock"/>.</param> public FlexiIncludeBlock(Uri source, ReadOnlyCollection <Clipping> clippings, FlexiIncludeType type, string cacheDirectory, FlexiIncludeBlock parentFlexiIncludeBlock, string containingSource, BlockParser blockParser) : base(blockParser) { Source = source; Clippings = clippings; Type = type; CacheDirectory = cacheDirectory; ParentFlexiIncludeBlock = parentFlexiIncludeBlock; ContainingSource = containingSource; Children = new List <FlexiIncludeBlock>(); }
/// <summary> /// Creates a <see cref="FlexiIncludeBlock"/>. /// </summary> /// <param name="proxyJsonBlock">The <see cref="ProxyJsonBlock"/> containing data for the <see cref="FlexiIncludeBlock"/>.</param> /// <param name="blockProcessor">The <see cref="BlockProcessor"/> processing the <see cref="FlexiIncludeBlock"/>.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="proxyJsonBlock"/> is <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="blockProcessor"/> is <c>null</c>.</exception> /// <exception cref="OptionsException">Thrown if an option is invalid.</exception> /// <exception cref="InvalidOperationException">Thrown if a <see cref="FlexiIncludeBlock"/> cycle is found.</exception> /// <exception cref="BlockException">Thrown if an exception is thrown while processing the <see cref="FlexiIncludeBlock"/>'s included content.</exception> public FlexiIncludeBlock Create(ProxyJsonBlock proxyJsonBlock, BlockProcessor blockProcessor) { (IFlexiIncludeBlockOptions flexiIncludeBlockOptions, IFlexiIncludeBlocksExtensionOptions flexiIncludeBlocksExtensionOptions) = _optionsService. CreateOptions(blockProcessor, proxyJsonBlock); // Source string source = flexiIncludeBlockOptions.Source; ValidateSource(source); // Type FlexiIncludeType type = flexiIncludeBlockOptions.Type; ValidateType(type); // Cache directory string cacheDirectory = ResolveAndValidateCacheDirectory(flexiIncludeBlockOptions.Cache, flexiIncludeBlockOptions.CacheDirectory); // Parent FlexiIncludeBlock parent = ResolveParent(blockProcessor); // Containing source string containingSource = ResolveContainingSource(parent); // Source absolute URI Uri sourceAbsoluteUri = ResolveSourceAbsoluteUri(source, flexiIncludeBlocksExtensionOptions.BaseUri, parent); // Create block var flexiIncludeBlock = new FlexiIncludeBlock(sourceAbsoluteUri, flexiIncludeBlockOptions.Clippings, type, cacheDirectory, parent, containingSource, proxyJsonBlock.Parser) { Column = proxyJsonBlock.Column, Line = proxyJsonBlock.Line, Span = proxyJsonBlock.Span }; parent?.Children.Add(flexiIncludeBlock); ProcessFlexiIncludeBlock(flexiIncludeBlock, proxyJsonBlock, blockProcessor); return(null); }