cli: split banger.go god file into focused files
Pure code motion — banger.go 3508→240 LOC, same-package decomposition keeps all identifiers visible without export changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3a5f4cd40d
commit
3f6ecb4376
12 changed files with 3478 additions and 3268 deletions
318
internal/cli/printers.go
Normal file
318
internal/cli/printers.go
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"banger/internal/api"
|
||||
"banger/internal/model"
|
||||
"banger/internal/system"
|
||||
)
|
||||
|
||||
// anyWriter is the minimal writer surface every printer needs. Split
|
||||
// out from io.Writer because some of our callers already hold a
|
||||
// tabwriter/bytes.Buffer by value.
|
||||
type anyWriter interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
// -- small helpers --------------------------------------------------
|
||||
|
||||
func humanSize(bytes int64) string {
|
||||
if bytes <= 0 {
|
||||
return "-"
|
||||
}
|
||||
const (
|
||||
kib = 1024
|
||||
mib = 1024 * kib
|
||||
gib = 1024 * mib
|
||||
)
|
||||
switch {
|
||||
case bytes >= gib:
|
||||
return fmt.Sprintf("%.1fGiB", float64(bytes)/float64(gib))
|
||||
case bytes >= mib:
|
||||
return fmt.Sprintf("%.1fMiB", float64(bytes)/float64(mib))
|
||||
case bytes >= kib:
|
||||
return fmt.Sprintf("%.1fKiB", float64(bytes)/float64(kib))
|
||||
default:
|
||||
return fmt.Sprintf("%dB", bytes)
|
||||
}
|
||||
}
|
||||
|
||||
func dashIfEmpty(s string) string {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return "-"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func emptyDash(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return "-"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// -- generic printers -----------------------------------------------
|
||||
|
||||
func printJSON(out anyWriter, v any) error {
|
||||
data, err := json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintln(out, string(data))
|
||||
return err
|
||||
}
|
||||
|
||||
// -- VM printers ----------------------------------------------------
|
||||
|
||||
func printVMSummary(out anyWriter, vm model.VMRecord) error {
|
||||
_, err := fmt.Fprintf(
|
||||
out,
|
||||
"%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
shortID(vm.ID),
|
||||
vm.Name,
|
||||
vm.State,
|
||||
vm.Runtime.GuestIP,
|
||||
model.FormatSizeBytes(vm.Spec.WorkDiskSizeBytes),
|
||||
vm.Runtime.DNSName,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func printVMIDList(out anyWriter, vms []model.VMRecord) error {
|
||||
for _, vm := range vms {
|
||||
if _, err := fmt.Fprintln(out, vm.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printVMListTable(out anyWriter, vms []model.VMRecord, imageNames map[string]string) error {
|
||||
w := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0)
|
||||
if _, err := fmt.Fprintln(w, "ID\tNAME\tSTATE\tIMAGE\tIP\tVCPU\tMEM\tDISK\tCREATED"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, vm := range vms {
|
||||
if _, err := fmt.Fprintf(
|
||||
w,
|
||||
"%s\t%s\t%s\t%s\t%s\t%d\t%d MiB\t%s\t%s\n",
|
||||
shortID(vm.ID),
|
||||
vm.Name,
|
||||
vm.State,
|
||||
vmImageLabel(vm.ImageID, imageNames),
|
||||
vm.Runtime.GuestIP,
|
||||
vm.Spec.VCPUCount,
|
||||
vm.Spec.MemoryMiB,
|
||||
model.FormatSizeBytes(vm.Spec.WorkDiskSizeBytes),
|
||||
relativeTime(vm.CreatedAt),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func printVMPortsTable(out anyWriter, result api.VMPortsResult) error {
|
||||
type portRow struct {
|
||||
Proto string
|
||||
Endpoint string
|
||||
Process string
|
||||
Command string
|
||||
Port int
|
||||
}
|
||||
rows := make([]portRow, 0, len(result.Ports))
|
||||
for _, port := range result.Ports {
|
||||
rows = append(rows, portRow{
|
||||
Proto: port.Proto,
|
||||
Endpoint: port.Endpoint,
|
||||
Process: port.Process,
|
||||
Command: port.Command,
|
||||
Port: port.Port,
|
||||
})
|
||||
}
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
if rows[i].Proto != rows[j].Proto {
|
||||
return rows[i].Proto < rows[j].Proto
|
||||
}
|
||||
if rows[i].Port != rows[j].Port {
|
||||
return rows[i].Port < rows[j].Port
|
||||
}
|
||||
if rows[i].Process != rows[j].Process {
|
||||
return rows[i].Process < rows[j].Process
|
||||
}
|
||||
return rows[i].Command < rows[j].Command
|
||||
})
|
||||
if len(rows) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0)
|
||||
if _, err := fmt.Fprintln(w, "PROTO\tENDPOINT\tPROCESS\tCOMMAND"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range rows {
|
||||
if _, err := fmt.Fprintf(
|
||||
w,
|
||||
"%s\t%s\t%s\t%s\n",
|
||||
row.Proto,
|
||||
emptyDash(row.Endpoint),
|
||||
emptyDash(row.Process),
|
||||
emptyDash(row.Command),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// -- image printers -------------------------------------------------
|
||||
|
||||
func printImageSummary(out anyWriter, image model.Image) error {
|
||||
_, err := fmt.Fprintf(out, "%s\t%s\t%t\t%s\n", shortID(image.ID), image.Name, image.Managed, image.RootfsPath)
|
||||
return err
|
||||
}
|
||||
|
||||
func imageNameIndex(images []model.Image) map[string]string {
|
||||
index := make(map[string]string, len(images))
|
||||
for _, image := range images {
|
||||
index[image.ID] = image.Name
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func vmImageLabel(imageID string, imageNames map[string]string) string {
|
||||
if name := strings.TrimSpace(imageNames[imageID]); name != "" {
|
||||
return name
|
||||
}
|
||||
return shortID(imageID)
|
||||
}
|
||||
|
||||
func printImageListTable(out anyWriter, images []model.Image) error {
|
||||
w := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0)
|
||||
if _, err := fmt.Fprintln(w, "ID\tNAME\tMANAGED\tROOTFS SIZE\tCREATED"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, image := range images {
|
||||
if _, err := fmt.Fprintf(
|
||||
w,
|
||||
"%s\t%s\t%t\t%s\t%s\n",
|
||||
shortID(image.ID),
|
||||
image.Name,
|
||||
image.Managed,
|
||||
rootfsSizeLabel(image.RootfsPath),
|
||||
relativeTime(image.CreatedAt),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func rootfsSizeLabel(path string) string {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return "-"
|
||||
}
|
||||
if info.Size() <= 0 {
|
||||
return "0"
|
||||
}
|
||||
return model.FormatSizeBytes(info.Size())
|
||||
}
|
||||
|
||||
// -- kernel printers ------------------------------------------------
|
||||
|
||||
func printKernelListTable(out anyWriter, entries []api.KernelEntry) error {
|
||||
w := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0)
|
||||
if _, err := fmt.Fprintln(w, "NAME\tDISTRO\tARCH\tKERNEL\tIMPORTED"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if _, err := fmt.Fprintf(
|
||||
w,
|
||||
"%s\t%s\t%s\t%s\t%s\n",
|
||||
entry.Name,
|
||||
dashIfEmpty(entry.Distro),
|
||||
dashIfEmpty(entry.Arch),
|
||||
dashIfEmpty(entry.KernelVersion),
|
||||
dashIfEmpty(entry.ImportedAt),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func printKernelCatalogTable(out anyWriter, entries []api.KernelCatalogEntry) error {
|
||||
w := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0)
|
||||
if _, err := fmt.Fprintln(w, "NAME\tDISTRO\tARCH\tKERNEL\tSIZE\tSTATE"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
state := "available"
|
||||
if entry.Pulled {
|
||||
state = "pulled"
|
||||
}
|
||||
if _, err := fmt.Fprintf(
|
||||
w,
|
||||
"%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
entry.Name,
|
||||
dashIfEmpty(entry.Distro),
|
||||
dashIfEmpty(entry.Arch),
|
||||
dashIfEmpty(entry.KernelVersion),
|
||||
humanSize(entry.SizeBytes),
|
||||
state,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// -- guest session printers -----------------------------------------
|
||||
|
||||
func printGuestSessionSummary(out anyWriter, session model.GuestSession) error {
|
||||
_, err := fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\n", session.ID, session.Name, session.Status, session.Command, session.CWD)
|
||||
return err
|
||||
}
|
||||
|
||||
func printGuestSessionTable(out io.Writer, sessions []model.GuestSession) error {
|
||||
tw := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0)
|
||||
if _, err := fmt.Fprintln(tw, "ID\tNAME\tSTATUS\tATTACH\tCOMMAND\tCWD"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, session := range sessions {
|
||||
attach := "no"
|
||||
if session.Attachable {
|
||||
attach = "yes"
|
||||
}
|
||||
if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\n", shortID(session.ID), session.Name, session.Status, attach, session.Command, session.CWD); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tw.Flush()
|
||||
}
|
||||
|
||||
// -- doctor printer -------------------------------------------------
|
||||
|
||||
func printDoctorReport(out anyWriter, report system.Report) error {
|
||||
for _, check := range report.Checks {
|
||||
status := strings.ToUpper(string(check.Status))
|
||||
if _, err := fmt.Fprintf(out, "%s\t%s\n", status, check.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, detail := range check.Details {
|
||||
if _, err := fmt.Fprintf(out, " - %s\n", detail); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue