Add cycle detection to tree rendering

This commit is contained in:
Matt Constable 2021-01-14 17:37:22 +00:00 committed by GitHub
parent dee3c01629
commit 994540d97f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 2 deletions

View File

@ -111,6 +111,7 @@ Spectre.Consoleでできることを見るために、
│ Panels │ examples/Panels/Panels.csproj │ Demonstrates how to render items in panels. │
│ Rules │ examples/Rules/Rules.csproj │ Demonstrates how to render horizontal rules (lines). │
│ Tables │ examples/Tables/Tables.csproj │ Demonstrates how to render tables in a console. │
│ Trees │ examples/Trees/Trees.csproj │ Demonstrates how to render trees in a console. │
╰────────────┴───────────────────────────────────────┴──────────────────────────────────────────────────────╯
```

View File

@ -1,5 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Spectre.Console.Testing;
using Spectre.Verify.Extensions;
using VerifyXunit;
@ -56,5 +57,29 @@ namespace Spectre.Console.Tests.Unit
// Then
return Verifier.Verify(console.Output);
}
[Fact]
public void Should_Throw_If_Tree_Contains_Cycles()
{
// Given
var console = new FakeConsole(width: 80);
var child2 = new TreeNode(new Text("child 2"));
var child3 = new TreeNode(new Text("child 3"));
var child1 = new TreeNode(new Text("child 1"));
child1.AddNodes(child2, child3);
var root = new TreeNode(new Text("Branch Node"));
root.AddNodes(child1);
child2.AddNode(root);
var tree = new Tree("root node");
tree.AddNodes(root);
// When
var result = Record.Exception(() => console.Render(tree));
// Then
result.ShouldBeOfType<CircularTreeException>();
}
}
}
}

View File

@ -0,0 +1,15 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Indicates that the tree being rendered includes a cycle, and cannot be rendered.
/// </summary>
public sealed class CircularTreeException : Exception
{
internal CircularTreeException(string message)
: base(message)
{
}
}
}

View File

@ -6,7 +6,8 @@ using Spectre.Console.Rendering;
namespace Spectre.Console
{
/// <summary>
/// A renderable tree.
/// Representation of non-circular tree data.
/// Each node added to the tree may only be present in it a single time, in order to facilitate cycle detection.
/// </summary>
public sealed class Tree : Renderable, IHasTreeNodes
{
@ -54,6 +55,7 @@ namespace Spectre.Console
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
var result = new List<Segment>();
var visitedNodes = new HashSet<TreeNode>();
var stack = new Stack<Queue<TreeNode>>();
stack.Push(new Queue<TreeNode>(new[] { _root }));
@ -77,6 +79,10 @@ namespace Spectre.Console
var isLastChild = stackNode.Count == 1;
var current = stackNode.Dequeue();
if (!visitedNodes.Add(current))
{
throw new CircularTreeException("Cycle detected in tree - unable to render.");
}
stack.Push(stackNode);