forked from TrueCloudLab/restic
cmd, ui: Deduplicate formatting utilities
This commit is contained in:
parent
ee6688a9f6
commit
006380199e
9 changed files with 136 additions and 130 deletions
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/restic/restic/internal/cache"
|
"github.com/restic/restic/internal/cache"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/table"
|
"github.com/restic/restic/internal/ui/table"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -138,7 +139,7 @@ func runCache(opts CacheOptions, gopts GlobalOptions, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
size = fmt.Sprintf("%11s", formatBytes(uint64(bytes)))
|
size = fmt.Sprintf("%11s", ui.FormatBytes(uint64(bytes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
name := entry.Name()
|
name := entry.Name()
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -425,8 +426,8 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
||||||
Printf("Others: %5d new, %5d removed\n", stats.Added.Others, stats.Removed.Others)
|
Printf("Others: %5d new, %5d removed\n", stats.Added.Others, stats.Removed.Others)
|
||||||
Printf("Data Blobs: %5d new, %5d removed\n", stats.Added.DataBlobs, stats.Removed.DataBlobs)
|
Printf("Data Blobs: %5d new, %5d removed\n", stats.Added.DataBlobs, stats.Removed.DataBlobs)
|
||||||
Printf("Tree Blobs: %5d new, %5d removed\n", stats.Added.TreeBlobs, stats.Removed.TreeBlobs)
|
Printf("Tree Blobs: %5d new, %5d removed\n", stats.Added.TreeBlobs, stats.Removed.TreeBlobs)
|
||||||
Printf(" Added: %-5s\n", formatBytes(uint64(stats.Added.Bytes)))
|
Printf(" Added: %-5s\n", ui.FormatBytes(uint64(stats.Added.Bytes)))
|
||||||
Printf(" Removed: %-5s\n", formatBytes(uint64(stats.Removed.Bytes)))
|
Printf(" Removed: %-5s\n", ui.FormatBytes(uint64(stats.Removed.Bytes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/restic/restic/internal/pack"
|
"github.com/restic/restic/internal/pack"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
"github.com/restic/restic/internal/ui"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -609,29 +610,29 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
|
||||||
|
|
||||||
// printPruneStats prints out the statistics
|
// printPruneStats prints out the statistics
|
||||||
func printPruneStats(gopts GlobalOptions, stats pruneStats) error {
|
func printPruneStats(gopts GlobalOptions, stats pruneStats) error {
|
||||||
Verboseff("\nused: %10d blobs / %s\n", stats.blobs.used, formatBytes(stats.size.used))
|
Verboseff("\nused: %10d blobs / %s\n", stats.blobs.used, ui.FormatBytes(stats.size.used))
|
||||||
if stats.blobs.duplicate > 0 {
|
if stats.blobs.duplicate > 0 {
|
||||||
Verboseff("duplicates: %10d blobs / %s\n", stats.blobs.duplicate, formatBytes(stats.size.duplicate))
|
Verboseff("duplicates: %10d blobs / %s\n", stats.blobs.duplicate, ui.FormatBytes(stats.size.duplicate))
|
||||||
}
|
}
|
||||||
Verboseff("unused: %10d blobs / %s\n", stats.blobs.unused, formatBytes(stats.size.unused))
|
Verboseff("unused: %10d blobs / %s\n", stats.blobs.unused, ui.FormatBytes(stats.size.unused))
|
||||||
if stats.size.unref > 0 {
|
if stats.size.unref > 0 {
|
||||||
Verboseff("unreferenced: %s\n", formatBytes(stats.size.unref))
|
Verboseff("unreferenced: %s\n", ui.FormatBytes(stats.size.unref))
|
||||||
}
|
}
|
||||||
totalBlobs := stats.blobs.used + stats.blobs.unused + stats.blobs.duplicate
|
totalBlobs := stats.blobs.used + stats.blobs.unused + stats.blobs.duplicate
|
||||||
totalSize := stats.size.used + stats.size.duplicate + stats.size.unused + stats.size.unref
|
totalSize := stats.size.used + stats.size.duplicate + stats.size.unused + stats.size.unref
|
||||||
unusedSize := stats.size.duplicate + stats.size.unused
|
unusedSize := stats.size.duplicate + stats.size.unused
|
||||||
Verboseff("total: %10d blobs / %s\n", totalBlobs, formatBytes(totalSize))
|
Verboseff("total: %10d blobs / %s\n", totalBlobs, ui.FormatBytes(totalSize))
|
||||||
Verboseff("unused size: %s of total size\n", formatPercent(unusedSize, totalSize))
|
Verboseff("unused size: %s of total size\n", ui.FormatPercent(unusedSize, totalSize))
|
||||||
|
|
||||||
Verbosef("\nto repack: %10d blobs / %s\n", stats.blobs.repack, formatBytes(stats.size.repack))
|
Verbosef("\nto repack: %10d blobs / %s\n", stats.blobs.repack, ui.FormatBytes(stats.size.repack))
|
||||||
Verbosef("this removes: %10d blobs / %s\n", stats.blobs.repackrm, formatBytes(stats.size.repackrm))
|
Verbosef("this removes: %10d blobs / %s\n", stats.blobs.repackrm, ui.FormatBytes(stats.size.repackrm))
|
||||||
Verbosef("to delete: %10d blobs / %s\n", stats.blobs.remove, formatBytes(stats.size.remove+stats.size.unref))
|
Verbosef("to delete: %10d blobs / %s\n", stats.blobs.remove, ui.FormatBytes(stats.size.remove+stats.size.unref))
|
||||||
totalPruneSize := stats.size.remove + stats.size.repackrm + stats.size.unref
|
totalPruneSize := stats.size.remove + stats.size.repackrm + stats.size.unref
|
||||||
Verbosef("total prune: %10d blobs / %s\n", stats.blobs.remove+stats.blobs.repackrm, formatBytes(totalPruneSize))
|
Verbosef("total prune: %10d blobs / %s\n", stats.blobs.remove+stats.blobs.repackrm, ui.FormatBytes(totalPruneSize))
|
||||||
Verbosef("remaining: %10d blobs / %s\n", totalBlobs-(stats.blobs.remove+stats.blobs.repackrm), formatBytes(totalSize-totalPruneSize))
|
Verbosef("remaining: %10d blobs / %s\n", totalBlobs-(stats.blobs.remove+stats.blobs.repackrm), ui.FormatBytes(totalSize-totalPruneSize))
|
||||||
unusedAfter := unusedSize - stats.size.remove - stats.size.repackrm
|
unusedAfter := unusedSize - stats.size.remove - stats.size.repackrm
|
||||||
Verbosef("unused size after prune: %s (%s of remaining size)\n",
|
Verbosef("unused size after prune: %s (%s of remaining size)\n",
|
||||||
formatBytes(unusedAfter), formatPercent(unusedAfter, totalSize-totalPruneSize))
|
ui.FormatBytes(unusedAfter), ui.FormatPercent(unusedAfter, totalSize-totalPruneSize))
|
||||||
Verbosef("\n")
|
Verbosef("\n")
|
||||||
Verboseff("totally used packs: %10d\n", stats.packs.used)
|
Verboseff("totally used packs: %10d\n", stats.packs.used)
|
||||||
Verboseff("partly used packs: %10d\n", stats.packs.partlyUsed)
|
Verboseff("partly used packs: %10d\n", stats.packs.partlyUsed)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/crypto"
|
"github.com/restic/restic/internal/crypto"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/walker"
|
"github.com/restic/restic/internal/walker"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
"github.com/minio/sha256-simd"
|
||||||
|
@ -164,9 +165,9 @@ func runStats(ctx context.Context, gopts GlobalOptions, args []string) error {
|
||||||
Printf(" Total File Count: %d\n", stats.TotalFileCount)
|
Printf(" Total File Count: %d\n", stats.TotalFileCount)
|
||||||
}
|
}
|
||||||
if stats.TotalUncompressedSize > 0 {
|
if stats.TotalUncompressedSize > 0 {
|
||||||
Printf(" Total Uncompressed Size: %-5s\n", formatBytes(stats.TotalUncompressedSize))
|
Printf(" Total Uncompressed Size: %-5s\n", ui.FormatBytes(stats.TotalUncompressedSize))
|
||||||
}
|
}
|
||||||
Printf(" Total Size: %-5s\n", formatBytes(stats.TotalSize))
|
Printf(" Total Size: %-5s\n", ui.FormatBytes(stats.TotalSize))
|
||||||
if stats.CompressionProgress > 0 {
|
if stats.CompressionProgress > 0 {
|
||||||
Printf(" Compression Progress: %.2f%%\n", stats.CompressionProgress)
|
Printf(" Compression Progress: %.2f%%\n", stats.CompressionProgress)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,59 +3,10 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatBytes(c uint64) string {
|
|
||||||
b := float64(c)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case c > 1<<40:
|
|
||||||
return fmt.Sprintf("%.3f TiB", b/(1<<40))
|
|
||||||
case c > 1<<30:
|
|
||||||
return fmt.Sprintf("%.3f GiB", b/(1<<30))
|
|
||||||
case c > 1<<20:
|
|
||||||
return fmt.Sprintf("%.3f MiB", b/(1<<20))
|
|
||||||
case c > 1<<10:
|
|
||||||
return fmt.Sprintf("%.3f KiB", b/(1<<10))
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%d B", c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatSeconds(sec uint64) string {
|
|
||||||
hours := sec / 3600
|
|
||||||
sec -= hours * 3600
|
|
||||||
min := sec / 60
|
|
||||||
sec -= min * 60
|
|
||||||
if hours > 0 {
|
|
||||||
return fmt.Sprintf("%d:%02d:%02d", hours, min, sec)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%d:%02d", min, sec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatPercent(numerator uint64, denominator uint64) string {
|
|
||||||
if denominator == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
percent := 100.0 * float64(numerator) / float64(denominator)
|
|
||||||
|
|
||||||
if percent > 100 {
|
|
||||||
percent = 100
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%3.2f%%", percent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatDuration(d time.Duration) string {
|
|
||||||
sec := uint64(d / time.Second)
|
|
||||||
return formatSeconds(sec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatNode(path string, n *restic.Node, long bool) string {
|
func formatNode(path string, n *restic.Node, long bool) string {
|
||||||
if !long {
|
if !long {
|
||||||
return path
|
return path
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
"github.com/restic/restic/internal/ui/termstatus"
|
"github.com/restic/restic/internal/ui/termstatus"
|
||||||
)
|
)
|
||||||
|
@ -39,10 +40,11 @@ func newProgressMax(show bool, max uint64, description string) *progress.Counter
|
||||||
return progress.New(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
|
return progress.New(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
|
||||||
var status string
|
var status string
|
||||||
if max == 0 {
|
if max == 0 {
|
||||||
status = fmt.Sprintf("[%s] %d %s", formatDuration(d), v, description)
|
status = fmt.Sprintf("[%s] %d %s",
|
||||||
|
ui.FormatDuration(d), v, description)
|
||||||
} else {
|
} else {
|
||||||
status = fmt.Sprintf("[%s] %s %d / %d %s",
|
status = fmt.Sprintf("[%s] %s %d / %d %s",
|
||||||
formatDuration(d), formatPercent(v, max), v, max, description)
|
ui.FormatDuration(d), ui.FormatPercent(v, max), v, max, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
printProgress(status, canUpdateStatus)
|
printProgress(status, canUpdateStatus)
|
||||||
|
|
|
@ -37,26 +37,26 @@ func (b *TextProgress) Update(total, processed Counter, errors uint, currentFile
|
||||||
if total.Files == 0 && total.Dirs == 0 {
|
if total.Files == 0 && total.Dirs == 0 {
|
||||||
// no total count available yet
|
// no total count available yet
|
||||||
status = fmt.Sprintf("[%s] %v files, %s, %d errors",
|
status = fmt.Sprintf("[%s] %v files, %s, %d errors",
|
||||||
formatDuration(time.Since(start)),
|
ui.FormatDuration(time.Since(start)),
|
||||||
processed.Files, formatBytes(processed.Bytes), errors,
|
processed.Files, ui.FormatBytes(processed.Bytes), errors,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
var eta, percent string
|
var eta, percent string
|
||||||
|
|
||||||
if secs > 0 && processed.Bytes < total.Bytes {
|
if secs > 0 && processed.Bytes < total.Bytes {
|
||||||
eta = fmt.Sprintf(" ETA %s", formatSeconds(secs))
|
eta = fmt.Sprintf(" ETA %s", ui.FormatSeconds(secs))
|
||||||
percent = formatPercent(processed.Bytes, total.Bytes)
|
percent = ui.FormatPercent(processed.Bytes, total.Bytes)
|
||||||
percent += " "
|
percent += " "
|
||||||
}
|
}
|
||||||
|
|
||||||
// include totals
|
// include totals
|
||||||
status = fmt.Sprintf("[%s] %s%v files %s, total %v files %v, %d errors%s",
|
status = fmt.Sprintf("[%s] %s%v files %s, total %v files %v, %d errors%s",
|
||||||
formatDuration(time.Since(start)),
|
ui.FormatDuration(time.Since(start)),
|
||||||
percent,
|
percent,
|
||||||
processed.Files,
|
processed.Files,
|
||||||
formatBytes(processed.Bytes),
|
ui.FormatBytes(processed.Bytes),
|
||||||
total.Files,
|
total.Files,
|
||||||
formatBytes(total.Bytes),
|
ui.FormatBytes(total.Bytes),
|
||||||
errors,
|
errors,
|
||||||
eta,
|
eta,
|
||||||
)
|
)
|
||||||
|
@ -85,69 +85,28 @@ func (b *TextProgress) Error(item string, err error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatPercent(numerator uint64, denominator uint64) string {
|
|
||||||
if denominator == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
percent := 100.0 * float64(numerator) / float64(denominator)
|
|
||||||
|
|
||||||
if percent > 100 {
|
|
||||||
percent = 100
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%3.2f%%", percent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatSeconds(sec uint64) string {
|
|
||||||
hours := sec / 3600
|
|
||||||
sec -= hours * 3600
|
|
||||||
min := sec / 60
|
|
||||||
sec -= min * 60
|
|
||||||
if hours > 0 {
|
|
||||||
return fmt.Sprintf("%d:%02d:%02d", hours, min, sec)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%d:%02d", min, sec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatDuration(d time.Duration) string {
|
|
||||||
sec := uint64(d / time.Second)
|
|
||||||
return formatSeconds(sec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatBytes(c uint64) string {
|
|
||||||
b := float64(c)
|
|
||||||
switch {
|
|
||||||
case c > 1<<40:
|
|
||||||
return fmt.Sprintf("%.3f TiB", b/(1<<40))
|
|
||||||
case c > 1<<30:
|
|
||||||
return fmt.Sprintf("%.3f GiB", b/(1<<30))
|
|
||||||
case c > 1<<20:
|
|
||||||
return fmt.Sprintf("%.3f MiB", b/(1<<20))
|
|
||||||
case c > 1<<10:
|
|
||||||
return fmt.Sprintf("%.3f KiB", b/(1<<10))
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%d B", c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompleteItem is the status callback function for the archiver when a
|
// CompleteItem is the status callback function for the archiver when a
|
||||||
// file/dir has been saved successfully.
|
// file/dir has been saved successfully.
|
||||||
func (b *TextProgress) CompleteItem(messageType, item string, previous, current *restic.Node, s archiver.ItemStats, d time.Duration) {
|
func (b *TextProgress) CompleteItem(messageType, item string, previous, current *restic.Node, s archiver.ItemStats, d time.Duration) {
|
||||||
switch messageType {
|
switch messageType {
|
||||||
case "dir new":
|
case "dir new":
|
||||||
b.VV("new %v, saved in %.3fs (%v added, %v stored, %v metadata)", item, d.Seconds(), formatBytes(s.DataSize), formatBytes(s.DataSizeInRepo), formatBytes(s.TreeSizeInRepo))
|
b.VV("new %v, saved in %.3fs (%v added, %v stored, %v metadata)",
|
||||||
|
item, d.Seconds(), ui.FormatBytes(s.DataSize),
|
||||||
|
ui.FormatBytes(s.DataSizeInRepo), ui.FormatBytes(s.TreeSizeInRepo))
|
||||||
case "dir unchanged":
|
case "dir unchanged":
|
||||||
b.VV("unchanged %v", item)
|
b.VV("unchanged %v", item)
|
||||||
case "dir modified":
|
case "dir modified":
|
||||||
b.VV("modified %v, saved in %.3fs (%v added, %v stored, %v metadata)", item, d.Seconds(), formatBytes(s.DataSize), formatBytes(s.DataSizeInRepo), formatBytes(s.TreeSizeInRepo))
|
b.VV("modified %v, saved in %.3fs (%v added, %v stored, %v metadata)",
|
||||||
|
item, d.Seconds(), ui.FormatBytes(s.DataSize),
|
||||||
|
ui.FormatBytes(s.DataSizeInRepo), ui.FormatBytes(s.TreeSizeInRepo))
|
||||||
case "file new":
|
case "file new":
|
||||||
b.VV("new %v, saved in %.3fs (%v added)", item, d.Seconds(), formatBytes(s.DataSize))
|
b.VV("new %v, saved in %.3fs (%v added)", item,
|
||||||
|
d.Seconds(), ui.FormatBytes(s.DataSize))
|
||||||
case "file unchanged":
|
case "file unchanged":
|
||||||
b.VV("unchanged %v", item)
|
b.VV("unchanged %v", item)
|
||||||
case "file modified":
|
case "file modified":
|
||||||
b.VV("modified %v, saved in %.3fs (%v added, %v stored)", item, d.Seconds(), formatBytes(s.DataSize), formatBytes(s.DataSizeInRepo))
|
b.VV("modified %v, saved in %.3fs (%v added, %v stored)", item,
|
||||||
|
d.Seconds(), ui.FormatBytes(s.DataSize), ui.FormatBytes(s.DataSizeInRepo))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +114,7 @@ func (b *TextProgress) CompleteItem(messageType, item string, previous, current
|
||||||
func (b *TextProgress) ReportTotal(item string, start time.Time, s archiver.ScanStats) {
|
func (b *TextProgress) ReportTotal(item string, start time.Time, s archiver.ScanStats) {
|
||||||
b.V("scan finished in %.3fs: %v files, %s",
|
b.V("scan finished in %.3fs: %v files, %s",
|
||||||
time.Since(start).Seconds(),
|
time.Since(start).Seconds(),
|
||||||
s.Files, formatBytes(s.Bytes),
|
s.Files, ui.FormatBytes(s.Bytes),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,11 +136,13 @@ func (b *TextProgress) Finish(snapshotID restic.ID, start time.Time, summary *Su
|
||||||
if dryRun {
|
if dryRun {
|
||||||
verb = "Would add"
|
verb = "Would add"
|
||||||
}
|
}
|
||||||
b.P("%s to the repository: %-5s (%-5s stored)\n", verb, formatBytes(summary.ItemStats.DataSize+summary.ItemStats.TreeSize), formatBytes(summary.ItemStats.DataSizeInRepo+summary.ItemStats.TreeSizeInRepo))
|
b.P("%s to the repository: %-5s (%-5s stored)\n", verb,
|
||||||
|
ui.FormatBytes(summary.ItemStats.DataSize+summary.ItemStats.TreeSize),
|
||||||
|
ui.FormatBytes(summary.ItemStats.DataSizeInRepo+summary.ItemStats.TreeSizeInRepo))
|
||||||
b.P("\n")
|
b.P("\n")
|
||||||
b.P("processed %v files, %v in %s",
|
b.P("processed %v files, %v in %s",
|
||||||
summary.Files.New+summary.Files.Changed+summary.Files.Unchanged,
|
summary.Files.New+summary.Files.Changed+summary.Files.Unchanged,
|
||||||
formatBytes(summary.ProcessedBytes),
|
ui.FormatBytes(summary.ProcessedBytes),
|
||||||
formatDuration(time.Since(start)),
|
ui.FormatDuration(time.Since(start)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
55
internal/ui/format.go
Normal file
55
internal/ui/format.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FormatBytes(c uint64) string {
|
||||||
|
b := float64(c)
|
||||||
|
switch {
|
||||||
|
case c > 1<<40:
|
||||||
|
return fmt.Sprintf("%.3f TiB", b/(1<<40))
|
||||||
|
case c > 1<<30:
|
||||||
|
return fmt.Sprintf("%.3f GiB", b/(1<<30))
|
||||||
|
case c > 1<<20:
|
||||||
|
return fmt.Sprintf("%.3f MiB", b/(1<<20))
|
||||||
|
case c > 1<<10:
|
||||||
|
return fmt.Sprintf("%.3f KiB", b/(1<<10))
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%d B", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatPercent formats numerator/denominator as a percentage.
|
||||||
|
func FormatPercent(numerator uint64, denominator uint64) string {
|
||||||
|
if denominator == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
percent := 100.0 * float64(numerator) / float64(denominator)
|
||||||
|
if percent > 100 {
|
||||||
|
percent = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%3.2f%%", percent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatDuration formats d as FormatSeconds would.
|
||||||
|
func FormatDuration(d time.Duration) string {
|
||||||
|
sec := uint64(d / time.Second)
|
||||||
|
return FormatSeconds(sec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatSeconds formats sec as MM:SS, or HH:MM:SS if sec seconds
|
||||||
|
// is at least an hour.
|
||||||
|
func FormatSeconds(sec uint64) string {
|
||||||
|
hours := sec / 3600
|
||||||
|
sec -= hours * 3600
|
||||||
|
min := sec / 60
|
||||||
|
sec -= min * 60
|
||||||
|
if hours > 0 {
|
||||||
|
return fmt.Sprintf("%d:%02d:%02d", hours, min, sec)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d:%02d", min, sec)
|
||||||
|
}
|
33
internal/ui/format_test.go
Normal file
33
internal/ui/format_test.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestFormatBytes(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
size uint64
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, "0 B"},
|
||||||
|
{1025, "1.001 KiB"},
|
||||||
|
{1<<30 + 7, "1.000 GiB"},
|
||||||
|
} {
|
||||||
|
if got := FormatBytes(c.size); got != c.want {
|
||||||
|
t.Errorf("want %q, got %q", c.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatPercent(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
num, denom uint64
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, 5, "0.00%"},
|
||||||
|
{3, 7, "42.86%"},
|
||||||
|
{99, 99, "100.00%"},
|
||||||
|
} {
|
||||||
|
if got := FormatPercent(c.num, c.denom); got != c.want {
|
||||||
|
t.Errorf("want %q, got %q", c.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue