@@ -4,6 +4,7 @@ package main
44import (
55 "context"
66 "encoding/json"
7+ "errors"
78 "flag"
89 "fmt"
910 "log"
@@ -26,13 +27,15 @@ func main() {
2627
2728 flag .Usage = func () {
2829 fmt .Fprintf (os .Stderr , "Usage: %s [options] <PR_URL>\n \n " , os .Args [0 ])
29- fmt .Fprintf (os .Stderr , "Calculate the real-world cost of a GitHub pull request.\n \n " )
30- fmt .Fprintf (os .Stderr , "Options:\n " )
30+ fmt .Fprint (os .Stderr , "Calculate the real-world cost of a GitHub pull request.\n \n " )
31+ fmt .Fprint (os .Stderr , "Options:\n " )
3132 flag .PrintDefaults ()
32- fmt .Fprintf (os .Stderr , "\n Examples:\n " )
33+ fmt .Fprint (os .Stderr , "\n Examples:\n " )
3334 fmt .Fprintf (os .Stderr , " %s https://github.com/owner/repo/pull/123\n " , os .Args [0 ])
3435 fmt .Fprintf (os .Stderr , " %s --salary 300000 --benefits 1.4 https://github.com/owner/repo/pull/123\n " , os .Args [0 ])
35- fmt .Fprintf (os .Stderr , " %s --salary 200000 --benefits 1.25 --event-minutes 30 --format json https://github.com/owner/repo/pull/123\n " , os .Args [0 ])
36+ fmt .Fprintf (os .Stderr ,
37+ " %s --salary 200000 --benefits 1.25 --event-minutes 30 --format json https://github.com/owner/repo/pull/123\n " ,
38+ os .Args [0 ])
3639 }
3740
3841 flag .Parse ()
@@ -47,7 +50,7 @@ func main() {
4750
4851 // Validate PR URL format
4952 if ! strings .HasPrefix (prURL , "https://github.com/" ) || ! strings .Contains (prURL , "/pull/" ) {
50- log .Fatalf ("Invalid PR URL. Expected format: https://github.com/owner/repo/pull/123" )
53+ log .Fatal ("Invalid PR URL. Expected format: https://github.com/owner/repo/pull/123" )
5154 }
5255
5356 // Create cost configuration from flags
@@ -57,9 +60,9 @@ func main() {
5760 cfg .EventDuration = time .Duration (* eventMinutes ) * time .Minute
5861 cfg .DelayCostFactor = * overheadFactor
5962
60- // Get GitHub token from gh CLI
63+ // Retrieve GitHub token from gh CLI
6164 ctx := context .Background ()
62- token , err := getGitHubToken (ctx )
65+ token , err := authToken (ctx )
6366 if err != nil {
6467 log .Fatalf ("Failed to get GitHub token: %v\n Please ensure 'gh' is installed and authenticated (run 'gh auth login')" , err )
6568 }
@@ -76,24 +79,26 @@ func main() {
7679 // Output in requested format
7780 switch * format {
7881 case "human" :
79- printHumanReadable (breakdown , prURL )
82+ printHumanReadable (& breakdown , prURL )
8083 case "json" :
81- printJSON (breakdown )
84+ if err := printJSON (& breakdown ); err != nil {
85+ log .Fatalf ("Failed to output results: %v" , err )
86+ }
8287 default :
8388 log .Fatalf ("Unknown format: %s (must be human or json)" , * format )
8489 }
8590}
8691
87- // getGitHubToken retrieves a GitHub token using the gh CLI.
88- func getGitHubToken (ctx context.Context ) (string , error ) {
92+ // authToken retrieves a GitHub token using the gh CLI.
93+ func authToken (ctx context.Context ) (string , error ) {
8994 ctx , cancel := context .WithTimeout (ctx , 5 * time .Second )
9095 defer cancel ()
9196
9297 cmd := exec .CommandContext (ctx , "gh" , "auth" , "token" )
9398 output , err := cmd .Output ()
9499 if err != nil {
95100 if ctx .Err () == context .DeadlineExceeded {
96- return "" , fmt . Errorf ("timeout getting auth token" )
101+ return "" , errors . New ("timeout getting auth token" )
97102 }
98103 return "" , fmt .Errorf ("failed to get auth token (is 'gh' installed and authenticated?): %w" , err )
99104 }
@@ -103,31 +108,32 @@ func getGitHubToken(ctx context.Context) (string, error) {
103108}
104109
105110// printHumanReadable outputs a detailed itemized bill in human-readable format.
106- func printHumanReadable (b cost.Breakdown , prURL string ) {
107- fmt .Printf ("PULL REQUEST COST ANALYSIS\n " )
108- fmt .Printf ("==========================\n \n " )
111+ func printHumanReadable (breakdown * cost.Breakdown , prURL string ) {
112+ fmt .Println ("PULL REQUEST COST ANALYSIS" )
113+ fmt .Println ("==========================" )
114+ fmt .Println ()
109115 fmt .Printf ("PR URL: %s\n " , prURL )
110116 fmt .Printf ("Hourly Rate: $%.2f ($%.0f salary * %.1fX total benefits multiplier)\n \n " ,
111- b .HourlyRate , b .AnnualSalary , b .BenefitsMultiplier )
117+ breakdown .HourlyRate , breakdown .AnnualSalary , breakdown .BenefitsMultiplier )
112118
113119 // Author Costs
114- fmt .Printf ("AUTHOR COSTS\n " )
120+ fmt .Println ("AUTHOR COSTS" )
115121 fmt .Printf (" Code Cost (COCOMO) $%10.2f (%d LOC, %.2f hrs)\n " ,
116- b .Author .CodeCost , b .Author .LinesAdded , b .Author .CodeHours )
122+ breakdown .Author .CodeCost , breakdown .Author .LinesAdded , breakdown .Author .CodeHours )
117123 fmt .Printf (" Code Context Switching $%10.2f (%.2f hrs)\n " ,
118- b .Author .CodeContextCost , b .Author .CodeContextHours )
124+ breakdown .Author .CodeContextCost , breakdown .Author .CodeContextHours )
119125 fmt .Printf (" GitHub Time $%10.2f (%d events, %.2f hrs)\n " ,
120- b .Author .GitHubCost , b .Author .Events , b .Author .GitHubHours )
126+ breakdown .Author .GitHubCost , breakdown .Author .Events , breakdown .Author .GitHubHours )
121127 fmt .Printf (" GitHub Context Switching $%10.2f (%d sessions, %.2f hrs)\n " ,
122- b .Author .GitHubContextCost , b .Author .Sessions , b .Author .GitHubContextHours )
123- fmt .Printf (" ---\n " )
128+ breakdown .Author .GitHubContextCost , breakdown .Author .Sessions , breakdown .Author .GitHubContextHours )
129+ fmt .Println (" ---" )
124130 fmt .Printf (" Author Subtotal $%10.2f (%.2f hrs total)\n \n " ,
125- b .Author .TotalCost , b .Author .TotalHours )
131+ breakdown .Author .TotalCost , breakdown .Author .TotalHours )
126132
127133 // Participant Costs
128- if len (b .Participants ) > 0 {
129- fmt .Printf ("PARTICIPANT COSTS\n " )
130- for _ , p := range b .Participants {
134+ if len (breakdown .Participants ) > 0 {
135+ fmt .Println ("PARTICIPANT COSTS" )
136+ for _ , p := range breakdown .Participants {
131137 fmt .Printf (" %s\n " , p .Actor )
132138 fmt .Printf (" Event Time $%10.2f (%d events, %.2f hrs)\n " ,
133139 p .GitHubCost , p .Events , p .GitHubHours )
@@ -140,53 +146,51 @@ func printHumanReadable(b cost.Breakdown, prURL string) {
140146 // Sum all participant costs
141147 var totalParticipantCost float64
142148 var totalParticipantHours float64
143- for _ , p := range b .Participants {
149+ for _ , p := range breakdown .Participants {
144150 totalParticipantCost += p .TotalCost
145151 totalParticipantHours += p .TotalHours
146152 }
147- fmt .Printf (" ---\n " )
153+ fmt .Println (" ---" )
148154 fmt .Printf (" Participants Subtotal $%10.2f (%.2f hrs total)\n \n " ,
149155 totalParticipantCost , totalParticipantHours )
150156 }
151157
152158 // Delay Cost
153- fmt .Printf ("DELAY COST\n " )
154- if b .DelayCapped {
159+ fmt .Println ("DELAY COST" )
160+ if breakdown .DelayCapped {
155161 fmt .Printf (" %-32s $%10.2f (%.0f hrs, capped at 60 days)\n " ,
156- "Project Delay (20%)" , b .DelayCostDetail .ProjectDelayCost , b .DelayCostDetail .ProjectDelayHours )
162+ "Project Delay (20%)" , breakdown .DelayCostDetail .ProjectDelayCost , breakdown .DelayCostDetail .ProjectDelayHours )
157163 } else {
158164 fmt .Printf (" %-32s $%10.2f (%.2f hrs)\n " ,
159- "Project Delay (20%)" , b .DelayCostDetail .ProjectDelayCost , b .DelayCostDetail .ProjectDelayHours )
165+ "Project Delay (20%)" , breakdown .DelayCostDetail .ProjectDelayCost , breakdown .DelayCostDetail .ProjectDelayHours )
160166 }
161167
162- if b .DelayCostDetail .ReworkPercentage > 0 {
163- label := fmt .Sprintf ("Code Updates (%.0f%% rework)" , b .DelayCostDetail .ReworkPercentage )
168+ if breakdown .DelayCostDetail .ReworkPercentage > 0 {
169+ label := fmt .Sprintf ("Code Updates (%.0f%% rework)" , breakdown .DelayCostDetail .ReworkPercentage )
164170 fmt .Printf (" %-32s $%10.2f (%.2f hrs)\n " ,
165- label , b .DelayCostDetail .CodeUpdatesCost , b .DelayCostDetail .CodeUpdatesHours )
171+ label , breakdown .DelayCostDetail .CodeUpdatesCost , breakdown .DelayCostDetail .CodeUpdatesHours )
166172 }
167173
168174 fmt .Printf (" %-32s $%10.2f (%.2f hrs)\n " ,
169- "Future GitHub (3 events)" , b .DelayCostDetail .FutureGitHubCost , b .DelayCostDetail .FutureGitHubHours )
170- fmt .Printf (" ---\n " )
175+ "Future GitHub (3 events)" , breakdown .DelayCostDetail .FutureGitHubCost , breakdown .DelayCostDetail .FutureGitHubHours )
176+ fmt .Println (" ---" )
171177
172- if b .DelayCapped {
178+ if breakdown .DelayCapped {
173179 fmt .Printf (" Total Delay Cost $%10.2f (actual: %.0f hours open)\n \n " ,
174- b .DelayCost , b .DelayHours )
180+ breakdown .DelayCost , breakdown .DelayHours )
175181 } else {
176- fmt .Printf (" Total Delay Cost $%10.2f\n \n " , b .DelayCost )
182+ fmt .Printf (" Total Delay Cost $%10.2f\n \n " , breakdown .DelayCost )
177183 }
178184
179185 // Total
180- fmt .Printf ("==========================\n " )
181- fmt .Printf ("TOTAL COST $%10.2f\n " , b .TotalCost )
182- fmt .Printf ("==========================\n " )
186+ fmt .Println ("==========================" )
187+ fmt .Printf ("TOTAL COST $%10.2f\n " , breakdown .TotalCost )
188+ fmt .Println ("==========================" )
183189}
184190
185191// printJSON outputs the cost breakdown in JSON format.
186- func printJSON (b cost.Breakdown ) {
192+ func printJSON (breakdown * cost.Breakdown ) error {
187193 encoder := json .NewEncoder (os .Stdout )
188194 encoder .SetIndent ("" , " " )
189- if err := encoder .Encode (b ); err != nil {
190- log .Fatalf ("Failed to encode JSON: %v" , err )
191- }
195+ return encoder .Encode (breakdown )
192196}
0 commit comments