Skip to content

Commit

Permalink
Add count command (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
chullybun authored Dec 20, 2023
1 parent 0bb5203 commit cdb58c7
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 23 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Represents the **NuGet** versions.

## v5.7.3
- *Fixed:* The `clean` code-generation command supports new path exclusion capabilities; see `dotnet run -- --help` for details.
- *Fixed:* The `count` code-generation command has been added to report the total number of files and lines for all and generated code.

## v5.7.2
- *Fixed:* The `Entity.HttpAgentCustomMapper` property has been added to the schema for correctly include within code-generation.
- *Fixed:* Upgraded `CoreEx` (`v3.7.0`) to include all related fixes and improvements.
Expand Down
2 changes: 1 addition & 1 deletion Common.targets
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>5.7.2</Version>
<Version>5.7.3</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
5 changes: 5 additions & 0 deletions tools/Beef.CodeGen.Core/Beef.CodeGen.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@
</ItemGroup>

<ItemGroup>
<None Remove="ExtendedHelp.txt" />
<None Remove="Templates\EntityIWebApiAgent_cs.hbs" />
<None Remove="Templates\ReferenceDataIWebApiAgent_cs.hbs" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="ExtendedHelp.txt" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OnRamp" Version="1.0.8" />
Expand Down
265 changes: 244 additions & 21 deletions tools/Beef.CodeGen.Core/CodeGenConsole.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef

using CoreEx.Abstractions;
using McMaster.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging;
using OnRamp;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.IO;
Expand All @@ -19,6 +21,8 @@ namespace Beef.CodeGen
/// <remarks>Command line parsing: https://natemcmaster.github.io/CommandLineUtils/ </remarks>
public class CodeGenConsole : OnRamp.Console.CodeGenConsole
{
private static readonly string[] _countExtensions = [".cs", ".json", ".jsn", ".yaml", ".yml", ".xml", ".sql"];

private string _entityScript = "EntityWebApiCoreAgent.yaml";
private string _refDataScript = "RefDataCoreCrud.yaml";
private string _dataModelScript = "DataModelOnly.yaml";
Expand Down Expand Up @@ -192,6 +196,9 @@ public CodeGenConsole DatabaseConnectionString(string connectionString)
protected override void OnBeforeExecute(CommandLineApplication app)
{
_cmdArg = app.Argument<CommandType>("command", "Execution command type.", false).IsRequired();

using var sr = Resource.GetStreamReader<CodeGenConsole>("ExtendedHelp.txt");
app.ExtendedHelpText = sr.ReadToEnd();
}

/// <inheritdoc/>
Expand Down Expand Up @@ -255,7 +262,10 @@ protected override async Task<CodeGenStatistics> OnCodeGenerationAsync()
stats.Add(await ExecuteCodeGenerationAsync(_dataModelScript, CodeGenFileManager.GetConfigFilename(exedir, CommandType.DataModel, company, appName), count++).ConfigureAwait(false));

if (cmd.HasFlag(CommandType.Clean))
ExecuteCleanAsync();
ExecuteClean();

if (cmd.HasFlag(CommandType.Count))
ExecuteCount();

if (count > 1)
{
Expand Down Expand Up @@ -310,41 +320,254 @@ public static async Task<CodeGenStatistics> ExecuteCodeGenerationAsync(CodeGener
/// <summary>
/// Executes the clean.
/// </summary>
private void ExecuteCleanAsync()
private void ExecuteClean()
{
if (Args.OutputDirectory == null)
return;

var exclude = Args.GetParameter<string?>("exclude")?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) ?? [];

Args.Logger?.LogInformation("{Content}", $"Cleaning: {Args.OutputDirectory.FullName}");
Args.Logger?.LogInformation("{Content}", $"Exclude: {string.Join(", ", exclude)}");
Args.Logger?.LogInformation("{Content}", string.Empty);


// Use the count logic to detemine all paths with specified exclusions.
var sw = Stopwatch.StartNew();
var dcs = new DirectoryCountStatistics(Args.OutputDirectory, exclude);
CountDirectoryAndItsChildren(dcs);
dcs?.Clean(Args.Logger!);

sw.Stop();
Args.Logger?.LogInformation("{Content}", string.Empty);
Args.Logger?.LogInformation("{Content}", $"{AppName} Complete. [{sw.Elapsed.TotalMilliseconds}ms, Files: {dcs?.GeneratedTotalFileCount ?? 0}]");
Args.Logger?.LogInformation("{Content}", string.Empty);
}

/// <summary>
/// Executes the count.
/// </summary>
private void ExecuteCount()
{
if (Args.OutputDirectory == null)
return;

var exclude = Args.GetParameter<string?>("exclude")?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) ?? [];

Args.Logger?.LogInformation("{Content}", $"Counting: {Args.OutputDirectory.FullName}");
Args.Logger?.LogInformation("{Content}", $"Include: {string.Join(", ", _countExtensions)}");
Args.Logger?.LogInformation("{Content}", $"Exclude: {string.Join(", ", exclude)}");
Args.Logger?.LogInformation("{Content}", string.Empty);
Args.Logger?.LogInformation("{Content}", "The following 'Generated' directories were cleaned/deleted:");
int fileCount = 0;
bool dirDeleted = false;

var sw = Stopwatch.StartNew();
var dcs = new DirectoryCountStatistics(Args.OutputDirectory, exclude);
CountDirectoryAndItsChildren(dcs);

var columnLength = Math.Max(dcs.TotalLineCount.ToString().Length, 5);
dcs.Write(Args.Logger!, columnLength, 0);

Args.Logger?.LogInformation("{Content}", string.Empty);
Args.Logger?.LogInformation("{Content}", $"{AppName} Complete. [{sw.Elapsed.TotalMilliseconds}ms]");
Args.Logger?.LogInformation("{Content}", string.Empty);
}

/// <summary>
/// Count the directory and its children (recursive).
/// </summary>
private static void CountDirectoryAndItsChildren(DirectoryCountStatistics dcs)
{
foreach (var di in dcs.Directory.EnumerateDirectories())
{
if (di.Name.Equals("obj", StringComparison.InvariantCultureIgnoreCase) || di.Name.Equals("bin", StringComparison.InvariantCultureIgnoreCase))
continue;

if (dcs.Exclude.Any(x => di.Name.Contains(x, StringComparison.InvariantCultureIgnoreCase)))
continue;

CountDirectoryAndItsChildren(dcs.AddChildDirectory(di));
}

foreach (var fi in dcs.Directory.EnumerateFiles())
{
if (!_countExtensions.Contains(fi.Extension, StringComparer.OrdinalIgnoreCase))
continue;

using var sr = fi.OpenText();
while (sr.ReadLine() is not null)
{
dcs.IncrementLineCount();
}

dcs.IncrementFileCount();
}
}

/// <summary>
/// Provides <see cref="DirectoryInfo"/> count statistics.
/// </summary>
internal class DirectoryCountStatistics
{
/// <summary>
/// Initializes a new instance of the <see cref="DirectoryCountStatistics"/> class.
/// </summary>
public DirectoryCountStatistics(DirectoryInfo directory, string[] exclude)
{
Directory = directory;
if (directory.Name == "Generated")
IsGenerated = true;

Exclude = exclude ?? [];
}

/// <summary>
/// Gets the <see cref="DirectoryInfo"/>.
/// </summary>
public DirectoryInfo Directory { get; }

/// <summary>
/// Gets the directory/path names to exclude.
/// </summary>
public string[] Exclude { get; private set; }

/// <summary>
/// Gets the file count.
/// </summary>
public int FileCount { get; private set; }

/// <summary>
/// Gets the total file count including children.
/// </summary>
public int TotalFileCount => FileCount + Children.Sum(x => x.TotalFileCount);

/// <summary>
/// Gets the generated file count.
/// </summary>
public int GeneratedFileCount { get; private set; }

/// <summary>
/// Gets the total generated file count including children.
/// </summary>
public int GeneratedTotalFileCount => GeneratedFileCount + Children.Sum(x => x.GeneratedTotalFileCount);

/// <summary>
/// Gets the line count;
/// </summary>
public int LineCount { get; private set; }

/// <summary>
/// Gets the total line count including children.
/// </summary>
public int TotalLineCount => LineCount + Children.Sum(x => x.TotalLineCount);

/// <summary>
/// Gets the generated line count.
/// </summary>
public int GeneratedLineCount { get; private set; }

/// <summary>
/// Gets the total line count including children.
/// </summary>
public int GeneratedTotalLineCount => GeneratedLineCount + Children.Sum(x => x.GeneratedTotalLineCount);

/// <summary>
/// Indicates whether the contents of the directory are generated.
/// </summary>
public bool IsGenerated { get; private set; }

/// <summary>
/// Gets the child <see cref="DirectoryCountStatistics"/> instances.
/// </summary>
public List<DirectoryCountStatistics> Children { get; } = [];

/// <summary>
/// Increments the file count.
/// </summary>
public void IncrementFileCount()
{
FileCount++;
if (IsGenerated)
GeneratedFileCount++;
}

/// <summary>
/// Increments the line count.
/// </summary>
public void IncrementLineCount()
{
LineCount++;
if (IsGenerated)
GeneratedLineCount++;
}

var list = Args.OutputDirectory.EnumerateDirectories("Generated", SearchOption.AllDirectories)
.Where(x => !x.FullName.Contains(Path.Combine("obj", "debug"), StringComparison.OrdinalIgnoreCase) && !x.FullName.Contains(Path.Combine("obj", "release"), StringComparison.OrdinalIgnoreCase)
&& !x.FullName.Contains(Path.Combine("bin", "debug"), StringComparison.OrdinalIgnoreCase) && !x.FullName.Contains(Path.Combine("bin", "release"), StringComparison.OrdinalIgnoreCase));
/// <summary>
/// Adds a child <see cref="DirectoryCountStatistics"/> instance.
/// </summary>
public DirectoryCountStatistics AddChildDirectory(DirectoryInfo di)
{
var dcs = new DirectoryCountStatistics(di, Exclude);
if (IsGenerated)
dcs.IsGenerated = true;

Children.Add(dcs);
return dcs;
}

if (list != null)
/// <summary>
/// Write the count statistics.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/>.</param>
/// <param name="columnLength">The maximum column length.</param>
/// <param name="indent">The indent size to show hierarchy.</param>
public void Write(ILogger logger, int columnLength, int indent = 0)
{
int count;
foreach (var di in list.Where(x => x.Exists))
if (indent == 0)
{
var hdrAll = string.Format("{0, " + columnLength + "}", "All");
var hdrGen = string.Format("{0, " + (columnLength + 5) + "}", "Generated");
var hdrfiles = string.Format("{0, " + columnLength + "}", "Files");
var hdrlines = string.Format("{0, " + columnLength + "}", "Lines");

logger.LogInformation("{Content}", $"{hdrAll} | {hdrAll} | {hdrGen} | {hdrGen} | Path/");
logger.LogInformation("{Content}", $"{hdrfiles} | {hdrlines} | {hdrfiles} Perc | {hdrlines} Perc | Directory");
logger.LogInformation("{Content}", new string('-', 75));
}

var totfiles = string.Format("{0, " + columnLength + "}", TotalFileCount);
var totlines = string.Format("{0, " + columnLength + "}", TotalLineCount);
var totgenFiles = string.Format("{0, " + columnLength + "}", GeneratedTotalFileCount);
var totgenFilesPerc = string.Format("{0, " + 3 + "}", GeneratedTotalFileCount == 0 ? 0 : Math.Round((double)GeneratedTotalFileCount / (double)TotalFileCount * 100.0, 0));
var totgenLines = string.Format("{0, " + columnLength + "}", GeneratedTotalLineCount);
var totgenLinesPerc = string.Format("{0, " + 3 + "}", GeneratedTotalLineCount == 0 ? 0 : Math.Round((double)GeneratedTotalLineCount / (double)TotalLineCount * 100.0, 0));

logger.LogInformation("{Content}", $"{totfiles} {totlines} {totgenFiles} {totgenFilesPerc}% {totgenLines} {totgenLinesPerc}% {new string(' ', indent * 2)}{Directory.FullName}");

foreach (var dcs in Children)
{
dirDeleted = true;
fileCount += count = di.GetFiles().Length;
Args.Logger?.LogWarning(" {Directory} [{FileCount} files]", di.FullName, count);
di.Delete(true);
if (dcs.TotalFileCount > 0)
dcs.Write(logger, columnLength, indent + 1);
}
}

sw.Stop();
if (!dirDeleted)
Args.Logger?.LogInformation("{Content}", " ** No directories found.");
/// <summary>
/// Cleans (deletes) all <see cref="IsGenerated"/> directories.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/></param>
public void Clean(ILogger logger)
{
// Where generated then delete.
if (IsGenerated)
{
logger.LogWarning(" Deleted: {Directory} [{FileCount} files]", Directory.FullName, TotalFileCount);
Directory.Delete(true);
return;
}

Args.Logger?.LogInformation("{Content}", string.Empty);
Args.Logger?.LogInformation("{Content}", $"{AppName} Complete. [{sw.Elapsed.TotalMilliseconds}ms, Files: {fileCount}]");
Args.Logger?.LogInformation("{Content}", string.Empty);
// Where not generated then clean children.
foreach (var dcs in Children)
{
dcs.Clean(logger);
}
}
}
}
}
7 changes: 6 additions & 1 deletion tools/Beef.CodeGen.Core/CommandType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public enum CommandType
/// <summary>
/// Cleans (removes) all child 'Generated' directories.
/// </summary>
Clean = 2048
Clean = 2048,

/// <summary>
/// Counts the files and lines of code for child directories distinguising between 'Generated' and non-generated.
/// </summary>
Count = 4096
}
}
7 changes: 7 additions & 0 deletions tools/Beef.CodeGen.Core/ExtendedHelp.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

Extended commands and argument(s):
clean Cleans (removes) all related directories named 'Generated'.
- Use --param exclude=name[,name] to exclude named directory(s) from the clean.

count Counts and reports the number of files and lines (All and Generated) within all related directories.
- Use --param exclude=name[,name] to exclude named directory(s) from the count.

0 comments on commit cdb58c7

Please sign in to comment.