mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
fix: UI text wrapping
This commit is contained in:
+16
-9
@@ -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
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user