Files
gh-action-readme/internal/output.go
Ismo Vuorinen f9823eef3e feat: add interactive wizard, contextual errors, and code improvements
- Add interactive configuration wizard with auto-detection and multi-format export
- Implement contextual error system with 14 error codes and actionable suggestions
- Add centralized progress indicators with consistent theming
- Fix all cyclomatic complexity issues (8 functions refactored)
- Eliminate code duplication with centralized utilities and error handling
- Add comprehensive test coverage for all new components
- Update TODO.md with completed tasks and accurate completion dates
2025-08-04 23:33:28 +03:00

249 lines
5.9 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package internal
import (
"fmt"
"os"
"strings"
"github.com/fatih/color"
"github.com/ivuorinen/gh-action-readme/internal/errors"
)
// ColoredOutput provides methods for colored terminal output.
type ColoredOutput struct {
NoColor bool
Quiet bool
}
// NewColoredOutput creates a new colored output instance.
func NewColoredOutput(quiet bool) *ColoredOutput {
return &ColoredOutput{
NoColor: color.NoColor || os.Getenv("NO_COLOR") != "",
Quiet: quiet,
}
}
// IsQuiet returns whether the output is in quiet mode.
func (co *ColoredOutput) IsQuiet() bool {
return co.Quiet
}
// Success prints a success message in green.
func (co *ColoredOutput) Success(format string, args ...any) {
if co.Quiet {
return
}
if co.NoColor {
fmt.Printf("✅ "+format+"\n", args...)
} else {
color.Green("✅ "+format, args...)
}
}
// Error prints an error message in red to stderr.
func (co *ColoredOutput) Error(format string, args ...any) {
if co.NoColor {
fmt.Fprintf(os.Stderr, "❌ "+format+"\n", args...)
} else {
_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "❌ "+format+"\n", args...)
}
}
// Warning prints a warning message in yellow.
func (co *ColoredOutput) Warning(format string, args ...any) {
if co.Quiet {
return
}
if co.NoColor {
fmt.Printf("⚠️ "+format+"\n", args...)
} else {
color.Yellow("⚠️ "+format, args...)
}
}
// Info prints an info message in blue.
func (co *ColoredOutput) Info(format string, args ...any) {
if co.Quiet {
return
}
if co.NoColor {
fmt.Printf(" "+format+"\n", args...)
} else {
color.Blue(" "+format, args...)
}
}
// Progress prints a progress message in cyan.
func (co *ColoredOutput) Progress(format string, args ...any) {
if co.Quiet {
return
}
if co.NoColor {
fmt.Printf("🔄 "+format+"\n", args...)
} else {
color.Cyan("🔄 "+format, args...)
}
}
// Bold prints text in bold.
func (co *ColoredOutput) Bold(format string, args ...any) {
if co.Quiet {
return
}
if co.NoColor {
fmt.Printf(format+"\n", args...)
} else {
_, _ = color.New(color.Bold).Printf(format+"\n", args...)
}
}
// Printf prints without color formatting (respects quiet mode).
func (co *ColoredOutput) Printf(format string, args ...any) {
if co.Quiet {
return
}
fmt.Printf(format, args...)
}
// Fprintf prints to specified writer without color formatting.
func (co *ColoredOutput) Fprintf(w *os.File, format string, args ...any) {
_, _ = fmt.Fprintf(w, format, args...)
}
// ErrorWithSuggestions prints a ContextualError with suggestions and help.
func (co *ColoredOutput) ErrorWithSuggestions(err *errors.ContextualError) {
if err == nil {
return
}
// Print main error message
if co.NoColor {
fmt.Fprintf(os.Stderr, "❌ %s\n", err.Error())
} else {
color.Red("❌ %s", err.Error())
}
}
// ErrorWithContext creates and prints a contextual error with suggestions.
func (co *ColoredOutput) ErrorWithContext(
code errors.ErrorCode,
message string,
context map[string]string,
) {
suggestions := errors.GetSuggestions(code, context)
helpURL := errors.GetHelpURL(code)
contextualErr := errors.New(code, message).
WithSuggestions(suggestions...).
WithHelpURL(helpURL)
if len(context) > 0 {
contextualErr = contextualErr.WithDetails(context)
}
co.ErrorWithSuggestions(contextualErr)
}
// ErrorWithSimpleFix prints an error with a simple suggestion.
func (co *ColoredOutput) ErrorWithSimpleFix(message, suggestion string) {
contextualErr := errors.New(errors.ErrCodeUnknown, message).
WithSuggestions(suggestion)
co.ErrorWithSuggestions(contextualErr)
}
// FormatContextualError formats a ContextualError for display.
func (co *ColoredOutput) FormatContextualError(err *errors.ContextualError) string {
if err == nil {
return ""
}
var parts []string
// Add main error message
parts = append(parts, co.formatMainError(err))
// Add details section
if len(err.Details) > 0 {
parts = append(parts, co.formatDetailsSection(err.Details)...)
}
// Add suggestions section
if len(err.Suggestions) > 0 {
parts = append(parts, co.formatSuggestionsSection(err.Suggestions)...)
}
// Add help URL section
if err.HelpURL != "" {
parts = append(parts, co.formatHelpURLSection(err.HelpURL))
}
return strings.Join(parts, "\n")
}
// formatMainError formats the main error message with code.
func (co *ColoredOutput) formatMainError(err *errors.ContextualError) string {
mainMsg := fmt.Sprintf("%s [%s]", err.Error(), err.Code)
if co.NoColor {
return "❌ " + mainMsg
}
return color.RedString("❌ ") + mainMsg
}
// formatDetailsSection formats the details section.
func (co *ColoredOutput) formatDetailsSection(details map[string]string) []string {
var parts []string
if co.NoColor {
parts = append(parts, "\nDetails:")
} else {
parts = append(parts, color.New(color.Bold).Sprint("\nDetails:"))
}
for key, value := range details {
if co.NoColor {
parts = append(parts, fmt.Sprintf(" %s: %s", key, value))
} else {
parts = append(parts, fmt.Sprintf(" %s: %s",
color.CyanString(key),
color.WhiteString(value)))
}
}
return parts
}
// formatSuggestionsSection formats the suggestions section.
func (co *ColoredOutput) formatSuggestionsSection(suggestions []string) []string {
var parts []string
if co.NoColor {
parts = append(parts, "\nSuggestions:")
} else {
parts = append(parts, color.New(color.Bold).Sprint("\nSuggestions:"))
}
for _, suggestion := range suggestions {
if co.NoColor {
parts = append(parts, fmt.Sprintf(" • %s", suggestion))
} else {
parts = append(parts, fmt.Sprintf(" %s %s",
color.YellowString("•"),
color.WhiteString(suggestion)))
}
}
return parts
}
// formatHelpURLSection formats the help URL section.
func (co *ColoredOutput) formatHelpURLSection(helpURL string) string {
if co.NoColor {
return fmt.Sprintf("\nFor more help: %s", helpURL)
}
return fmt.Sprintf("\n%s: %s",
color.New(color.Bold).Sprint("For more help"),
color.BlueString(helpURL))
}