dharma/pkg/reporter/reporter.go
Felipe M. 0ef15167d5
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
initial release
2025-05-04 10:49:50 +02:00

192 lines
4.6 KiB
Go

package reporter
import (
"encoding/csv"
"encoding/json"
"fmt"
"io"
"strings"
"time"
"git.nakama.town/fmartingr/dharma/pkg/scraper"
"github.com/fatih/color"
)
// Reporter is an interface for report generators
type Reporter interface {
Generate(results *scraper.Results, writer io.Writer) error
}
// New creates a new reporter based on the format
func New(format string) (Reporter, error) {
switch strings.ToLower(format) {
case "pretty":
return &PrettyReporter{}, nil
case "json":
return &JSONReporter{}, nil
case "csv":
return &CSVReporter{}, nil
default:
return nil, fmt.Errorf("unsupported format: %s", format)
}
}
// PrettyReporter generates a human-readable report for terminal
type PrettyReporter struct{}
// Generate generates a pretty report
func (r *PrettyReporter) Generate(results *scraper.Results, writer io.Writer) error {
red := color.New(color.FgRed).SprintFunc()
green := color.New(color.FgGreen).SprintFunc()
yellow := color.New(color.FgYellow).SprintFunc()
blue := color.New(color.FgBlue).SprintFunc()
cyan := color.New(color.FgCyan).SprintFunc()
// Count internal vs external links
countInternalSuccess := 0
countInternalErrors := 0
countExternalSuccess := 0
countExternalErrors := 0
for _, result := range results.Successes {
if result.IsExternal {
countExternalSuccess++
} else {
countInternalSuccess++
}
}
for _, result := range results.Errors {
if result.IsExternal {
countExternalErrors++
} else {
countInternalErrors++
}
}
fmt.Fprintf(writer, "Website scan report for: %s\n", blue(results.BaseURL))
fmt.Fprintf(writer, "Scanned at: %s\n", time.Now().Format(time.RFC1123))
fmt.Fprintf(writer, "Total resources checked: %d\n", results.Total)
fmt.Fprintf(writer, "Success: %s, Errors: %s\n",
green(len(results.Successes)),
red(len(results.Errors)))
fmt.Fprintf(writer, "Internal links: %s success, %s errors\n",
green(countInternalSuccess),
red(countInternalErrors))
fmt.Fprintf(writer, "External links: %s success, %s errors\n\n",
green(countExternalSuccess),
red(countExternalErrors))
if len(results.Errors) == 0 {
fmt.Fprintf(writer, "%s No errors found!\n", green("✓"))
return nil
}
// Group errors by internal/external
internalErrors := []scraper.Result{}
externalErrors := []scraper.Result{}
for _, result := range results.Errors {
if result.IsExternal {
externalErrors = append(externalErrors, result)
} else {
internalErrors = append(internalErrors, result)
}
}
// Print internal errors first if we have any
if len(internalErrors) > 0 {
fmt.Fprintln(writer, "Errors found:")
for _, result := range internalErrors {
status := fmt.Sprintf("%d", result.Status)
if result.Status == 0 {
status = "ERR"
}
fmt.Fprintf(writer, "%-6s (%-10s) %s [from: %s]\n",
red(status),
yellow(result.Type),
result.URL,
result.SourceURL,
)
}
}
// Print external errors if we have any
if len(externalErrors) > 0 {
if len(internalErrors) > 0 {
fmt.Fprintln(writer, "")
}
fmt.Fprintln(writer, "External Errors:")
fmt.Fprintln(writer, strings.Repeat("-", 80))
fmt.Fprintf(writer, "%-6s | %-10s | %s | %s\n", "Status", "Type", "URL", "Source")
fmt.Fprintln(writer, strings.Repeat("-", 80))
for _, result := range externalErrors {
status := fmt.Sprintf("%d", result.Status)
if result.Status == 0 {
status = "ERR"
}
fmt.Fprintf(writer, "%-6s | %-10s | %s | %s\n",
red(status),
cyan(result.Type),
result.URL,
result.SourceURL,
)
}
}
return nil
}
// JSONReporter generates a JSON report
type JSONReporter struct{}
// Generate generates a JSON report
func (r *JSONReporter) Generate(results *scraper.Results, writer io.Writer) error {
return json.NewEncoder(writer).Encode(results)
}
// CSVReporter generates a CSV report
type CSVReporter struct{}
// Generate generates a CSV report
func (r *CSVReporter) Generate(results *scraper.Results, writer io.Writer) error {
csvWriter := csv.NewWriter(writer)
defer csvWriter.Flush()
// Write header
if err := csvWriter.Write([]string{"Status", "Type", "URL", "Source URL", "Error"}); err != nil {
return err
}
// Write errors
for _, result := range results.Errors {
status := fmt.Sprintf("%d", result.Status)
if result.Status == 0 {
status = "ERROR"
}
if err := csvWriter.Write([]string{
status,
result.Type,
result.URL,
result.SourceURL,
result.Error,
}); err != nil {
return err
}
}
return nil
}
// Helper function to truncate strings
func truncate(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen-3] + "..."
}