1
+ package cmd
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "strings"
7
+ "time"
8
+ )
9
+
10
+ // displayTableStats displays stats in a table format
11
+ func displayTableStats (stats * StatsCollector ) {
12
+ // ANSI color helpers
13
+ green := "\033 [32m"
14
+ yellow := "\033 [33m"
15
+ reset := "\033 [0m"
16
+ colorize := func (s , color string ) string {
17
+ if noColor {
18
+ return s
19
+ }
20
+ return color + s + reset
21
+ }
22
+
23
+ // Find max repo name length
24
+ maxRepoLen := len ("Repository" )
25
+ for _ , repoStat := range stats .PerRepoStats {
26
+ if l := len (repoStat .RepoName ); l > maxRepoLen {
27
+ maxRepoLen = l
28
+ }
29
+ }
30
+ if maxRepoLen > 40 {
31
+ maxRepoLen = 40 // hard cap for very long repo names
32
+ }
33
+
34
+ repoCol := maxRepoLen
35
+ colWidths := []int {repoCol , 14 , 20 , 12 }
36
+
37
+ // Table border helpers
38
+ top := "╭"
39
+ sep := "├"
40
+ bot := "╰"
41
+ for i , w := range colWidths {
42
+ top += pad ("─" , w + 2 ) // +2 for padding spaces
43
+ sep += pad ("─" , w + 2 )
44
+ bot += pad ("─" , w + 2 )
45
+ if i < len (colWidths )- 1 {
46
+ top += "┬"
47
+ sep += "┼"
48
+ bot += "┴"
49
+ } else {
50
+ top += "╮"
51
+ sep += "┤"
52
+ bot += "╯"
53
+ }
54
+ }
55
+
56
+ headRepo := fmt .Sprintf ("%-*s" , repoCol , "Repository" )
57
+ headCombined := fmt .Sprintf ("%*s" , colWidths [1 ], "PRs Combined" )
58
+ headSkipped := fmt .Sprintf ("%-*s" , colWidths [2 ], "Skipped" )
59
+ headStatus := fmt .Sprintf ("%-*s" , colWidths [3 ], "Status" )
60
+ head := fmt .Sprintf (
61
+ "│ %-*s │ %s │ %s │ %s │" ,
62
+ repoCol , headRepo ,
63
+ headCombined ,
64
+ headSkipped ,
65
+ headStatus ,
66
+ )
67
+
68
+ fmt .Println (top )
69
+ fmt .Println (head )
70
+ fmt .Println (sep )
71
+
72
+ for _ , repoStat := range stats .PerRepoStats {
73
+ status := "OK"
74
+ statusColor := green
75
+ if repoStat .TotalPRs == 0 {
76
+ status = "NO OPEN PRs"
77
+ statusColor = green
78
+ } else if repoStat .NotEnoughPRs {
79
+ status = "NOT ENOUGH"
80
+ statusColor = yellow
81
+ }
82
+
83
+ mcColor := green
84
+ dnmColor := green
85
+ if repoStat .SkippedMergeConf > 0 {
86
+ mcColor = yellow
87
+ }
88
+ if repoStat .SkippedCriteria > 0 {
89
+ dnmColor = yellow
90
+ }
91
+ mcRaw := fmt .Sprintf ("%d" , repoStat .SkippedMergeConf )
92
+ dnmRaw := fmt .Sprintf ("%d" , repoStat .SkippedCriteria )
93
+ skippedRaw := fmt .Sprintf ("%s (MC), %s (DNM)" , mcRaw , dnmRaw )
94
+ skippedPadded := fmt .Sprintf ("%-*s" , colWidths [2 ], skippedRaw )
95
+ mcIdx := strings .Index (skippedPadded , mcRaw )
96
+ dnmIdx := strings .Index (skippedPadded , dnmRaw )
97
+ skippedColored := skippedPadded
98
+ if mcIdx != - 1 {
99
+ skippedColored = skippedColored [:mcIdx ] + colorize (mcRaw , mcColor ) + skippedColored [mcIdx + len (mcRaw ):]
100
+ }
101
+ if dnmIdx != - 1 {
102
+ dnmIdx = strings .Index (skippedColored , dnmRaw )
103
+ skippedColored = skippedColored [:dnmIdx ] + colorize (dnmRaw , dnmColor ) + skippedColored [dnmIdx + len (dnmRaw ):]
104
+ }
105
+ statusColored := colorize (status , statusColor )
106
+ statusColored = fmt .Sprintf ("%-*s" , colWidths [3 ]+ len (statusColored )- len (status ), statusColored )
107
+
108
+ fmt .Printf (
109
+ "│ %-*s │ %s │ %s │ %s │\n " ,
110
+ repoCol , repoStat .RepoName ,
111
+ fmt .Sprintf ("%*d" , colWidths [1 ], repoStat .CombinedCount ),
112
+ skippedColored ,
113
+ statusColored ,
114
+ )
115
+ }
116
+ fmt .Println (bot )
117
+
118
+ // Print summary mini-table with proper padding
119
+ summaryTop := "╭───────────────┬───────────────┬───────────────────────┬───────────────╮"
120
+ summaryHead := "│ Repos │ Combined PRs │ Skipped │ Total PRs │"
121
+ summarySep := "├───────────────┼───────────────┼───────────────────────┼───────────────┤"
122
+ skippedRaw := fmt .Sprintf ("%d (MC), %d (DNM)" , stats .PRsSkippedMergeConflict , stats .PRsSkippedCriteria )
123
+ summaryRow := fmt .Sprintf (
124
+ "│ %-13d │ %-13d │ %-21s │ %-13d │" ,
125
+ stats .ReposProcessed ,
126
+ stats .PRsCombined ,
127
+ skippedRaw ,
128
+ len (stats .CombinedPRLinks ),
129
+ )
130
+ summaryBot := "╰───────────────┴───────────────┴───────────────────────┴───────────────╯"
131
+ fmt .Println ()
132
+ fmt .Println (summaryTop )
133
+ fmt .Println (summaryHead )
134
+ fmt .Println (summarySep )
135
+ fmt .Println (summaryRow )
136
+ fmt .Println (summaryBot )
137
+
138
+ // Print PR links block (blue color)
139
+ if len (stats .CombinedPRLinks ) > 0 {
140
+ blue := "\033 [34m"
141
+ fmt .Println ("\n Links to Combined PRs:" )
142
+ for _ , link := range stats .CombinedPRLinks {
143
+ if noColor {
144
+ fmt .Println ("-" , link )
145
+ } else {
146
+ fmt .Printf ("- %s%s%s\n " , blue , link , reset )
147
+ }
148
+ }
149
+ }
150
+ fmt .Println ()
151
+ }
152
+
153
+ // displayJSONStats displays stats in JSON format
154
+ func displayJSONStats (stats * StatsCollector ) {
155
+ output := map [string ]interface {}{
156
+ "reposProcessed" : stats .ReposProcessed ,
157
+ "prsCombined" : stats .PRsCombined ,
158
+ "prsSkippedMergeConflict" : stats .PRsSkippedMergeConflict ,
159
+ "prsSkippedCriteria" : stats .PRsSkippedCriteria ,
160
+ "executionTime" : stats .EndTime .Sub (stats .StartTime ).String (),
161
+ "combinedPRLinks" : stats .CombinedPRLinks ,
162
+ "perRepoStats" : stats .PerRepoStats ,
163
+ }
164
+ jsonData , _ := json .MarshalIndent (output , "" , " " )
165
+ fmt .Println (string (jsonData ))
166
+ }
167
+
168
+ // displayPlainStats displays stats in plain text format
169
+ func displayPlainStats (stats * StatsCollector ) {
170
+ elapsed := stats .EndTime .Sub (stats .StartTime )
171
+ fmt .Printf ("Repositories Processed: %d\n " , stats .ReposProcessed )
172
+ fmt .Printf ("PRs Combined: %d\n " , stats .PRsCombined )
173
+ fmt .Printf ("PRs Skipped (Merge Conflicts): %d\n " , stats .PRsSkippedMergeConflict )
174
+ fmt .Printf ("PRs Skipped (Did Not Match): %d\n " , stats .PRsSkippedCriteria )
175
+ fmt .Printf ("Execution Time: %s\n " , elapsed .Round (time .Second ))
176
+
177
+ fmt .Println ("Links to Combined PRs:" )
178
+ for _ , link := range stats .CombinedPRLinks {
179
+ fmt .Println ("-" , link )
180
+ }
181
+
182
+ fmt .Println ("\n Per-Repository Details:" )
183
+ for _ , repoStat := range stats .PerRepoStats {
184
+ fmt .Printf (" %s\n " , repoStat .RepoName )
185
+ if repoStat .NotEnoughPRs {
186
+ fmt .Println (" Not enough PRs to combine." )
187
+ continue
188
+ }
189
+ fmt .Printf (" Combined: %d\n " , repoStat .CombinedCount )
190
+ fmt .Printf (" Skipped (Merge Conflicts): %d\n " , repoStat .SkippedMergeConf )
191
+ fmt .Printf (" Skipped (Did Not Match): %d\n " , repoStat .SkippedCriteria )
192
+ if repoStat .CombinedPRLink != "" {
193
+ fmt .Printf (" Combined PR: %s\n " , repoStat .CombinedPRLink )
194
+ }
195
+ }
196
+ }
197
+
198
+ // pad returns a string of n runes of s (usually "─")
199
+ func pad (s string , n int ) string {
200
+ if n <= 0 {
201
+ return ""
202
+ }
203
+ out := ""
204
+ for i := 0 ; i < n ; i ++ {
205
+ out += s
206
+ }
207
+ return out
208
+ }
209
+
210
+ // truncate shortens a string to maxLen runes, adding … if truncated
211
+ func truncate (s string , maxLen int ) string {
212
+ runes := []rune (s )
213
+ if len (runes ) <= maxLen {
214
+ return s
215
+ }
216
+ if maxLen <= 1 {
217
+ return string (runes [:maxLen ])
218
+ }
219
+ return string (runes [:maxLen - 1 ]) + "…"
220
+ }
0 commit comments