Skip to content

Commit

Permalink
Add arguments to verifiy years behind are under set limits
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveDesmond-ca committed Oct 4, 2022
1 parent 3265d6e commit b5db64a
Show file tree
Hide file tree
Showing 26 changed files with 403 additions and 117 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ A simple measure of dependency freshness
`-q`, `--quiet`: only show outdated packages

`-u`, `--update`: update project files after displaying packages

#### Limits:

`-l`, `--limit`: exits with error code if total libyears behind is greater than this value

`-p`, `--limit-project`: exits with error code if any project is more libyears behind than this value

`-a`, `--limit-any`: exits with error code if any dependency is more libyears behind than this value
2 changes: 1 addition & 1 deletion src/LibYear.Core/FileTypes/IProjectFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ public interface IProjectFile
{
string FileName { get; }
IDictionary<string, PackageVersion?> Packages { get; }
string Update(IEnumerable<Result> results);
string Update(IReadOnlyCollection<Result> results);
}
11 changes: 5 additions & 6 deletions src/LibYear.Core/FileTypes/XmlProjectFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected XmlProjectFile(string filename, string contents, string elementName, s
);
}

public string Update(IEnumerable<Result> results)
public string Update(IReadOnlyCollection<Result> results)
{
foreach (var result in results.Where(r => r.Latest != null))
{
Expand Down Expand Up @@ -68,12 +68,11 @@ private void UpdateElement(XElement element, string latestVersion)
e.Value = latestVersion;
}

private IEnumerable<XElement> GetMatchingElements(Result result)
{
return _xmlContents.Descendants(_elementName)
private IReadOnlyCollection<XElement> GetMatchingElements(Result result)
=> _xmlContents.Descendants(_elementName)
.Where(d => _packageAttributeNames.Any(attributeName => (d.Attribute(attributeName)?.Value ?? d.Element(attributeName)?.Value) == result.Name
&& (d.Attribute(_versionAttributeName)?.Value ?? d.Element(_versionAttributeName)?.Value) == result.Installed?.Version.ToString()
)
);
}
)
.ToArray();
}
10 changes: 10 additions & 0 deletions src/LibYear.Core/HasAgeMeasurements.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace LibYear.Core;

public abstract class HasAgeMeasurements
{
public double YearsBehind => DaysBehind > 0
? DaysBehind / 365
: 0;

public abstract double DaysBehind { get; }
}
2 changes: 1 addition & 1 deletion src/LibYear.Core/IPackageVersionChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace LibYear.Core;

public interface IPackageVersionChecker
{
Task<IDictionary<IProjectFile, IEnumerable<Result>>> GetPackages(IEnumerable<IProjectFile> projectFiles);
Task<SolutionResult> GetPackages(IReadOnlyCollection<IProjectFile> projectFiles);
}
4 changes: 2 additions & 2 deletions src/LibYear.Core/IProjectFileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace LibYear.Core;

public interface IProjectFileManager
{
Task<IList<IProjectFile>> GetAllProjects(IReadOnlyList<string> paths);
Task<IEnumerable<string>> Update(IDictionary<IProjectFile, IEnumerable<Result>> allResults);
Task<IReadOnlyCollection<IProjectFile>> GetAllProjects(IReadOnlyCollection<string> paths);
Task<IReadOnlyCollection<string>> Update(SolutionResult result);
}
5 changes: 2 additions & 3 deletions src/LibYear.Core/LibYear.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>6.0.1</Version>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Version>7.0.0</Version>
<TargetFramework>net6.0</TargetFramework>
<Product>libyear</Product>
<PackageId>LibYear.Core</PackageId>
<Authors>ecoAPM LLC</Authors>
Expand Down
3 changes: 1 addition & 2 deletions src/LibYear.Core/PackageVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ public override string ToString()

public override string ToString(string format, IFormatProvider? formatProvider)
{
if (formatProvider == null
|| !TryFormatter(format, formatProvider, out var formattedString))
if (formatProvider == null || !TryFormatter(format, formatProvider, out var formattedString))
{
formattedString = ToString();
}
Expand Down
23 changes: 12 additions & 11 deletions src/LibYear.Core/PackageVersionChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,31 @@ namespace LibYear.Core;
public class PackageVersionChecker : IPackageVersionChecker
{
private readonly PackageMetadataResource _metadataResource;
private readonly IDictionary<string, IList<Release>> _versionCache;
private readonly IDictionary<string, IReadOnlyCollection<Release>> _versionCache;

public PackageVersionChecker(PackageMetadataResource metadataResource)
: this(metadataResource, new ConcurrentDictionary<string, IList<Release>>())
: this(metadataResource, new ConcurrentDictionary<string, IReadOnlyCollection<Release>>())
{
}

public PackageVersionChecker(PackageMetadataResource metadataResource, IDictionary<string, IList<Release>> versionCache)
public PackageVersionChecker(PackageMetadataResource metadataResource, IDictionary<string, IReadOnlyCollection<Release>> versionCache)
{
_metadataResource = metadataResource;
_versionCache = versionCache;
}

public async Task<IDictionary<IProjectFile, IEnumerable<Result>>> GetPackages(IEnumerable<IProjectFile> projectFiles)
public async Task<SolutionResult> GetPackages(IReadOnlyCollection<IProjectFile> projectFiles)
{
var tasks = projectFiles.ToDictionary(proj => proj, GetResults);
await Task.WhenAll(tasks.Values);
return tasks.ToDictionary(p => p.Key, p => p.Value.GetAwaiter().GetResult());
var results = await Task.WhenAll(tasks.Values);
return new SolutionResult(results);
}

private async Task<IEnumerable<Result>> GetResults(IProjectFile proj)
private async Task<ProjectResult> GetResults(IProjectFile proj)
{
var results = proj.Packages.Select(p => GetResult(p.Key, p.Value));
return await Task.WhenAll(results);
var tasks = proj.Packages.Select(p => GetResult(p.Key, p.Value));
var results = await Task.WhenAll(tasks);
return new ProjectResult(proj, results);
}

public async Task<Result> GetResult(string packageName, PackageVersion? installed)
Expand All @@ -52,9 +53,9 @@ public async Task<Result> GetResult(string packageName, PackageVersion? installe
return new Result(packageName, current, latest);
}

public async Task<IList<Release>> GetVersions(string packageName)
public async Task<IReadOnlyCollection<Release>> GetVersions(string packageName)
{
var metadata = await _metadataResource.GetMetadataAsync(packageName, true, true, NullSourceCacheContext.Instance, NullLogger.Instance, CancellationToken.None);
return metadata.Select(m => new Release(m)).ToList();
return metadata.Select(m => new Release(m)).ToArray();
}
}
28 changes: 13 additions & 15 deletions src/LibYear.Core/ProjectFileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ public class ProjectFileManager : IProjectFileManager
public ProjectFileManager(IFileSystem fileSystem)
=> _fileSystem = fileSystem;

public async Task<IList<IProjectFile>> GetAllProjects(IReadOnlyList<string> paths)
public async Task<IReadOnlyCollection<IProjectFile>> GetAllProjects(IReadOnlyCollection<string> paths)
{
if (!paths.Any())
return await GetProjectsInDir(Directory.GetCurrentDirectory());

var tasks = paths.Select(GetProjects);
var projects = await Task.WhenAll(tasks);
return projects.SelectMany(p => p).ToList();
return projects.SelectMany(p => p).ToArray();
}

private async Task<IList<IProjectFile>> GetProjects(string path)
private async Task<IReadOnlyCollection<IProjectFile>> GetProjects(string path)
{
if (_fileSystem.Directory.Exists(path))
{
return await GetProjectsInDir(path);
}

var fileInfo = _fileSystem.FileInfo.FromFileName(path);
return new[] { await ReadFile(fileInfo) }.Where(f => f != null).ToList();
return new[] { await ReadFile(fileInfo) }.ToArray();
}

public async Task<IList<IProjectFile>> GetProjectsInDir(string dirPath)
public async Task<IReadOnlyCollection<IProjectFile>> GetProjectsInDir(string dirPath)
{
var dir = _fileSystem.DirectoryInfo.FromDirectoryName(dirPath);
var projectFiles = await FindProjectsInDir(dir, SearchOption.TopDirectoryOnly);
Expand All @@ -40,16 +40,16 @@ public async Task<IList<IProjectFile>> GetProjectsInDir(string dirPath)
: await FindProjectsInDir(dir, SearchOption.AllDirectories);
}

public async Task<IList<IProjectFile>> FindProjectsInDir(IDirectoryInfo dir, SearchOption searchMode)
public async Task<IReadOnlyCollection<IProjectFile>> FindProjectsInDir(IDirectoryInfo dir, SearchOption searchMode)
=> await Task.WhenAll(FindProjects(dir, searchMode));

private IEnumerable<Task<IProjectFile>> FindProjects(IDirectoryInfo dir, SearchOption searchMode) =>
private IReadOnlyCollection<Task<IProjectFile>> FindProjects(IDirectoryInfo dir, SearchOption searchMode) =>
dir.EnumerateFiles("*.csproj", searchMode)
.Union(dir.EnumerateFiles("Directory.build.props", searchMode))
.Union(dir.EnumerateFiles("Directory.build.targets", searchMode))
.Union(dir.EnumerateFiles("packages.config", searchMode))
.Select(ReadFile)
.ToList();
.ToArray();

private static bool IsCsProjFile(IFileSystemInfo fileInfo) => fileInfo.Extension == ".csproj";
private static bool IsDirectoryBuildPropsFile(IFileSystemInfo fileInfo) => fileInfo.Name == "Directory.Build.props";
Expand All @@ -73,18 +73,16 @@ private async Task<IProjectFile> ReadFile(IFileSystemInfo fileInfo)
throw new NotImplementedException("Unknown file type");
}

public async Task<IEnumerable<string>> Update(IDictionary<IProjectFile, IEnumerable<Result>> allResults)
public async Task<IReadOnlyCollection<string>> Update(SolutionResult result)
{
var updated = new List<string>();
foreach (var result in allResults)
foreach (var project in result.Details)
{
var projectFile = result.Key;
var results = result.Value;
var update = projectFile.Update(results);
var update = project.ProjectFile.Update(project.Details);

var stream = _fileSystem.FileStream.Create(projectFile.FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
var stream = _fileSystem.FileStream.Create(project.ProjectFile.FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
await new StreamWriter(stream).WriteAsync(update);
updated.Add(projectFile.FileName);
updated.Add(project.ProjectFile.FileName);
}

return updated;
Expand Down
18 changes: 18 additions & 0 deletions src/LibYear.Core/ProjectResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using LibYear.Core.FileTypes;

namespace LibYear.Core;

public class ProjectResult : HasAgeMeasurements
{
public IProjectFile ProjectFile { get; }
public IReadOnlyCollection<Result> Details { get; }

public ProjectResult(IProjectFile projectFile, IReadOnlyCollection<Result> details)
{
ProjectFile = projectFile;
Details = details;
}

public override double DaysBehind
=> Details.Sum(r => r.DaysBehind);
}
9 changes: 3 additions & 6 deletions src/LibYear.Core/Result.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace LibYear.Core;

public class Result
public class Result : HasAgeMeasurements
{
public string Name { get; }
public Release? Installed { get; }
Expand All @@ -13,9 +13,6 @@ public Result(string name, Release? installed, Release? latest)
Latest = latest;
}

public double YearsBehind => DaysBehind > 0
? DaysBehind / 365
: 0;

private double DaysBehind => (Latest?.Date - Installed?.Date ?? TimeSpan.Zero).TotalDays;
public override double DaysBehind
=> (Latest?.Date - Installed?.Date ?? TimeSpan.Zero).TotalDays;
}
12 changes: 12 additions & 0 deletions src/LibYear.Core/SolutionResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace LibYear.Core;

public class SolutionResult : HasAgeMeasurements
{
public IReadOnlyCollection<ProjectResult> Details { get; }

public SolutionResult(IReadOnlyCollection<ProjectResult> details)
=> Details = details;

public override double DaysBehind
=> Details.Sum(r => r.DaysBehind);
}
44 changes: 23 additions & 21 deletions src/LibYear/App.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using LibYear.Core;
using LibYear.Core.FileTypes;
using Spectre.Console;

namespace LibYear;
Expand All @@ -17,61 +16,64 @@ public App(IPackageVersionChecker checker, IProjectFileManager projectFileManage
_console = console;
}

public async Task Run(Settings settings)
public async Task<int> Run(Settings settings)
{
_console.WriteLine();
var projects = await _projectFileManager.GetAllProjects(settings.Paths);
if (!projects.Any())
{
_console.WriteLine("No project files found");
return;
return 1;
}

var allResults = await _checker.GetPackages(projects);
GetAllResultsTables(allResults, settings.QuietMode);
var result = await _checker.GetPackages(projects);
DisplayAllResultsTables(result, settings.QuietMode);

if (settings.Update)
{
var updated = await _projectFileManager.Update(allResults);
var updated = await _projectFileManager.Update(result);
foreach (var projectFile in updated)
{
_console.WriteLine($"{projectFile} updated");
}
}

var limitChecker = new LimitChecker(settings);
return limitChecker.AnyLimitsExceeded(result)
? 1
: 0;
}

private void GetAllResultsTables(IDictionary<IProjectFile, IEnumerable<Result>> allResults, bool quietMode)
private void DisplayAllResultsTables(SolutionResult allResults, bool quietMode)
{
if (!allResults.Any())
if (!allResults.Details.Any())
return;

int MaxLength(Func<Result, int> field) => allResults.Max(results => results.Value.Any() ? results.Value.Max(field) : 0);
int MaxLength(Func<Result, int> field) => allResults.Details.Max(results => results.Details.Any() ? results.Details.Max(field) : 0);
var namePad = Math.Max("Package".Length, MaxLength(r => r.Name.Length));
var installedPad = Math.Max("Installed".Length, MaxLength(r => r.Installed?.Version.ToString().Length ?? 0));
var latestPad = Math.Max("Latest".Length, MaxLength(r => r.Latest?.Version.ToString().Length ?? 0));

var width = allResults.Max(r => r.Key.FileName.Length);
foreach (var results in allResults)
var width = allResults.Details.Max(r => r.ProjectFile.FileName.Length);
foreach (var results in allResults.Details)
GetResultsTable(results, width, namePad, installedPad, latestPad, quietMode);

if (allResults.Count > 1)
if (allResults.Details.Count > 1)
{
var allTotal = allResults.Sum(ar => ar.Value.Sum(r => r.YearsBehind));
_console.WriteLine($"Total is {allTotal:F1} libyears behind");
_console.WriteLine($"Total is {allResults.YearsBehind:F1} libyears behind");
}
}

private void GetResultsTable(KeyValuePair<IProjectFile, IEnumerable<Result>> results, int titlePad, int namePad, int installedPad, int latestPad, bool quietMode)
private void GetResultsTable(ProjectResult results, int titlePad, int namePad, int installedPad, int latestPad, bool quietMode)
{
if (!results.Value.Any())
if (!results.Details.Any())
return;

var projectTotal = results.Value.Sum(r => r.YearsBehind);
var width = Math.Max(titlePad + 2, namePad + installedPad + latestPad + 48) + 2;
var table = new Table
{
Title = new TableTitle($" {results.Key.FileName}".PadRight(width)),
Caption = new TableTitle(($" Project is {projectTotal:F1} libyears behind").PadRight(width)),
Title = new TableTitle($" {results.ProjectFile.FileName}".PadRight(width)),
Caption = new TableTitle(($" Project is {results.YearsBehind:F1} libyears behind").PadRight(width)),
Width = width
};
table.AddColumn(new TableColumn("Package").Width(namePad));
Expand All @@ -81,7 +83,7 @@ private void GetResultsTable(KeyValuePair<IProjectFile, IEnumerable<Result>> res
table.AddColumn(new TableColumn("Released"));
table.AddColumn(new TableColumn("Age (y)"));

foreach (var result in results.Value.Where(r => !quietMode || r.YearsBehind > 0))
foreach (var result in results.Details.Where(r => !quietMode || r.YearsBehind > 0))
{
table.AddRow(
result.Name,
Expand All @@ -93,7 +95,7 @@ private void GetResultsTable(KeyValuePair<IProjectFile, IEnumerable<Result>> res
);
}

if (quietMode && projectTotal == 0)
if (quietMode && results.YearsBehind == 0)
{
table.ShowHeaders = false;
}
Expand Down
Loading

0 comments on commit b5db64a

Please sign in to comment.