Skip to content
Open
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
97 changes: 52 additions & 45 deletions src/Cake.Cli/Hosts/TreeScriptHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@
using Cake.Core;
using Cake.Core.Graph;
using Cake.Core.Scripting;
using ThreadingTask = System.Threading.Tasks.Task;

namespace Cake.Cli
{
namespace Cake.Cli {
/// <summary>
/// The script host used for showing task descriptions.
/// </summary>
public sealed class TreeScriptHost : ScriptHost
{
public sealed class TreeScriptHost : ScriptHost {
private const int _maxDepth = 0;
private const string _cross = "├─";
private const string _corner = "└─";
Expand All @@ -31,102 +30,110 @@ public sealed class TreeScriptHost : ScriptHost
/// <param name="console">The console.</param>
public TreeScriptHost(ICakeEngine engine, ICakeContext context, IConsole console)
: base(engine, context)
{
{
_console = console ?? throw new ArgumentNullException(nameof(console));
}

/// <inheritdoc/>
public override Task<CakeReport> RunTargetAsync(string target)
{
public override Task<CakeReport> RunTargetAsync(string target) {
PrintTaskTree();

return System.Threading.Tasks.Task.FromResult<CakeReport>(null);
return ThreadingTask.FromResult<CakeReport>(null);
}

/// <inheritdoc/>
public override Task<CakeReport> RunTargetsAsync(IEnumerable<string> targets)
{
public override Task<CakeReport> RunTargetsAsync(IEnumerable<string> targets) {
PrintTaskTree();

return System.Threading.Tasks.Task.FromResult<CakeReport>(null);
return ThreadingTask.FromResult<CakeReport>(null);
}

private void PrintTaskTree()
{
var topLevelTasks = GetTopLevelTasks();
// Build the full graph once (includes Dependencies + Dependees)
var graph = CakeGraphBuilder.Build(Tasks);
var topLevelTasks = GetTopLevelTasks(graph);

_console.WriteLine();

foreach (ICakeTaskInfo task in topLevelTasks)
foreach (var task in topLevelTasks)
{
PrintTask(task, string.Empty, false, 0);
// root tasks start at depth 0, no branch characters yet
PrintTask(task, graph, string.Empty, isLast: false, depth: 0);
_console.WriteLine();
}
}

private List<ICakeTaskInfo> GetTopLevelTasks()
private List<ICakeTaskInfo> GetTopLevelTasks(CakeGraph graph)
{
// Display "Default" first, then alphabetical
var graph = CakeGraphBuilder.Build(Tasks);
return Tasks.Where(task => !graph.Edges.Any(
edge => edge.Start.Equals(task.Name, StringComparison.OrdinalIgnoreCase)))
// Top-level = tasks that never appear as Start (no outgoing edges)
return Tasks
.Where(task => !graph.Edges.Any(
edge => edge.Start.Equals(task.Name, StringComparison.OrdinalIgnoreCase)))
.OrderByDescending(task => task.Name.Equals("Default", StringComparison.OrdinalIgnoreCase))
.ThenBy(task => task.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
}

private void PrintTask(ICakeTaskInfo task, string indent, bool isLast, int depth)
{
private void PrintTask(
ICakeTaskInfo task,
CakeGraph graph,
string indent,
bool isLast,
int depth)
{
// Builds ASCII graph
_console.Write(indent);
if (isLast)
{
if (isLast) {
_console.Write(_corner);
indent += " ";
}
else if (depth > 0)
{
} else if (depth > 0) {
_console.Write(_cross);
indent += _vertical;
}

PrintName(task, depth);

if ((_maxDepth > 0) && (depth >= _maxDepth))
{
if ((_maxDepth > 0) && (depth >= _maxDepth)) {
return;
}

for (var i = 0; i < task.Dependencies.Count; i++)
{
// First() is safe as CakeGraphBuilder has already validated graph is valid
var childTask = Tasks
.Where(x => x.Name.Equals(task.Dependencies[i].Name, StringComparison.OrdinalIgnoreCase))
.First();
// Children = all tasks that have an edge Start -> End = current task.
// This respects both IsDependentOn and IsDependeeOf,
// because CakeGraphBuilder already encoded both into the graph.
var childTasks = graph.Edges
.Where(edge => edge.End.Equals(task.Name, StringComparison.OrdinalIgnoreCase))
.Select(edge => Tasks.First(t =>
t.Name.Equals(edge.Start, StringComparison.OrdinalIgnoreCase)))
.Distinct()
.OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase)
.ToList();

for (var i = 0; i < childTasks.Count; i++)
{
var childTask = childTasks[i];
var childIsLast = i == childTasks.Count - 1;

PrintTask(childTask, indent, i == (task.Dependencies.Count - 1), depth + 1);
PrintTask(childTask, graph, indent, childIsLast, depth + 1);
}
}

private void PrintName(ICakeTaskInfo task, int depth)
{
private void PrintName(ICakeTaskInfo task, int depth) {
var originalColor = _console.ForegroundColor;

if (depth == 0)
{
{
_console.ForegroundColor = ConsoleColor.Cyan;
}
else if (task is CakeTask cakeTask &&
(cakeTask.Actions.Any() || cakeTask.DelayedActions.Any()))
{
(cakeTask.Actions.Any() || cakeTask.DelayedActions.Any()))
{
_console.ForegroundColor = ConsoleColor.Green;
}
else
{
} else {
_console.ForegroundColor = ConsoleColor.Gray;
}

_console.WriteLine(task.Name);
_console.ForegroundColor = originalColor;
}
}
}
}