fix: UI text wrapping

This commit is contained in:
abhisek
2025-05-15 15:26:48 +05:30
parent 7eb4fac99f
commit 72819e8a99
4 changed files with 139 additions and 31 deletions
+16 -9
View File
@@ -15,10 +15,19 @@ import (
) )
type PackageManagerGuardInteraction struct { type PackageManagerGuardInteraction struct {
SetStatus func(status string) // SetStatus is called to set the status of the guard in the UI
ClearStatus func() SetStatus func(status string)
// ClearStatus is called to clear the status of the guard in the UI
ClearStatus func()
// GetConfirmationOnMalware is called to get the confirmation of the user on the malware packages
GetConfirmationOnMalware func(malwarePackages []*analyzer.PackageVersionAnalysisResult) (bool, error) GetConfirmationOnMalware func(malwarePackages []*analyzer.PackageVersionAnalysisResult) (bool, error)
Block func() error
// Block is called to block the installation of the malware packages. One or more malicious
// packages are passed as arguments. These are the packages that were detected as malicious.
// Client code must perform the necessary error handling and termination of the process.
Block func(...*analyzer.PackageVersionAnalysisResult) error
} }
type PackageManagerGuardConfig struct { type PackageManagerGuardConfig struct {
@@ -121,8 +130,7 @@ func (g *packageManagerGuard) Run(ctx context.Context, args []string) error {
confirmableMalwarePackages := []*analyzer.PackageVersionAnalysisResult{} confirmableMalwarePackages := []*analyzer.PackageVersionAnalysisResult{}
for _, result := range analysisResults { for _, result := range analysisResults {
if result.Action == analyzer.ActionBlock { if result.Action == analyzer.ActionBlock {
_ = g.blockInstallation() return g.blockInstallation(result)
return fmt.Errorf("malicious packages detected, installation aborted")
} }
if result.Action == analyzer.ActionConfirm { if result.Action == analyzer.ActionConfirm {
@@ -137,8 +145,7 @@ func (g *packageManagerGuard) Run(ctx context.Context, args []string) error {
} }
if !confirmed { if !confirmed {
_ = g.blockInstallation() return g.blockInstallation(confirmableMalwarePackages...)
return fmt.Errorf("malicious packages detected, installation aborted")
} }
} }
@@ -234,12 +241,12 @@ func (g *packageManagerGuard) setStatus(status string) {
g.interaction.SetStatus(status) g.interaction.SetStatus(status)
} }
func (g *packageManagerGuard) blockInstallation() error { func (g *packageManagerGuard) blockInstallation(malwarePackages ...*analyzer.PackageVersionAnalysisResult) error {
if g.interaction.Block == nil { if g.interaction.Block == nil {
return nil return nil
} }
return g.interaction.Block() return g.interaction.Block(malwarePackages...)
} }
func (g *packageManagerGuard) clearStatus() { func (g *packageManagerGuard) clearStatus() {
+42 -19
View File
@@ -37,12 +37,15 @@ func ClearStatus() {
fmt.Print("\r") fmt.Print("\r")
} }
func Block() error { func Block(malwarePackages ...*analyzer.PackageVersionAnalysisResult) error {
StopSpinner() StopSpinner()
fmt.Println() fmt.Println()
fmt.Println(Colors.Red("❌ Malicious package blocked!")) fmt.Println(Colors.Red("❌ Malicious package blocked!"))
printMaliciousPackagesList(malwarePackages)
fmt.Println()
os.Exit(1) os.Exit(1)
return nil return nil
@@ -63,22 +66,7 @@ func GetConfirmationOnMalware(malwarePackages []*analyzer.PackageVersionAnalysis
fmt.Println() fmt.Println()
fmt.Println(Colors.Red(fmt.Sprintf("🚨 Suspicious package(s) detected: %d", len(malwarePackages)))) fmt.Println(Colors.Red(fmt.Sprintf("🚨 Suspicious package(s) detected: %d", len(malwarePackages))))
for _, mp := range malwarePackages { printMaliciousPackagesList(malwarePackages)
fmt.Println()
fmt.Println("⚠️ ", Colors.Red(fmt.Sprintf("%s@%s", mp.PackageVersion.GetPackage().GetName(),
mp.PackageVersion.GetVersion())))
if verbosityLevel == VerbosityLevelVerbose {
fmt.Println(Colors.Yellow(termWidthFormatText(mp.Summary, 60)))
if mp.ReferenceURL != "" {
fmt.Println()
fmt.Println(Colors.Yellow(fmt.Sprintf("Reference: %s", mp.ReferenceURL)))
}
fmt.Println()
}
}
fmt.Println() fmt.Println()
fmt.Print(Colors.Yellow("Do you want to continue with the installation? (y/N) ")) fmt.Print(Colors.Yellow("Do you want to continue with the installation? (y/N) "))
@@ -108,14 +96,44 @@ func Fatalf(msg string, args ...interface{}) {
os.Exit(1) os.Exit(1)
} }
func printMaliciousPackagesList(malwarePackages []*analyzer.PackageVersionAnalysisResult) {
for _, mp := range malwarePackages {
fmt.Println()
fmt.Println("⚠️ ", Colors.Red(fmt.Sprintf("%s@%s", mp.PackageVersion.GetPackage().GetName(),
mp.PackageVersion.GetVersion())))
if verbosityLevel == VerbosityLevelVerbose {
fmt.Println(Colors.Yellow(termWidthFormatText(mp.Summary, 80)))
if mp.ReferenceURL != "" {
fmt.Println()
fmt.Println(Colors.Yellow(fmt.Sprintf("Reference: %s", mp.ReferenceURL)))
}
}
}
}
// Format the string to be maximum maxWidth. Use newlines to wrap the text. // Format the string to be maximum maxWidth. Use newlines to wrap the text.
func termWidthFormatText(text string, maxWidth int) string { func termWidthFormatText(text string, maxWidth int) string {
// Replace all newlines with spaces so that we can split the text into words
// This is to ensure that we don't split the text at the newlines
text = strings.ReplaceAll(text, "\n", " ")
words := strings.Split(text, " ") words := strings.Split(text, " ")
lines := []string{} lines := []string{}
currentLine := "" currentLine := ""
for _, word := range words { for i, word := range words {
if len(currentLine)+len(word) > maxWidth { // Skip empty words that might result from multiple spaces
if word == "" {
continue
}
if i == 0 {
// First word doesn't need a leading space
currentLine = word
} else if len(currentLine)+len(word)+1 > maxWidth {
// +1 for the space we would add
lines = append(lines, currentLine) lines = append(lines, currentLine)
currentLine = word currentLine = word
} else { } else {
@@ -123,5 +141,10 @@ func termWidthFormatText(text string, maxWidth int) string {
} }
} }
// Don't forget to add the last line
if currentLine != "" {
lines = append(lines, currentLine)
}
return strings.Join(lines, "\n") return strings.Join(lines, "\n")
} }
+79
View File
@@ -0,0 +1,79 @@
package ui
import (
"testing"
)
// TestTermWidthFormatText is exported for testing
func TestTermWidthFormatTextFunc(t *testing.T) {
tests := []struct {
name string
text string
maxWidth int
expected string
}{
{
name: "empty string",
text: "",
maxWidth: 10,
expected: "",
},
{
name: "single word less than max width",
text: "hello",
maxWidth: 10,
expected: "hello",
},
{
name: "single word longer than max width",
text: "supercalifragilisticexpialidocious",
maxWidth: 10,
expected: "supercalifragilisticexpialidocious",
},
{
name: "multiple words on single line",
text: "hello world",
maxWidth: 20,
expected: "hello world",
},
{
name: "multiple words wrapped to multiple lines",
text: "The quick brown fox jumps over the lazy dog",
maxWidth: 20,
expected: "The quick brown fox\njumps over the lazy\ndog",
},
{
name: "text with existing newlines",
text: "hello\nworld",
maxWidth: 20,
expected: "hello world",
},
{
name: "text with multiple spaces",
text: "hello world test",
maxWidth: 20,
expected: "hello world test",
},
{
name: "very small max width",
text: "hello world",
maxWidth: 3,
expected: "hello\nworld",
},
{
name: "large max width",
text: "The quick brown fox jumps over the lazy dog",
maxWidth: 100,
expected: "The quick brown fox jumps over the lazy dog",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := termWidthFormatText(tt.text, tt.maxWidth)
if result != tt.expected {
t.Errorf("termWidthFormatText() = %q, want %q", result, tt.expected)
}
})
}
}
+2 -3
View File
@@ -36,9 +36,8 @@ func main() {
os.Setenv("APP_LOG_LEVEL", "debug") os.Setenv("APP_LOG_LEVEL", "debug")
} }
// Skip stdout logging when debugging and verbose is not enabled // Skip stdout logging when debugging is not enabled
// This is default behavior for the CLI unless explicitly set if !debug {
if !debug && !verbose {
os.Setenv("APP_LOG_SKIP_STDOUT_LOGGER", "true") os.Setenv("APP_LOG_SKIP_STDOUT_LOGGER", "true")
} }