clonetool/util.go
2026-09-06 20:15:54 +02:00

74 lines
1.7 KiB
Go

package main
import (
"fmt"
"os/exec"
"strings"
"sync"
)
// runCmd runs name with args, returning trimmed combined output.
func runCmd(name string, args ...string) (string, error) {
out, err := exec.Command(name, args...).CombinedOutput()
return strings.TrimSpace(string(out)), err
}
// runCmdStdin is runCmd with a fixed stdin string (for tools that prompt).
func runCmdStdin(stdin, name string, args ...string) (string, error) {
c := exec.Command(name, args...)
c.Stdin = strings.NewReader(stdin)
out, err := c.CombinedOutput()
return strings.TrimSpace(string(out)), err
}
// limitedBuffer keeps only the last maxLen bytes written to it — used to
// capture a bounded tail of a subprocess's stderr for error messages
// without risking unbounded memory growth from a noisy remote command.
type limitedBuffer struct {
mu sync.Mutex
buf []byte
maxLen int
}
func newLimitedBuffer(maxLen int) *limitedBuffer {
return &limitedBuffer{maxLen: maxLen}
}
func (b *limitedBuffer) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
b.buf = append(b.buf, p...)
if len(b.buf) > b.maxLen {
b.buf = b.buf[len(b.buf)-b.maxLen:]
}
return len(p), nil
}
func (b *limitedBuffer) String() string {
b.mu.Lock()
defer b.mu.Unlock()
return string(b.buf)
}
// roundUp rounds n up to the next multiple of to (to <= 1 is a no-op).
func roundUp(n, to int64) int64 {
if to <= 1 {
return n
}
return (n + to - 1) / to * to
}
func humanBytes(n int64) string {
const unit = 1024
if n < unit {
return fmt.Sprintf("%d B", n)
}
div, exp := int64(unit), 0
for m := n / unit; m >= unit; m /= unit {
div *= unit
exp++
}
units := "KMGTPE"
return fmt.Sprintf("%.1f%ciB", float64(n)/float64(div), units[exp])
}