Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/source/syntax/_snippets/unused-snippet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This snippet is not included anywhere

```{warning}
This snippet is not included anywhere
```
4 changes: 2 additions & 2 deletions docs/source/syntax/file_inclusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ If there are portions of content that are relevant to multiple pages, you can in
### Syntax

```markdown
:::{include} _snippets/my_snippet.md
:::{include} _snippets/reusable-snippet.md
:::
```

:::{include} _snippets/my_snippet.md
:::{include} _snippets/reusable-snippet.md
:::

### Asciidoc syntax
Expand Down
3 changes: 3 additions & 0 deletions src/Elastic.Markdown/IO/DocumentationFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ public record StaticFile(IFileInfo SourceFile, IDirectoryInfo RootPath)

public record ExcludedFile(IFileInfo SourceFile, IDirectoryInfo RootPath)
: DocumentationFile(SourceFile, RootPath);

public record SnippetFile(IFileInfo SourceFile, IDirectoryInfo RootPath)
: DocumentationFile(SourceFile, RootPath);
5 changes: 5 additions & 0 deletions src/Elastic.Markdown/IO/DocumentationSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ private DocumentationFile CreateMarkDownFile(IFileInfo file, BuildContext contex
if (Configuration.Globs.Any(g => g.IsMatch(relativePath)))
return new MarkdownFile(file, SourcePath, MarkdownParser, context);

// we ignore files in folders that start with an underscore
if (relativePath.IndexOf("_snippets", StringComparison.Ordinal) >= 0)
return new SnippetFile(file, SourcePath);

// we ignore files in folders that start with an underscore
if (relativePath.IndexOf("/_", StringComparison.Ordinal) > 0 || relativePath.StartsWith("_"))
return new ExcludedFile(file, SourcePath);

Expand Down
19 changes: 19 additions & 0 deletions src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,24 @@ private void ExtractInclusionPath(ParserContext context)
Found = true;
else
this.EmitError($"`{IncludePath}` does not exist.");

// literal includes may point to locations other than `_snippets` since they do not
// participate in emitting links
if (Literal)
return;

var file = FileSystem.FileInfo.New(IncludePath);

if (file.Directory != null && file.Directory.FullName.IndexOf("_snippets", StringComparison.Ordinal) < 0)
{
this.EmitError($"{{include}} only supports including snippets from `_snippet` folders. `{IncludePath}` is not a snippet");
Found = false;
}

if (file.FullName == context.Path.FullName)
{
this.EmitError($"{{include}} cyclical include detected `{IncludePath}` points to itself");
Found = false;
}
}
}
20 changes: 14 additions & 6 deletions tests/Elastic.Markdown.Tests/DocSet/LinkReferenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,23 @@

namespace Elastic.Markdown.Tests.DocSet;

public class LinkReferenceTests(ITestOutputHelper output) : NavigationTestsBase(output)
public class LinkReferenceTests : NavigationTestsBase
{
public LinkReferenceTests(ITestOutputHelper output) : base(output) => Reference = LinkReference.Create(Set);

private LinkReference Reference { get; }

[Fact]
public void Create()
{
var reference = LinkReference.Create(Set);
public void ShouldNotBeNull() =>
Reference.Should().NotBeNull();

reference.Should().NotBeNull();
}
[Fact]
public void EmitsLinks() =>
Reference.Links.Should().NotBeNullOrEmpty();

[Fact]
public void ShouldNotIncludeSnippets() =>
Reference.Links.Should().NotContain(l => l.Contains("_snippets/"));
}

public class GitCheckoutInformationTests(ITestOutputHelper output) : NavigationTestsBase(output)
Expand Down
67 changes: 67 additions & 0 deletions tests/Elastic.Markdown.Tests/FileInclusion/IncludeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,70 @@ public void EmitsError()
.OnlyContain(d => d.Message.Contains("include requires an argument."));
}
}

public class IncludeNeedsToLiveInSpecialFolder(ITestOutputHelper output) : DirectiveTest<IncludeBlock>(output,
"""
```{include} test.md
```
"""
)
{

protected override void AddToFileSystem(MockFileSystem fileSystem)
{
// language=markdown
var inclusion = "*Hello world*";
fileSystem.AddFile(@"docs/source/test.md", inclusion);
}

[Fact]
public void ParsesBlock() => Block.Should().NotBeNull();

[Fact]
public void IncludesNothing() => Html.Should().Be("");

[Fact]
public void EmitsError()
{
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1);
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error);
Collector.Diagnostics.Should()
.OnlyContain(d => d.Message.Contains("only supports including snippets from `_snippet` folders."));
}
}


public class CanNotIncludeItself(ITestOutputHelper output) : DirectiveTest<IncludeBlock>(output,
"""
```{include} _snippets/test.md
```
"""
)
{

protected override void AddToFileSystem(MockFileSystem fileSystem)
{
// language=markdown
var inclusion =
"""
:::{include} test.md
:::
""";
fileSystem.AddFile(@"docs/source/_snippets/test.md", inclusion);
}

[Fact]
public void ParsesBlock() => Block.Should().NotBeNull();

[Fact]
public void IncludesNothing() => Html.Should().Be("");

[Fact]
public void EmitsError()
{
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1);
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error);
Collector.Diagnostics.Should()
.OnlyContain(d => d.Message.Contains("cyclical include detected"));
}
}
Loading