From 7ada6d629ccfd3f015ec482397c157d0ebb8b10b Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:10:52 +0900 Subject: [PATCH 1/2] Add Bot/Community PR categorization to digest pages --- src/PRDigest.NET/HtmlGenereator.cs | 117 ++++++++++++++++------- src/PRDigest.NET/PullReqeustAnalayzer.cs | 58 +++++++++-- 2 files changed, 129 insertions(+), 46 deletions(-) diff --git a/src/PRDigest.NET/HtmlGenereator.cs b/src/PRDigest.NET/HtmlGenereator.cs index 01d6a7b..05655b5 100644 --- a/src/PRDigest.NET/HtmlGenereator.cs +++ b/src/PRDigest.NET/HtmlGenereator.cs @@ -63,9 +63,13 @@ public static string GenerateIndex(string archivesDir, string outputsDir) statsHtml = $"""
-
{analyzerResult.PullRequestCount}
+
{analyzerResult.PullRequestTotalCount}
マージされたPR
+
+
{analyzerResult.PullRequestCountForBot}
+
マージされたPR(Bot)
+
{analyzerResult.LabelCount}
ラベル種類
@@ -120,6 +124,7 @@ public static string GenerateHtmlFromMarkdown(string startTargetDate, string mar } var analyzerResult = PullReqeustAnalayzer.Analayze(document); + var categoryViewHtml = GenerateCategorizedTocHtml(analyzerResult); var labelViewHtml = GenerateLabelViewHtml(analyzerResult); var content = $""" @@ -128,11 +133,15 @@ public static string GenerateHtmlFromMarkdown(string startTargetDate, string mar
+
{tocHtml}
+ @@ -164,49 +173,83 @@ private static string GenerateLabelViewHtml(PullReqeustAnalayzer.AnalayzerResult foreach (var heading in headingBlocks) { - // Extract PR number and title from HeadingBlock inlines - var pullRequestNumber = ""; - var titleText = ""; + AppendHeadingListItem(ref builder, heading); + } + + builder.AppendLiteral($" {Environment.NewLine}"); + builder.AppendLiteral($"{Environment.NewLine}"); + } + + return builder.ToStringAndClear(); + } + + private static string GenerateCategorizedTocHtml(PullReqeustAnalayzer.AnalayzerResult analyzerResult) + { + var builder = new DefaultInterpolatedStringHandler(0, 0); + builder.AppendLiteral($"

カテゴリ別PR一覧

{Environment.NewLine}"); + + // Community PRs (expanded) + var communityCount = analyzerResult.CommunityPullRequestHeadingSpan.Length; + builder.AppendLiteral($"
{Environment.NewLine}"); + builder.AppendLiteral($" Community PRs ({communityCount} PRs){Environment.NewLine}"); + builder.AppendLiteral($"
    {Environment.NewLine}"); + foreach (var heading in analyzerResult.CommunityPullRequestHeadingSpan) + { + AppendHeadingListItem(ref builder, heading); + } + builder.AppendLiteral($"
{Environment.NewLine}"); + builder.AppendLiteral($"
{Environment.NewLine}"); + + // Bot PRs (collapsed) + var botCount = analyzerResult.BotPullRequestHeadings?.Count ?? 0; + builder.AppendLiteral($"
{Environment.NewLine}"); + builder.AppendLiteral($" Bot PRs ({botCount} PRs){Environment.NewLine}"); + builder.AppendLiteral($"
    {Environment.NewLine}"); + foreach (var heading in analyzerResult.BotPullRequestHeadings ?? []) + { + AppendHeadingListItem(ref builder, heading); + } + builder.AppendLiteral($"
{Environment.NewLine}"); + builder.AppendLiteral($"
{Environment.NewLine}"); + + return builder.ToStringAndClear(); + } + + private static void AppendHeadingListItem(ref DefaultInterpolatedStringHandler builder, HeadingBlock heading) + { + var pullRequestNumber = ""; + var titleText = ""; - var inline = heading.Inline?.FirstChild; - while (inline is not null) + var inline = heading.Inline?.FirstChild; + while (inline is not null) + { + if (inline is LinkInline linkInline) + { + var linkChild = linkInline.FirstChild; + while (linkChild is not null) { - if (inline is LinkInline linkInline) + if (linkChild is LiteralInline lit) { - // The link text contains the PR number like "#124237" - var linkChild = linkInline.FirstChild; - while (linkChild is not null) - { - if (linkChild is LiteralInline lit) - { - pullRequestNumber = lit.Content.ToString(); - } - linkChild = linkChild.NextSibling; - } + pullRequestNumber = lit.Content.ToString(); } - else if (inline is LiteralInline literal) - { - titleText += literal.Content.ToString(); - } - else if (inline is CodeInline codeInline) - { - titleText += codeInline.Content; - } - inline = inline.NextSibling; + linkChild = linkChild.NextSibling; } - - // Derive anchor ID from PR number (remove '#') - var anchorId = pullRequestNumber.TrimStart('#'); - var displayText = $"{pullRequestNumber} {titleText.Trim()}"; - - builder.AppendLiteral($"
  • {System.Net.WebUtility.HtmlEncode(displayText)}
  • {Environment.NewLine}"); } - - builder.AppendLiteral($" {Environment.NewLine}"); - builder.AppendLiteral($"{Environment.NewLine}"); + else if (inline is LiteralInline literal) + { + titleText += literal.Content.ToString(); + } + else if (inline is CodeInline codeInline) + { + titleText += codeInline.Content; + } + inline = inline.NextSibling; } - return builder.ToStringAndClear(); + var anchorId = pullRequestNumber.TrimStart('#'); + var displayText = $"{pullRequestNumber} {titleText.Trim()}"; + + builder.AppendLiteral($"
  • {System.Net.WebUtility.HtmlEncode(displayText)}
  • {Environment.NewLine}"); } private static string GenerateTemplateHtml(string title, string subTitle, string content, bool includeViewScript = false) @@ -576,7 +619,7 @@ footer p { .stats-grid { display: grid; - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 16px 0 24px 0; } diff --git a/src/PRDigest.NET/PullReqeustAnalayzer.cs b/src/PRDigest.NET/PullReqeustAnalayzer.cs index 0ad9887..2cd1a4f 100644 --- a/src/PRDigest.NET/PullReqeustAnalayzer.cs +++ b/src/PRDigest.NET/PullReqeustAnalayzer.cs @@ -9,11 +9,14 @@ internal static class PullReqeustAnalayzer public static AnalayzerResult Analayze(MarkdownDocument document) { var tableOfContents = false; - var pullRequestCount = 0; + var pullRequestTotalCount = 0; + var pullRequestCountForBot = 0; HeadingBlock? nextPrNumber = null; HashSet? prNumberTable = null; Dictionary>? labelTable = new(); Dictionary? labelColorMap = new(); + List botPullRequestHeadings = new(); + List communityPrHeadings = new(); foreach (var block in document) { @@ -29,10 +32,39 @@ public static AnalayzerResult Analayze(MarkdownDocument document) { if (tableOfContents && nextPrNumber is not null) { - var labelBlock = listBlock.Descendants().Skip(3).FirstOrDefault(); - var labels = labelBlock?.Descendants() - .Where(l => - { + // pullRequestInfo is 4 item. + // 0: User + // 1: Created at + // 2: Merged at + // 3: Labels + var pullRequestInfo = listBlock.Descendants().ToArray(); + + var userBlock = pullRequestInfo[0]; + var user = userBlock?.Descendants().Skip(1).FirstOrDefault(); + + if (user is not null) + { + var userName = user.Content.ToString().Trim(); + + // check ..[bot].. or @Copilot to count bot PRs + if (userName.EndsWith("[bot]", StringComparison.OrdinalIgnoreCase) || + userName.IndexOf("@Copilot", StringComparison.OrdinalIgnoreCase) > -1) + { + pullRequestCountForBot++; + botPullRequestHeadings.Add(nextPrNumber); + } + else + { + communityPrHeadings.Add(nextPrNumber); + } + } + else + { + communityPrHeadings.Add(nextPrNumber); + } + + var labelBlock = pullRequestInfo[3]; + var labels = labelBlock?.Descendants().Where(l => { var labelText = l.Content.ToString(); return !string.IsNullOrWhiteSpace(labelText) && !labelText.Contains("ラベル"); }); @@ -89,7 +121,7 @@ public static AnalayzerResult Analayze(MarkdownDocument document) { foreach (var listItemBlock in listBlock.Descendants()) { - pullRequestCount++; + pullRequestTotalCount++; var prNumber = listItemBlock.Descendants().FirstOrDefault(); if (prNumber is not null) { @@ -105,17 +137,25 @@ public static AnalayzerResult Analayze(MarkdownDocument document) return new AnalayzerResult { - PullRequestCount = pullRequestCount, + PullRequestTotalCount = pullRequestTotalCount, + PullRequestCountForBot = pullRequestCountForBot, LabelInfo = labelTable, - LabelColorMap = labelColorMap + LabelColorMap = labelColorMap, + BotPullRequestHeadings = botPullRequestHeadings, + CommunityPullRequestHeadings = communityPrHeadings }; } public ref struct AnalayzerResult { - public int PullRequestCount; + public int PullRequestTotalCount; + public int PullRequestCountForBot; public Dictionary>? LabelInfo; public Dictionary? LabelColorMap; + public List? BotPullRequestHeadings; + public List CommunityPullRequestHeadings; + public readonly int LabelCount => LabelInfo?.Count ?? 0; + public ReadOnlySpan CommunityPullRequestHeadingSpan => CollectionsMarshal.AsSpan(CommunityPullRequestHeadings); } } \ No newline at end of file From 11ae139ae969b52ecc908253af931fbea9624088 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:22:34 +0900 Subject: [PATCH 2/2] Update src/PRDigest.NET/PullReqeustAnalayzer.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/PRDigest.NET/PullReqeustAnalayzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PRDigest.NET/PullReqeustAnalayzer.cs b/src/PRDigest.NET/PullReqeustAnalayzer.cs index 2cd1a4f..403e0bb 100644 --- a/src/PRDigest.NET/PullReqeustAnalayzer.cs +++ b/src/PRDigest.NET/PullReqeustAnalayzer.cs @@ -32,7 +32,7 @@ public static AnalayzerResult Analayze(MarkdownDocument document) { if (tableOfContents && nextPrNumber is not null) { - // pullRequestInfo is 4 item. + // pullRequestInfo is 4 items. // 0: User // 1: Created at // 2: Merged at