ci: add golangci-lint config and fix formatting
- Add .golangci.yml with minimal linters (govet, ineffassign) - Run gofmt -s and goimports on all files to fix formatting - Disable fieldalignment and copylocks checks in govet
This commit is contained in:
@@ -17,32 +17,32 @@ type DetailedReporter struct {
|
||||
|
||||
// OperationStatus represents the status of a backup/restore operation
|
||||
type OperationStatus struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // "backup", "restore", "verify"
|
||||
Status string `json:"status"` // "running", "completed", "failed"
|
||||
StartTime time.Time `json:"start_time"`
|
||||
EndTime *time.Time `json:"end_time,omitempty"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
Progress int `json:"progress"` // 0-100
|
||||
Message string `json:"message"`
|
||||
Details map[string]string `json:"details"`
|
||||
Steps []StepStatus `json:"steps"`
|
||||
BytesTotal int64 `json:"bytes_total"`
|
||||
BytesDone int64 `json:"bytes_done"`
|
||||
FilesTotal int `json:"files_total"`
|
||||
FilesDone int `json:"files_done"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // "backup", "restore", "verify"
|
||||
Status string `json:"status"` // "running", "completed", "failed"
|
||||
StartTime time.Time `json:"start_time"`
|
||||
EndTime *time.Time `json:"end_time,omitempty"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
Progress int `json:"progress"` // 0-100
|
||||
Message string `json:"message"`
|
||||
Details map[string]string `json:"details"`
|
||||
Steps []StepStatus `json:"steps"`
|
||||
BytesTotal int64 `json:"bytes_total"`
|
||||
BytesDone int64 `json:"bytes_done"`
|
||||
FilesTotal int `json:"files_total"`
|
||||
FilesDone int `json:"files_done"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
// StepStatus represents individual steps within an operation
|
||||
type StepStatus struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
EndTime *time.Time `json:"end_time,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
EndTime *time.Time `json:"end_time,omitempty"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
Message string `json:"message"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Logger interface for detailed reporting
|
||||
@@ -79,7 +79,7 @@ func (dr *DetailedReporter) StartOperation(id, name, opType string) *OperationTr
|
||||
}
|
||||
|
||||
dr.operations = append(dr.operations, operation)
|
||||
|
||||
|
||||
if dr.startTime.IsZero() {
|
||||
dr.startTime = time.Now()
|
||||
}
|
||||
@@ -90,9 +90,9 @@ func (dr *DetailedReporter) StartOperation(id, name, opType string) *OperationTr
|
||||
}
|
||||
|
||||
// Log operation start
|
||||
dr.logger.Info("Operation started",
|
||||
"id", id,
|
||||
"name", name,
|
||||
dr.logger.Info("Operation started",
|
||||
"id", id,
|
||||
"name", name,
|
||||
"type", opType,
|
||||
"timestamp", operation.StartTime.Format(time.RFC3339))
|
||||
|
||||
@@ -117,7 +117,7 @@ func (ot *OperationTracker) UpdateProgress(progress int, message string) {
|
||||
if ot.reporter.operations[i].ID == ot.operationID {
|
||||
ot.reporter.operations[i].Progress = progress
|
||||
ot.reporter.operations[i].Message = message
|
||||
|
||||
|
||||
// Update visual indicator
|
||||
if ot.reporter.indicator != nil {
|
||||
progressMsg := fmt.Sprintf("[%d%%] %s", progress, message)
|
||||
@@ -150,7 +150,7 @@ func (ot *OperationTracker) AddStep(name, message string) *StepTracker {
|
||||
for i := range ot.reporter.operations {
|
||||
if ot.reporter.operations[i].ID == ot.operationID {
|
||||
ot.reporter.operations[i].Steps = append(ot.reporter.operations[i].Steps, step)
|
||||
|
||||
|
||||
// Log step start
|
||||
ot.reporter.logger.Info("Step started",
|
||||
"operation_id", ot.operationID,
|
||||
@@ -190,7 +190,7 @@ func (ot *OperationTracker) SetFileProgress(filesDone, filesTotal int) {
|
||||
if ot.reporter.operations[i].ID == ot.operationID {
|
||||
ot.reporter.operations[i].FilesDone = filesDone
|
||||
ot.reporter.operations[i].FilesTotal = filesTotal
|
||||
|
||||
|
||||
if filesTotal > 0 {
|
||||
progress := (filesDone * 100) / filesTotal
|
||||
ot.reporter.operations[i].Progress = progress
|
||||
@@ -209,25 +209,25 @@ func (ot *OperationTracker) SetByteProgress(bytesDone, bytesTotal int64) {
|
||||
if ot.reporter.operations[i].ID == ot.operationID {
|
||||
ot.reporter.operations[i].BytesDone = bytesDone
|
||||
ot.reporter.operations[i].BytesTotal = bytesTotal
|
||||
|
||||
|
||||
if bytesTotal > 0 {
|
||||
progress := int((bytesDone * 100) / bytesTotal)
|
||||
ot.reporter.operations[i].Progress = progress
|
||||
|
||||
|
||||
// Calculate ETA and speed
|
||||
elapsed := time.Since(ot.reporter.operations[i].StartTime).Seconds()
|
||||
if elapsed > 0 && bytesDone > 0 {
|
||||
speed := float64(bytesDone) / elapsed // bytes/sec
|
||||
remaining := bytesTotal - bytesDone
|
||||
eta := time.Duration(float64(remaining)/speed) * time.Second
|
||||
|
||||
|
||||
// Update progress message with ETA and speed
|
||||
if ot.reporter.indicator != nil {
|
||||
speedStr := formatSpeed(int64(speed))
|
||||
etaStr := formatDuration(eta)
|
||||
progressMsg := fmt.Sprintf("[%d%%] %s / %s (%s/s, ETA: %s)",
|
||||
progress,
|
||||
formatBytes(bytesDone),
|
||||
progressMsg := fmt.Sprintf("[%d%%] %s / %s (%s/s, ETA: %s)",
|
||||
progress,
|
||||
formatBytes(bytesDone),
|
||||
formatBytes(bytesTotal),
|
||||
speedStr,
|
||||
etaStr)
|
||||
@@ -253,7 +253,7 @@ func (ot *OperationTracker) Complete(message string) {
|
||||
ot.reporter.operations[i].EndTime = &now
|
||||
ot.reporter.operations[i].Duration = now.Sub(ot.reporter.operations[i].StartTime)
|
||||
ot.reporter.operations[i].Message = message
|
||||
|
||||
|
||||
// Complete visual indicator
|
||||
if ot.reporter.indicator != nil {
|
||||
ot.reporter.indicator.Complete(fmt.Sprintf("✅ %s", message))
|
||||
@@ -283,7 +283,7 @@ func (ot *OperationTracker) Fail(err error) {
|
||||
ot.reporter.operations[i].Duration = now.Sub(ot.reporter.operations[i].StartTime)
|
||||
ot.reporter.operations[i].Message = err.Error()
|
||||
ot.reporter.operations[i].Errors = append(ot.reporter.operations[i].Errors, err.Error())
|
||||
|
||||
|
||||
// Fail visual indicator
|
||||
if ot.reporter.indicator != nil {
|
||||
ot.reporter.indicator.Fail(fmt.Sprintf("❌ %s", err.Error()))
|
||||
@@ -321,7 +321,7 @@ func (st *StepTracker) Complete(message string) {
|
||||
st.reporter.operations[i].Steps[j].EndTime = &now
|
||||
st.reporter.operations[i].Steps[j].Duration = now.Sub(st.reporter.operations[i].Steps[j].StartTime)
|
||||
st.reporter.operations[i].Steps[j].Message = message
|
||||
|
||||
|
||||
// Log step completion
|
||||
st.reporter.logger.Info("Step completed",
|
||||
"operation_id", st.operationID,
|
||||
@@ -351,7 +351,7 @@ func (st *StepTracker) Fail(err error) {
|
||||
st.reporter.operations[i].Steps[j].EndTime = &now
|
||||
st.reporter.operations[i].Steps[j].Duration = now.Sub(st.reporter.operations[i].Steps[j].StartTime)
|
||||
st.reporter.operations[i].Steps[j].Message = err.Error()
|
||||
|
||||
|
||||
// Log step failure
|
||||
st.reporter.logger.Error("Step failed",
|
||||
"operation_id", st.operationID,
|
||||
@@ -428,8 +428,8 @@ type OperationSummary struct {
|
||||
func (os *OperationSummary) FormatSummary() string {
|
||||
return fmt.Sprintf(
|
||||
"📊 Operations Summary:\n"+
|
||||
" Total: %d | Completed: %d | Failed: %d | Running: %d\n"+
|
||||
" Total Duration: %s",
|
||||
" Total: %d | Completed: %d | Failed: %d | Running: %d\n"+
|
||||
" Total Duration: %s",
|
||||
os.TotalOperations,
|
||||
os.CompletedOperations,
|
||||
os.FailedOperations,
|
||||
@@ -461,7 +461,7 @@ func formatBytes(bytes int64) string {
|
||||
GB = 1024 * MB
|
||||
TB = 1024 * GB
|
||||
)
|
||||
|
||||
|
||||
switch {
|
||||
case bytes >= TB:
|
||||
return fmt.Sprintf("%.2f TB", float64(bytes)/float64(TB))
|
||||
@@ -483,7 +483,7 @@ func formatSpeed(bytesPerSec int64) string {
|
||||
MB = 1024 * KB
|
||||
GB = 1024 * MB
|
||||
)
|
||||
|
||||
|
||||
switch {
|
||||
case bytesPerSec >= GB:
|
||||
return fmt.Sprintf("%.2f GB", float64(bytesPerSec)/float64(GB))
|
||||
@@ -494,4 +494,4 @@ func formatSpeed(bytesPerSec int64) string {
|
||||
default:
|
||||
return fmt.Sprintf("%d B", bytesPerSec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@ func (e *ETAEstimator) GetETA() time.Duration {
|
||||
if e.itemsComplete == 0 || e.totalItems == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
elapsed := e.GetElapsed()
|
||||
avgTimePerItem := elapsed / time.Duration(e.itemsComplete)
|
||||
remainingItems := e.totalItems - e.itemsComplete
|
||||
|
||||
|
||||
return avgTimePerItem * time.Duration(remainingItems)
|
||||
}
|
||||
|
||||
@@ -83,12 +83,12 @@ func (e *ETAEstimator) GetFullStatus(baseMessage string) string {
|
||||
// No items to track, just show elapsed
|
||||
return fmt.Sprintf("%s | Elapsed: %s", baseMessage, e.FormatElapsed())
|
||||
}
|
||||
|
||||
|
||||
if e.itemsComplete == 0 {
|
||||
// Just started
|
||||
return fmt.Sprintf("%s | 0/%d | Starting...", baseMessage, e.totalItems)
|
||||
}
|
||||
|
||||
|
||||
// Full status with progress and ETA
|
||||
return fmt.Sprintf("%s | %s | Elapsed: %s | ETA: %s",
|
||||
baseMessage,
|
||||
@@ -102,44 +102,44 @@ func FormatDuration(d time.Duration) string {
|
||||
if d < time.Second {
|
||||
return "< 1s"
|
||||
}
|
||||
|
||||
|
||||
hours := int(d.Hours())
|
||||
minutes := int(d.Minutes()) % 60
|
||||
seconds := int(d.Seconds()) % 60
|
||||
|
||||
|
||||
if hours > 0 {
|
||||
if minutes > 0 {
|
||||
return fmt.Sprintf("%dh %dm", hours, minutes)
|
||||
}
|
||||
return fmt.Sprintf("%dh", hours)
|
||||
}
|
||||
|
||||
|
||||
if minutes > 0 {
|
||||
if seconds > 5 { // Only show seconds if > 5
|
||||
return fmt.Sprintf("%dm %ds", minutes, seconds)
|
||||
}
|
||||
return fmt.Sprintf("%dm", minutes)
|
||||
}
|
||||
|
||||
|
||||
return fmt.Sprintf("%ds", seconds)
|
||||
}
|
||||
|
||||
// EstimateSizeBasedDuration estimates duration based on size (fallback when no progress tracking)
|
||||
func EstimateSizeBasedDuration(sizeBytes int64, cores int) time.Duration {
|
||||
sizeMB := float64(sizeBytes) / (1024 * 1024)
|
||||
|
||||
|
||||
// Base estimate: ~100MB per minute on average hardware
|
||||
baseMinutes := sizeMB / 100.0
|
||||
|
||||
|
||||
// Adjust for CPU cores (more cores = faster, but not linear)
|
||||
// Use square root to represent diminishing returns
|
||||
if cores > 1 {
|
||||
speedup := 1.0 + (0.3 * (float64(cores) - 1)) // 30% improvement per core
|
||||
baseMinutes = baseMinutes / speedup
|
||||
}
|
||||
|
||||
|
||||
// Add 20% buffer for safety
|
||||
baseMinutes = baseMinutes * 1.2
|
||||
|
||||
|
||||
return time.Duration(baseMinutes * float64(time.Minute))
|
||||
}
|
||||
|
||||
@@ -7,19 +7,19 @@ import (
|
||||
|
||||
func TestNewETAEstimator(t *testing.T) {
|
||||
estimator := NewETAEstimator("Test Operation", 10)
|
||||
|
||||
|
||||
if estimator.operation != "Test Operation" {
|
||||
t.Errorf("Expected operation 'Test Operation', got '%s'", estimator.operation)
|
||||
}
|
||||
|
||||
|
||||
if estimator.totalItems != 10 {
|
||||
t.Errorf("Expected totalItems 10, got %d", estimator.totalItems)
|
||||
}
|
||||
|
||||
|
||||
if estimator.itemsComplete != 0 {
|
||||
t.Errorf("Expected itemsComplete 0, got %d", estimator.itemsComplete)
|
||||
}
|
||||
|
||||
|
||||
if estimator.startTime.IsZero() {
|
||||
t.Error("Expected startTime to be set")
|
||||
}
|
||||
@@ -27,12 +27,12 @@ func TestNewETAEstimator(t *testing.T) {
|
||||
|
||||
func TestUpdateProgress(t *testing.T) {
|
||||
estimator := NewETAEstimator("Test", 10)
|
||||
|
||||
|
||||
estimator.UpdateProgress(5)
|
||||
if estimator.itemsComplete != 5 {
|
||||
t.Errorf("Expected itemsComplete 5, got %d", estimator.itemsComplete)
|
||||
}
|
||||
|
||||
|
||||
estimator.UpdateProgress(8)
|
||||
if estimator.itemsComplete != 8 {
|
||||
t.Errorf("Expected itemsComplete 8, got %d", estimator.itemsComplete)
|
||||
@@ -41,24 +41,24 @@ func TestUpdateProgress(t *testing.T) {
|
||||
|
||||
func TestGetProgress(t *testing.T) {
|
||||
estimator := NewETAEstimator("Test", 10)
|
||||
|
||||
|
||||
// Test 0% progress
|
||||
if progress := estimator.GetProgress(); progress != 0 {
|
||||
t.Errorf("Expected 0%%, got %.2f%%", progress)
|
||||
}
|
||||
|
||||
|
||||
// Test 50% progress
|
||||
estimator.UpdateProgress(5)
|
||||
if progress := estimator.GetProgress(); progress != 50.0 {
|
||||
t.Errorf("Expected 50%%, got %.2f%%", progress)
|
||||
}
|
||||
|
||||
|
||||
// Test 100% progress
|
||||
estimator.UpdateProgress(10)
|
||||
if progress := estimator.GetProgress(); progress != 100.0 {
|
||||
t.Errorf("Expected 100%%, got %.2f%%", progress)
|
||||
}
|
||||
|
||||
|
||||
// Test zero division
|
||||
zeroEstimator := NewETAEstimator("Test", 0)
|
||||
if progress := zeroEstimator.GetProgress(); progress != 0 {
|
||||
@@ -68,10 +68,10 @@ func TestGetProgress(t *testing.T) {
|
||||
|
||||
func TestGetElapsed(t *testing.T) {
|
||||
estimator := NewETAEstimator("Test", 10)
|
||||
|
||||
|
||||
// Wait a bit
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
|
||||
elapsed := estimator.GetElapsed()
|
||||
if elapsed < 100*time.Millisecond {
|
||||
t.Errorf("Expected elapsed time >= 100ms, got %v", elapsed)
|
||||
@@ -80,16 +80,16 @@ func TestGetElapsed(t *testing.T) {
|
||||
|
||||
func TestGetETA(t *testing.T) {
|
||||
estimator := NewETAEstimator("Test", 10)
|
||||
|
||||
|
||||
// No progress yet, ETA should be 0
|
||||
if eta := estimator.GetETA(); eta != 0 {
|
||||
t.Errorf("Expected ETA 0 for no progress, got %v", eta)
|
||||
}
|
||||
|
||||
|
||||
// Simulate 5 items completed in 5 seconds
|
||||
estimator.startTime = time.Now().Add(-5 * time.Second)
|
||||
estimator.UpdateProgress(5)
|
||||
|
||||
|
||||
eta := estimator.GetETA()
|
||||
// Should be approximately 5 seconds (5 items remaining at 1 sec/item)
|
||||
if eta < 4*time.Second || eta > 6*time.Second {
|
||||
@@ -99,18 +99,18 @@ func TestGetETA(t *testing.T) {
|
||||
|
||||
func TestFormatProgress(t *testing.T) {
|
||||
estimator := NewETAEstimator("Test", 13)
|
||||
|
||||
|
||||
// Test at 0%
|
||||
if result := estimator.FormatProgress(); result != "0/13 (0%)" {
|
||||
t.Errorf("Expected '0/13 (0%%)', got '%s'", result)
|
||||
}
|
||||
|
||||
|
||||
// Test at 38%
|
||||
estimator.UpdateProgress(5)
|
||||
if result := estimator.FormatProgress(); result != "5/13 (38%)" {
|
||||
t.Errorf("Expected '5/13 (38%%)', got '%s'", result)
|
||||
}
|
||||
|
||||
|
||||
// Test at 100%
|
||||
estimator.UpdateProgress(13)
|
||||
if result := estimator.FormatProgress(); result != "13/13 (100%)" {
|
||||
@@ -125,16 +125,16 @@ func TestFormatDuration(t *testing.T) {
|
||||
}{
|
||||
{500 * time.Millisecond, "< 1s"},
|
||||
{5 * time.Second, "5s"},
|
||||
{65 * time.Second, "1m"}, // 5 seconds not shown (<=5)
|
||||
{125 * time.Second, "2m"}, // 5 seconds not shown (<=5)
|
||||
{65 * time.Second, "1m"}, // 5 seconds not shown (<=5)
|
||||
{125 * time.Second, "2m"}, // 5 seconds not shown (<=5)
|
||||
{3 * time.Minute, "3m"},
|
||||
{3*time.Minute + 3*time.Second, "3m"}, // < 5 seconds not shown
|
||||
{3*time.Minute + 10*time.Second, "3m 10s"}, // > 5 seconds shown
|
||||
{3*time.Minute + 3*time.Second, "3m"}, // < 5 seconds not shown
|
||||
{3*time.Minute + 10*time.Second, "3m 10s"}, // > 5 seconds shown
|
||||
{90 * time.Minute, "1h 30m"},
|
||||
{120 * time.Minute, "2h"},
|
||||
{150 * time.Minute, "2h 30m"},
|
||||
}
|
||||
|
||||
|
||||
for _, tt := range tests {
|
||||
result := FormatDuration(tt.duration)
|
||||
if result != tt.expected {
|
||||
@@ -145,16 +145,16 @@ func TestFormatDuration(t *testing.T) {
|
||||
|
||||
func TestFormatETA(t *testing.T) {
|
||||
estimator := NewETAEstimator("Test", 10)
|
||||
|
||||
|
||||
// No progress - should show "calculating..."
|
||||
if result := estimator.FormatETA(); result != "calculating..." {
|
||||
t.Errorf("Expected 'calculating...', got '%s'", result)
|
||||
}
|
||||
|
||||
|
||||
// With progress
|
||||
estimator.startTime = time.Now().Add(-10 * time.Second)
|
||||
estimator.UpdateProgress(5)
|
||||
|
||||
|
||||
result := estimator.FormatETA()
|
||||
if result != "~10s remaining" {
|
||||
t.Errorf("Expected '~10s remaining', got '%s'", result)
|
||||
@@ -164,7 +164,7 @@ func TestFormatETA(t *testing.T) {
|
||||
func TestFormatElapsed(t *testing.T) {
|
||||
estimator := NewETAEstimator("Test", 10)
|
||||
estimator.startTime = time.Now().Add(-45 * time.Second)
|
||||
|
||||
|
||||
result := estimator.FormatElapsed()
|
||||
if result != "45s" {
|
||||
t.Errorf("Expected '45s', got '%s'", result)
|
||||
@@ -173,23 +173,23 @@ func TestFormatElapsed(t *testing.T) {
|
||||
|
||||
func TestGetFullStatus(t *testing.T) {
|
||||
estimator := NewETAEstimator("Backing up cluster", 13)
|
||||
|
||||
|
||||
// Just started (0 items)
|
||||
result := estimator.GetFullStatus("Backing up cluster")
|
||||
if result != "Backing up cluster | 0/13 | Starting..." {
|
||||
t.Errorf("Unexpected result for 0 items: '%s'", result)
|
||||
}
|
||||
|
||||
|
||||
// With progress
|
||||
estimator.startTime = time.Now().Add(-30 * time.Second)
|
||||
estimator.UpdateProgress(5)
|
||||
|
||||
|
||||
result = estimator.GetFullStatus("Backing up cluster")
|
||||
// Should contain all components
|
||||
if len(result) < 50 { // Reasonable minimum length
|
||||
t.Errorf("Result too short: '%s'", result)
|
||||
}
|
||||
|
||||
|
||||
// Check it contains key elements (format may vary slightly)
|
||||
if !contains(result, "5/13") {
|
||||
t.Errorf("Result missing progress '5/13': '%s'", result)
|
||||
@@ -208,7 +208,7 @@ func TestGetFullStatus(t *testing.T) {
|
||||
func TestGetFullStatusWithZeroItems(t *testing.T) {
|
||||
estimator := NewETAEstimator("Test Operation", 0)
|
||||
estimator.startTime = time.Now().Add(-5 * time.Second)
|
||||
|
||||
|
||||
result := estimator.GetFullStatus("Test Operation")
|
||||
// Should only show elapsed time when no items to track
|
||||
if !contains(result, "Test Operation") || !contains(result, "Elapsed:") {
|
||||
@@ -226,13 +226,13 @@ func TestEstimateSizeBasedDuration(t *testing.T) {
|
||||
if duration < 60*time.Second || duration > 90*time.Second {
|
||||
t.Errorf("Expected ~1.2 minutes for 100MB/1core, got %v", duration)
|
||||
}
|
||||
|
||||
|
||||
// Test 100MB with 8 cores (should be faster)
|
||||
duration8cores := EstimateSizeBasedDuration(100*1024*1024, 8)
|
||||
if duration8cores >= duration {
|
||||
t.Errorf("Expected faster with more cores: %v vs %v", duration8cores, duration)
|
||||
}
|
||||
|
||||
|
||||
// Test larger file
|
||||
duration1GB := EstimateSizeBasedDuration(1024*1024*1024, 1)
|
||||
if duration1GB <= duration {
|
||||
@@ -242,9 +242,8 @@ func TestEstimateSizeBasedDuration(t *testing.T) {
|
||||
|
||||
// Helper function
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr ||
|
||||
len(s) > len(substr) && (
|
||||
s[:len(substr)] == substr ||
|
||||
return len(s) >= len(substr) && (s == substr ||
|
||||
len(s) > len(substr) && (s[:len(substr)] == substr ||
|
||||
s[len(s)-len(substr):] == substr ||
|
||||
indexHelper(s, substr) >= 0))
|
||||
}
|
||||
|
||||
@@ -43,11 +43,11 @@ func NewSpinner() *Spinner {
|
||||
func (s *Spinner) Start(message string) {
|
||||
s.message = message
|
||||
s.active = true
|
||||
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(s.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
|
||||
i := 0
|
||||
lastMessage := ""
|
||||
for {
|
||||
@@ -57,12 +57,12 @@ func (s *Spinner) Start(message string) {
|
||||
case <-ticker.C:
|
||||
if s.active {
|
||||
displayMsg := s.message
|
||||
|
||||
|
||||
// Add ETA info if estimator is available
|
||||
if s.estimator != nil {
|
||||
displayMsg = s.estimator.GetFullStatus(s.message)
|
||||
}
|
||||
|
||||
|
||||
currentFrame := fmt.Sprintf("%s %s", s.frames[i%len(s.frames)], displayMsg)
|
||||
if s.message != lastMessage {
|
||||
// Print new line for new messages
|
||||
@@ -130,13 +130,13 @@ func NewDots() *Dots {
|
||||
func (d *Dots) Start(message string) {
|
||||
d.message = message
|
||||
d.active = true
|
||||
|
||||
|
||||
fmt.Fprint(d.writer, message)
|
||||
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
|
||||
count := 0
|
||||
for {
|
||||
select {
|
||||
@@ -191,13 +191,13 @@ func (d *Dots) SetEstimator(estimator *ETAEstimator) {
|
||||
|
||||
// ProgressBar creates a visual progress bar
|
||||
type ProgressBar struct {
|
||||
writer io.Writer
|
||||
message string
|
||||
total int
|
||||
current int
|
||||
width int
|
||||
active bool
|
||||
stopCh chan bool
|
||||
writer io.Writer
|
||||
message string
|
||||
total int
|
||||
current int
|
||||
width int
|
||||
active bool
|
||||
stopCh chan bool
|
||||
}
|
||||
|
||||
// NewProgressBar creates a new progress bar
|
||||
@@ -265,12 +265,12 @@ func (p *ProgressBar) render() {
|
||||
if !p.active {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
percent := float64(p.current) / float64(p.total)
|
||||
filled := int(percent * float64(p.width))
|
||||
|
||||
|
||||
bar := strings.Repeat("█", filled) + strings.Repeat("░", p.width-filled)
|
||||
|
||||
|
||||
fmt.Fprintf(p.writer, "\n%s [%s] %d%%", p.message, bar, int(percent*100))
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ func NewIndicator(interactive bool, indicatorType string) Indicator {
|
||||
if !interactive {
|
||||
return NewLineByLine() // Use line-by-line for non-interactive mode
|
||||
}
|
||||
|
||||
|
||||
switch indicatorType {
|
||||
case "spinner":
|
||||
return NewSpinner()
|
||||
@@ -457,9 +457,9 @@ func NewNullIndicator() *NullIndicator {
|
||||
return &NullIndicator{}
|
||||
}
|
||||
|
||||
func (n *NullIndicator) Start(message string) {}
|
||||
func (n *NullIndicator) Update(message string) {}
|
||||
func (n *NullIndicator) Complete(message string) {}
|
||||
func (n *NullIndicator) Fail(message string) {}
|
||||
func (n *NullIndicator) Stop() {}
|
||||
func (n *NullIndicator) Start(message string) {}
|
||||
func (n *NullIndicator) Update(message string) {}
|
||||
func (n *NullIndicator) Complete(message string) {}
|
||||
func (n *NullIndicator) Fail(message string) {}
|
||||
func (n *NullIndicator) Stop() {}
|
||||
func (n *NullIndicator) SetEstimator(estimator *ETAEstimator) {}
|
||||
|
||||
Reference in New Issue
Block a user