Skip to content

Commit

Permalink
refactor: FragmentFinder.FindFragments's return type
Browse files Browse the repository at this point in the history
  • Loading branch information
netpyoung committed Nov 20, 2024
1 parent be16cba commit a7861d0
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ public sealed class Settings : CommandSettings
[CommandOption("--date")]
public string ProjectDate { get; set; } = string.Empty;

[Description("Do not ask for confirmation to remove news fragments")]
[CommandOption("--yes")]
public bool IsAnswerYes { get; set; }

[Description("Do not ask for confirmations. But keep news fragments.")]
[CommandOption("--keep")]
public bool IsAnswerKeep { get; set; }

public override ValidationResult Validate()
{
if (string.IsNullOrEmpty(ProjectVersion))
Expand All @@ -51,10 +59,10 @@ public override ValidationResult Validate()

public override async Task<int> ExecuteAsync(CommandContext context, Settings setting)
{
Exception? ex = Utils.GetConfig(setting.Directory, setting.Config, out string baseDirectory, out PatchNoteConfig config);
if (ex is not null)
Exception? exOrNull = Utils.GetConfig(setting.Directory, setting.Config, out string baseDirectory, out PatchNoteConfig config);
if (exOrNull is not null)
{
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
AnsiConsole.WriteException(exOrNull, ExceptionFormats.ShortenEverything);
return 1;
}

Expand All @@ -73,13 +81,19 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
}
else
{
throw new PatchNoteMakerException("WTF");
AnsiConsole.MarkupLine("'--version' is required since the config file does not contain 'version' or 'package'.");
return 1;
}

Console.WriteLine("Finding news fragments...");
TemplateModel model = DummyTemplateModel();

(Dictionary<string, Dictionary<(string?, string?, int), string>>, List<(string, string?)>) x = FragmentFinder.FindFragments(baseDirectory, config, strict: false);
(Exception? fragmentResultExOrNull, FragmentResult fragmentResult) = FragmentFinder.FindFragments(baseDirectory, config, strict: false);
if (fragmentResultExOrNull != null)
{
AnsiConsole.WriteException(fragmentResultExOrNull, ExceptionFormats.ShortenEverything);
return 1;
}

Console.WriteLine("Rendering news fragments...");
List<string> willDeleteFilePaths = new List<string>(20);
Expand Down
174 changes: 135 additions & 39 deletions NF.Tool.PatchNoteMaker/NF.Tool.PatchNoteMaker.CLI/Impl/FragmentFinder.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,71 @@
using NF.Tool.PatchNoteMaker.Common;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace NF.Tool.PatchNoteMaker.CLI.Impl
{
public sealed class FragmentBasename
{
public string Issue { get; set; }
public string Category { get; init; }
public int RetryCounter { get; set; }

public FragmentBasename(string issue, string category, int retryCounter)
{
Issue = issue;
Category = category;
RetryCounter = retryCounter;
}

public static FragmentBasename Invalid()
{
return new FragmentBasename(string.Empty, string.Empty, 0);
}
}
public sealed class FragmentContent
{
public string SectionName { get; set; } = string.Empty;
public List<SectionFragment> SectionFragments { get; set; } = new List<SectionFragment>();

public sealed record class SectionFragment(FragmentBasename FragmentBasename, string Data);
}

public sealed class FragmentFile
{
public required string FileName { get; init; }
public required string Category { get; init; }
}

public sealed class FragmentResult
{
public required List<FragmentContent> FragmentContents { get; init; }
public required List<FragmentFile> FragmentFiles { get; init; }

public static FragmentResult Default()
{
return new FragmentResult
{
FragmentContents = new List<FragmentContent>(),
FragmentFiles = new List<FragmentFile>()
};
}
}

public sealed class FragmentFinder
{

[Description("Key: SectionName")]
// section_name, section_fragments(issue, category, counter), content // fragment_files(filename, category)
public static
(Dictionary<string, Dictionary<(string?, string?, int), string>>, List<(string, string?)>)
(Exception? exOrNull, FragmentResult result)
FindFragments
(string baseDirectory, PatchNoteConfig config, bool strict)
{
HashSet<string> ignoredFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
HashSet<string> ignoredFileNameSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".gitignore",
".gitkeep",
Expand All @@ -26,23 +77,22 @@ public static

if (!string.IsNullOrEmpty(config.Maker.TemplateFilePath))
{
ignoredFiles.Add(Path.GetFileName(config.Maker.TemplateFilePath));
ignoredFileNameSet.Add(Path.GetFileName(config.Maker.TemplateFilePath).ToLower());
}

foreach (string filename in config.Maker.Ignores)
{
ignoredFiles.Add(filename.ToLower());
ignoredFileNameSet.Add(filename.ToLower());
}

FragmentsPath getSectionPath = new FragmentsPath(baseDirectory, config);
Dictionary<string, int> orphanFragmentCounter = new Dictionary<string, int>(config.Sections.Count);

Dictionary<string, Dictionary<(string?, string?, int), string>> content = new Dictionary<string, Dictionary<(string?, string?, int), string>>();
List<(string, string?)> fragmentFiles = new List<(string, string?)>();
Dictionary<string?, int> orphanFragmentCounter = new Dictionary<string?, int>();

List<FragmentContent> fragmentContents = new List<FragmentContent>(config.Sections.Count);
List<FragmentFile> fragmentFiles = new List<FragmentFile>(config.Sections.Count);
foreach (PatchNoteConfig.PatchNoteSection section in config.Sections)
{
string key = section.Name;
string sectionName = section.Name;
string sectionDir = getSectionPath.Resolve(section.Path);

string[] files;
Expand All @@ -52,77 +102,123 @@ public static
}
catch (DirectoryNotFoundException)
{
files = Array.Empty<string>();
files = [];
}

Dictionary<(string?, string?, int), string> fileContent = new Dictionary<(string?, string?, int), string>();
List<FragmentContent.SectionFragment> fileContent = new List<FragmentContent.SectionFragment>();

foreach (string file in files)
{
string basename = Path.GetFileName(file);
if (ignoredFiles.Any(pattern => IsMatch(basename.ToLower(), pattern)))
if (ignoredFileNameSet.Any(pattern => IsMatch(basename.ToLower(), pattern)))
{
continue;
}

(string issue, string category, int counter) = ParseNewFragmentBasename(basename, config.Types);
if (category == null)
FragmentBasename? fragmentBaseNameOrNull = ParseNewFragmentBasenameOrNull(basename, config.Types);
if (fragmentBaseNameOrNull == null)
{
if (strict && issue == null)
if (strict)
{
throw new InvalidOperationException(
$"Invalid news fragment name: {basename}\n" +
"If this filename is deliberate, add it to 'ignore' in your configuration.");
InvalidOperationException ex = new InvalidOperationException($"Invalid news fragment name: {basename}\nIf this filename is deliberate, add it to 'ignore' in your configuration.");
return (ex, FragmentResult.Default());
}
continue;
}

if (config.Maker.OrphanPrefix != null && issue != null && issue.StartsWith(config.Maker.OrphanPrefix))
FragmentBasename fragmentBaseName = fragmentBaseNameOrNull;
if (!string.IsNullOrEmpty(config.Maker.OrphanPrefix))
{
issue = "";
if (!orphanFragmentCounter.ContainsKey(category))
if (fragmentBaseName.Issue.StartsWith(config.Maker.OrphanPrefix))
{
orphanFragmentCounter[category] = 0;
fragmentBaseName.Issue = "";
if (!orphanFragmentCounter.ContainsKey(fragmentBaseName.Category))
{
orphanFragmentCounter[fragmentBaseName.Category] = 0;
}
fragmentBaseName.RetryCounter = orphanFragmentCounter[fragmentBaseName.Category]++;
}
counter = orphanFragmentCounter[category]++;
}

if (!string.IsNullOrEmpty(config.Maker.IssuePattern) &&
issue != null &&
!Regex.IsMatch(issue, config.Maker.IssuePattern))
if (!string.IsNullOrEmpty(config.Maker.IssuePattern))
{
throw new InvalidOperationException(
$"Issue name '{issue}' does not match the configured pattern, '{config.Maker.IssuePattern}'");
if (!Regex.IsMatch(fragmentBaseName.Issue, config.Maker.IssuePattern))
{
InvalidOperationException ex = new InvalidOperationException($"Issue name '{fragmentBaseName.Issue}' does not match the configured pattern, '{config.Maker.IssuePattern}'");
return (ex, FragmentResult.Default());
}
}

string fullFilename = Path.Combine(sectionDir, basename);
fragmentFiles.Add((fullFilename, category));
string data = File.ReadAllText(fullFilename);
fragmentFiles.Add(new FragmentFile { FileName = fullFilename, Category = fragmentBaseName.Category });

(string? issue, string category, int counter) keyTuple = (issue, category, counter);
if (fileContent.ContainsKey(keyTuple))
string data = File.ReadAllText(fullFilename);
if (fileContent.Find(x => x.FragmentBasename == fragmentBaseName) != null)
{
throw new InvalidOperationException(
$"Multiple files for {issue}.{category} in {sectionDir}");
InvalidOperationException ex = new InvalidOperationException($"Multiple files for {fragmentBaseName.Issue}.{fragmentBaseName.Category} in {sectionDir}");
return (ex, FragmentResult.Default());
}

fileContent[keyTuple] = data;
fileContent.Add(new FragmentContent.SectionFragment(fragmentBaseName, data));
}

content[key] = fileContent;
fragmentContents.Add(new FragmentContent { SectionName = sectionName, SectionFragments = fileContent });
}

return (content, fragmentFiles);
return (null, new FragmentResult { FragmentContents = fragmentContents, FragmentFiles = fragmentFiles });
}

private static bool IsMatch(string input, string pattern)
{
return Regex.IsMatch(input, "^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$");
}

private static (string issue, string category, int counter) ParseNewFragmentBasename(string basename, List<PatchNoteConfig.PatchNoteType> types)
private static FragmentBasename? ParseNewFragmentBasenameOrNull(string basename, List<PatchNoteConfig.PatchNoteType> types)
{
throw new NotImplementedException();
if (string.IsNullOrWhiteSpace(basename))
{
return null;
}

string[] parts = basename.Split('.');
if (parts.Length == 1)
{
return null;
}

int i = parts.Length - 1;
while (true)
{
if (i == 1)
{
return null;
}

if (types.Find(x => x.Name == parts[i]) == null)
{
i--;
continue;
}

string category = parts[i];
string issue = string.Join(".", parts.Take(i)).Trim();

if (int.TryParse(issue, out int issueAsInt))
{
issue = issueAsInt.ToString();
}

int retryCounter = 0;
if (i + 1 < parts.Length)
{
if (int.TryParse(parts[i + 1], out int parsedCounter))
{
retryCounter = parsedCounter;
}
}

return new FragmentBasename(issue, category, retryCounter);
}
}
}
}

0 comments on commit a7861d0

Please sign in to comment.