Codebase list golang-github-vbauerster-mpb / 19f0fe8 decor / decorators.go
19f0fe8

Tree @19f0fe8 (Download .tar.gz)

decorators.go @19f0fe8raw · history · blame

package decor

import (
	"fmt"
	"math"
	"time"
	"unicode/utf8"
)

const (
	// DidentRight bit specifies identation direction.
	// |foo   |b     | With DidentRight
	// |   foo|     b| Without DidentRight
	DidentRight = 1 << iota

	// DextraSpace bit adds extra space, makes sense with DSyncWidth only.
	// When DidentRight bit set, the space will be added to the right,
	// otherwise to the left.
	DextraSpace

	// DSyncWidth bit enables same column width synchronization.
	// Effective with multiple bars only.
	DSyncWidth

	// DSyncWidthR is shortcut for DSyncWidth|DidentRight
	DSyncWidthR = DSyncWidth | DidentRight

	// DSyncSpace is shortcut for DSyncWidth|DextraSpace
	DSyncSpace = DSyncWidth | DextraSpace

	// DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DidentRight
	DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight
)

const (
	ET_STYLE_GO = iota
	ET_STYLE_HHMMSS
	ET_STYLE_HHMM
	ET_STYLE_MMSS
)

// Statistics is a struct, which gets passed to a Decorator.
type Statistics struct {
	ID          int
	Completed   bool
	Total       int64
	Current     int64
	StartTime   time.Time
	TimeElapsed time.Duration
}

// Decorator is an interface with one method:
//
//	Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string
//
// All decorators in this package implement this interface.
type Decorator interface {
	Decor(*Statistics, chan<- int, <-chan int) string
}

// OnCompleteMessenger is an interface with one method:
//
//	OnCompleteMessage(message string, wc ...WC)
//
// Decorators implementing this interface suppose to return provided string on complete event.
type OnCompleteMessenger interface {
	OnCompleteMessage(string, ...WC)
}

type AmountReceiver interface {
	NextAmount(int)
}

type ShutdownListener interface {
	Shutdown()
}

// DecoratorFunc is an adapter for Decorator interface
type DecoratorFunc func(*Statistics, chan<- int, <-chan int) string

func (f DecoratorFunc) Decor(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string {
	return f(s, widthAccumulator, widthDistributor)
}

// WC is a struct with two public fields W and C, both of int type.
// W represents width and C represents bit set of width related config.
type WC struct {
	W      int
	C      int
	format string
}

func (wc WC) formatMsg(msg string, widthAccumulator chan<- int, widthDistributor <-chan int) string {
	format := wc.buildFormat()
	if (wc.C & DSyncWidth) != 0 {
		widthAccumulator <- utf8.RuneCountInString(msg)
		max := <-widthDistributor
		if max == 0 {
			max = wc.W
		}
		if (wc.C & DextraSpace) != 0 {
			max++
		}
		return fmt.Sprintf(fmt.Sprintf(format, max), msg)
	}
	return fmt.Sprintf(fmt.Sprintf(format, wc.W), msg)
}

func (wc *WC) buildFormat() string {
	if wc.format != "" {
		return wc.format
	}
	wc.format = "%%"
	if (wc.C & DidentRight) != 0 {
		wc.format += "-"
	}
	wc.format += "%ds"
	return wc.format
}

// Global convenience shortcuts
var (
	WCSyncWidth  = WC{C: DSyncWidth}
	WCSyncWidthR = WC{C: DSyncWidthR}
	WCSyncSpace  = WC{C: DSyncSpace}
	WCSyncSpaceR = WC{C: DSyncSpaceR}
)

// OnComplete returns decorator, which wraps provided decorator, with sole
// purpose to display provided message on complete event.
//
//	`decorator` Decorator to wrap
//
//	`message` message to display on complete event
//
//	`wc` optional WC config
func OnComplete(decorator Decorator, message string, wc ...WC) Decorator {
	if cm, ok := decorator.(OnCompleteMessenger); ok {
		cm.OnCompleteMessage(message, wc...)
		return decorator
	}
	msgDecorator := Name(message, wc...)
	return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string {
		if s.Completed {
			return msgDecorator.Decor(s, widthAccumulator, widthDistributor)
		}
		return decorator.Decor(s, widthAccumulator, widthDistributor)
	})
}

// StaticName returns name decorator.
//
//	`name` string to display
//
//	`wc` optional WC config
func StaticName(name string, wc ...WC) Decorator {
	return Name(name, wc...)
}

// Name returns name decorator.
//
//	`name` string to display
//
//	`wc` optional WC config
func Name(name string, wc ...WC) Decorator {
	var wc0 WC
	if len(wc) > 0 {
		wc0 = wc[0]
	}
	return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string {
		return wc0.formatMsg(name, widthAccumulator, widthDistributor)
	})
}

// CountersNoUnit returns raw counters decorator
//
//	`pairFormat` printf compatible verbs for current and total, like "%f" or "%d"
//
//	`wc` optional WC config
func CountersNoUnit(pairFormat string, wc ...WC) Decorator {
	return counters(pairFormat, 0, wc...)
}

// CountersKibiByte returns human friendly byte counters decorator, where counters unit is multiple by 1024.
//
//	`pairFormat` printf compatible verbs for current and total, like "%f" or "%d"
//
//	`wc` optional WC config
//
// pairFormat example:
//
//	"%.1f / %.1f" = "1.0MiB / 12.0MiB" or "% .1f / % .1f" = "1.0 MiB / 12.0 MiB"
func CountersKibiByte(pairFormat string, wc ...WC) Decorator {
	return counters(pairFormat, unitKiB, wc...)
}

// CountersKiloByte returns human friendly byte counters decorator, where counters unit is multiple by 1000.
//
//	`pairFormat` printf compatible verbs for current and total, like "%f" or "%d"
//
//	`wc` optional WC config
//
// pairFormat example:
//
//	"%.1f / %.1f" = "1.0MB / 12.0MB" or "% .1f / % .1f" = "1.0 MB / 12.0 MB"
func CountersKiloByte(pairFormat string, wc ...WC) Decorator {
	return counters(pairFormat, unitKB, wc...)
}

func counters(pairFormat string, unit int, wc ...WC) Decorator {
	var wc0 WC
	if len(wc) > 0 {
		wc0 = wc[0]
	}
	return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string {
		var str string
		switch unit {
		case unitKiB:
			str = fmt.Sprintf(pairFormat, CounterKiB(s.Current), CounterKiB(s.Total))
		case unitKB:
			str = fmt.Sprintf(pairFormat, CounterKB(s.Current), CounterKB(s.Total))
		default:
			str = fmt.Sprintf(pairFormat, s.Current, s.Total)
		}
		return wc0.formatMsg(str, widthAccumulator, widthDistributor)
	})
}

// Elapsed returns elapsed time decorator.
//
//	`style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
//
//	`wc` optional WC config
func Elapsed(style int, wc ...WC) Decorator {
	var wc0 WC
	if len(wc) > 0 {
		wc0 = wc[0]
	}
	return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string {
		var str string
		hours := int64((s.TimeElapsed / time.Hour) % 60)
		minutes := int64((s.TimeElapsed / time.Minute) % 60)
		seconds := int64((s.TimeElapsed / time.Second) % 60)

		switch style {
		case ET_STYLE_GO:
			str = fmt.Sprint(time.Duration(s.TimeElapsed.Seconds()) * time.Second)
		case ET_STYLE_HHMMSS:
			str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
		case ET_STYLE_HHMM:
			str = fmt.Sprintf("%02d:%02d", hours, minutes)
		case ET_STYLE_MMSS:
			str = fmt.Sprintf("%02d:%02d", minutes, seconds)
		}
		return wc0.formatMsg(str, widthAccumulator, widthDistributor)
	})
}

// Percentage returns percentage decorator.
//
//	`wc` optional WC config
func Percentage(wc ...WC) Decorator {
	var wc0 WC
	if len(wc) > 0 {
		wc0 = wc[0]
	}
	return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string {
		str := fmt.Sprintf("%d %%", CalcPercentage(s.Total, s.Current, 100))
		return wc0.formatMsg(str, widthAccumulator, widthDistributor)
	})
}

// CalcPercentage is a helper function, to calculate percentage.
func CalcPercentage(total, current, width int64) int64 {
	if total <= 0 {
		return 0
	}
	p := float64(width*current) / float64(total)
	return int64(round(p))
}

// SpeedNoUnit returns raw I/O operation speed decorator.
//
//	`unitFormat` printf compatible verb for value, like "%f" or "%d"
//
//	`wc` optional WC config
//
// unitFormat example:
//
//	"%.1f" = "1.0" or "% .1f" = "1.0"
func SpeedNoUnit(unitFormat string, wc ...WC) Decorator {
	return speed(unitFormat, 0, wc...)
}

// SpeedKibiByte returns human friendly I/O operation speed decorator,
//
//	`unitFormat` printf compatible verb for value, like "%f" or "%d"
//
//	`wc` optional WC config
//
// unitFormat example:
//
//	"%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s"
func SpeedKibiByte(unitFormat string, wc ...WC) Decorator {
	return speed(unitFormat, unitKiB, wc...)
}

// SpeedKiloByte returns human friendly I/O operation speed decorator,
//
//	`unitFormat` printf compatible verb for value, like "%f" or "%d"
//
//	`wc` optional WC config
//
// unitFormat example:
//
//	"%.1f" = "1.0MB/s" or "% .1f" = "1.0 MB/s"
func SpeedKiloByte(unitFormat string, wc ...WC) Decorator {
	return speed(unitFormat, unitKB, wc...)
}

func speed(unitFormat string, unit int, wc ...WC) Decorator {
	var wc0 WC
	if len(wc) > 0 {
		wc0 = wc[0]
	}
	return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string {
		var str string
		speed := float64(s.Current) / s.TimeElapsed.Seconds()
		if math.IsNaN(speed) || math.IsInf(speed, 0) {
			speed = .0
		}

		switch unit {
		case unitKiB:
			str = fmt.Sprintf(unitFormat, SpeedKiB(speed))
		case unitKB:
			str = fmt.Sprintf(unitFormat, SpeedKB(speed))
		default:
			str = fmt.Sprintf(unitFormat, speed)
		}
		return wc0.formatMsg(str, widthAccumulator, widthDistributor)
	})
}