Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add output filtering and split & combined output strategies #98

Closed
wants to merge 5 commits into from
Closed
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
59 changes: 59 additions & 0 deletions src/dotnet-affected/AffectedSummaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Affected.Cli;
using DotnetAffected.Abstractions;
using System.Collections.Generic;
using System.Linq;

namespace DotnetAffected.Core
{
internal static class AffectedSummaryExtensions
{
/// <summary>
/// Converts the <see cref="AffectedSummary"/> to a list of IProjectInfo, including all affected, changed and excluded projects.
/// </summary>
public static IEnumerable<IProjectInfo> GetAllProjects(this AffectedSummary summary)
{
var affectedProjects = GetAffectedProjects(summary);
var changedProjects = GetChangedProjects(summary);
var excludedProjects = GetExcludedProjects(summary);

var allProjects = changedProjects
.Concat(affectedProjects)
.Concat(excludedProjects);

return allProjects;
}

/// <summary>
/// Converts the <see cref="AffectedSummary"/> to a list of IProjectInfo, including all affected projects.
/// </summary>
public static IEnumerable<IProjectInfo> GetAffectedProjects(this AffectedSummary summary)
{
var affectedProjects = summary.AffectedProjects
.Select(p => new ProjectInfo(p, ProjectStatus.Affected));

return affectedProjects;
}

/// <summary>
/// Converts the <see cref="AffectedSummary"/> to a list of IProjectInfo, including all changed projects.
/// </summary>
public static IEnumerable<IProjectInfo> GetChangedProjects(this AffectedSummary summary)
{
var changedProjects = summary.ProjectsWithChangedFiles
.Select(p => new ProjectInfo(p, ProjectStatus.Changed));

return changedProjects;
}

/// <summary>
/// Converts the <see cref="AffectedSummary"/> to a list of IProjectInfo, including all excluded projects.
/// </summary>
public static IEnumerable<IProjectInfo> GetExcludedProjects(this AffectedSummary summary)
{
var excludedProjects = summary.ExcludedProjects
.Select(p => new ProjectInfo(p, ProjectStatus.Excluded));

return excludedProjects;
}
}
}
87 changes: 52 additions & 35 deletions src/dotnet-affected/Commands/AffectedRootCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ internal class AffectedRootCommand : RootCommand
public static readonly DryRunOption DryRunOption = new();
public static readonly OutputDirOption OutputDirOption = new();
public static readonly OutputNameOption OutputNameOption = new();
public static readonly OutputStrategyOption OutputStrategyOption = new();
public static readonly OutputFilterOption OutputFilterOption = new();

public AffectedRootCommand()
: base("Determines which projects are affected by a set of changes.\n" +
Expand All @@ -33,65 +35,86 @@ public AffectedRootCommand()
this.AddOption(DryRunOption);
this.AddOption(OutputDirOption);
this.AddOption(OutputNameOption);
this.AddOption(OutputStrategyOption);
this.AddOption(OutputFilterOption);

this.SetHandler(async ctx =>
{
var (options, summary) = ctx.ExecuteAffectedExecutor();
summary.ThrowIfNoChanges();

var verbose = ctx.ParseResult.GetValueForOption(AffectedGlobalOptions.VerboseOption)!;
var verbose = ctx.ParseResult.GetValueForOption(AffectedGlobalOptions.VerboseOption);
var console = ctx.Console;

if (verbose)
{
var infoView = new AffectedInfoView(summary);
console.Append(infoView);
}

var allProjects = summary
.ProjectsWithChangedFiles
.Concat(summary.AffectedProjects)
.Select(p => new ProjectInfo(p));

// Generate output using formatters
var outputOptions = ctx.GetAffectedCommandOutputOptions(options);
var filter = new OutputFilter(outputOptions);
var projects = filter.GetFilteredProjects(summary);
var outputFactory = new OutputStrategyFactory(outputOptions);
var outputStrategy = outputFactory.CreateOutputStrategy(projects);

var formatterExecutor = new OutputFormatterExecutor(console);
await formatterExecutor.Execute(
allProjects,
outputOptions.Formatters,
outputOptions.OutputDir,
outputOptions.OutputName,
outputOptions.DryRun,
verbose);
foreach (IOutput output in outputStrategy.GetOutputs())
{
// Generate output using formatters
var formatterExecutor = new OutputFormatterExecutor(console);
await formatterExecutor.Execute(
output.Projects,
outputOptions.Formatters,
output.Directory,
output.Name,
outputOptions.DryRun,
verbose);
}
});
}
}

internal sealed class FormatOption : Option<string[]>
{
public FormatOption()
: base(new[]
{
"--format", "-f"
})
: base(new[] { "--format", "-f" })
{
this.Description = "Space-seperated output file formats. Possible values: <traversal, text, json>.";

this.SetDefaultValue(new[]
{
"traversal"
});
this.SetDefaultValue(new[] { "traversal" });
this.AllowMultipleArgumentsPerToken = true;
}
}

internal sealed class OutputStrategyOption : Option<string>
{
public OutputStrategyOption()
: base(new[] { "--output-strategy" })
{
this.Description =
"Determines the output strategy. If set to \"combined\", all output will be written to a single file. If set to \"split\", files will be created based on the defined output filters.";
this.SetDefaultValue(OutputStrategies.Combined);
this.FromAmong(OutputStrategies.All.ToArray());
this.AddCompletions(OutputStrategies.All.ToArray());
}
}

internal sealed class OutputFilterOption : Option<string[]>
{
public OutputFilterOption()
: base(new[] { "--output-filter" })
{
this.Description = "Defines what files should be output; affected, changed, or excluded projects.";
this.SetDefaultValue(new[] { OutputFilters.Affected, OutputFilters.Changed });
this.FromAmong(OutputFilters.All.ToArray());
this.AddCompletions(OutputFilters.All.ToArray());
}
}

internal sealed class DryRunOption : Option<bool>
{
public DryRunOption()
: base(new[]
{
"--dry-run"
})
: base(new[] { "--dry-run" })
{
this.Description = "Only output to stdout. No output files will be created.";
this.SetDefaultValue(false);
Expand All @@ -101,10 +124,7 @@ public DryRunOption()
internal sealed class OutputDirOption : Option<string>
{
public OutputDirOption()
: base(new[]
{
"--output-dir"
})
: base(new[] { "--output-dir" })
{
this.Description = "The directory where the output file(s) will be generated.\n" +
"Relative paths will be based on --repository-path.";
Expand All @@ -114,10 +134,7 @@ public OutputDirOption()
internal sealed class OutputNameOption : Option<string>
{
public OutputNameOption()
: base(new[]
{
"--output-name"
})
: base(new[] { "--output-name" })
{
this.Description = "The filename to create.\n" +
"Format file extensions will be appended.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ public AffectedCommandOutputOptions(
string? outputDir,
string outputName,
string[] formatters,
string[] outputFilters,
string outputStrategy,
bool dryRun)
{
OutputDir = DetermineOutputDir(repositoryPath, outputDir);
OutputName = outputName;
Formatters = formatters;
OutputFilters = outputFilters;
OutputStrategy = outputStrategy;
DryRun = dryRun;
}

public string[] OutputFilters { get; }
public string OutputDir { get; }
public string OutputName { get; }
public string[] Formatters { get; }
public string OutputStrategy { get; }
public bool DryRun { get; }

private static string DetermineOutputDir(string repositoryPath, string? outDir)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ protected override AffectedCommandOutputOptions GetBoundValue(BindingContext bin
result.GetValueForOption(AffectedRootCommand.OutputDirOption)!,
result.GetValueForOption(AffectedRootCommand.OutputNameOption)!,
result.GetValueForOption(AffectedRootCommand.FormatOption)!,
result.GetValueForOption(AffectedRootCommand.OutputFilterOption)!,
result.GetValueForOption(AffectedRootCommand.OutputStrategyOption)!,
result.GetValueForOption(AffectedRootCommand.DryRunOption)
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/dotnet-affected/Domain/IProjectInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ public interface IProjectInfo
/// Gets the full path to the project's file.
/// </summary>
string FilePath { get; }

/// <summary>
/// Describes whether the project is affected, changed, excluded, or unaffected.
/// </summary>
ProjectStatus Status { get; }
}
}
6 changes: 5 additions & 1 deletion src/dotnet-affected/Domain/ProjectInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ public ProjectInfo(string name, string filePath)
this.FilePath = filePath;
}

public ProjectInfo(ProjectGraphNode node)
public ProjectInfo(ProjectGraphNode node, ProjectStatus status)
{
this.Name = node.GetProjectName();
this.FilePath = node.ProjectInstance.FullPath;
this.Status = status;
}

/// <summary>
Expand All @@ -27,5 +28,8 @@ public ProjectInfo(ProjectGraphNode node)
/// Gets the full path to the project's file.
/// </summary>
public string FilePath { get; }

// <inheritdoc />
public ProjectStatus Status { get; }
}
}
21 changes: 21 additions & 0 deletions src/dotnet-affected/Domain/ProjectStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Affected.Cli
{
/// <summary>
/// Describes whether the project is affected, changed, or excluded.
/// </summary>
public enum ProjectStatus
{
/// <summary>
/// Indicates that the project is indirectly affected by changed dependencies.
/// </summary>
Affected = 0,
/// <summary>
/// Indicates that the project has changed files.
/// </summary>
Changed = 1,
/// <summary>
/// Indicates that the project is excluded from the potentially affected or changed projects.
/// </summary>
Excluded = 2,
}
}
43 changes: 43 additions & 0 deletions src/dotnet-affected/Infrastructure/OutputFilters/OutputFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Affected.Cli.Commands;
using DotnetAffected.Abstractions;
using DotnetAffected.Core;
using System.Collections.Generic;
using System.Linq;

namespace Affected.Cli
{
internal class OutputFilter
{
private readonly AffectedCommandOutputOptions _options;

public OutputFilter(AffectedCommandOutputOptions options)
{
_options = options;
}

public IList<IProjectInfo> GetFilteredProjects(AffectedSummary summary)
{
List<IProjectInfo> projectsToInclude = new List<IProjectInfo>();

HashSet<ProjectStatus> appliedFilters =
new HashSet<ProjectStatus>(_options.OutputFilters.Select(f => OutputFilters.StatusMap[f]));

if (appliedFilters.Contains(ProjectStatus.Affected))
{
projectsToInclude.AddRange(summary.GetAffectedProjects());
}

if (appliedFilters.Contains(ProjectStatus.Changed))
{
projectsToInclude.AddRange(summary.GetChangedProjects());
}

if (appliedFilters.Contains(ProjectStatus.Excluded))
{
projectsToInclude.AddRange(summary.GetExcludedProjects());
}

return projectsToInclude;
}
}
}
19 changes: 19 additions & 0 deletions src/dotnet-affected/Infrastructure/OutputFilters/OutputFilters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.Generic;

namespace Affected.Cli
{
internal static class OutputFilters
{
public const string Affected = "affected";
public const string Changed = "changed";
public const string Excluded = "excluded";

public static readonly IReadOnlyDictionary<string, ProjectStatus> StatusMap = new Dictionary<string, ProjectStatus>
{
{ Affected, ProjectStatus.Affected },
{ Changed, ProjectStatus.Changed },
{ Excluded, ProjectStatus.Excluded }
};
public static readonly IReadOnlyList<string> All = new[] { Affected, Changed, Excluded };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public async Task Execute(IEnumerable<IProjectInfo> projects,

var outputFileName = outputName + formatter.NewFileExtension;
var outputPath = Path.Combine(outputDirectory, outputFileName);

if (dryRun)
{
_console.Out.WriteLine($"DRY-RUN: WRITE {outputPath}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Linq;

namespace Affected.Cli
{
internal class CombinedOutputStrategy : IOutputStrategy
{
public string Name { get; }
public string Directory { get; }
public IEnumerable<IProjectInfo> Projects { get; }

public CombinedOutputStrategy(string name, string directory, IEnumerable<IProjectInfo> projects)
{
Name = name;
Directory = directory;
Projects = projects;
}

public IEnumerable<IOutput> GetOutputs()
{
return new IOutput[] { new Output(Name, Directory, Projects) };
}
}
}
Loading
Loading