275 lines
5.9 KiB
Go
275 lines
5.9 KiB
Go
package formatter
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/charmbracelet/lipgloss/table"
|
|
"github.com/docker/go-units"
|
|
"golang.org/x/term"
|
|
|
|
"coopcloud.tech/abra/pkg/config"
|
|
"coopcloud.tech/abra/pkg/log"
|
|
"github.com/schollz/progressbar/v3"
|
|
)
|
|
|
|
var BoldStyle = lipgloss.NewStyle().
|
|
Bold(true)
|
|
|
|
var BoldUnderlineStyle = lipgloss.NewStyle().
|
|
Bold(true).
|
|
Underline(true)
|
|
|
|
func ShortenID(str string) string {
|
|
return str[:12]
|
|
}
|
|
|
|
func SmallSHA(hash string) string {
|
|
return hash[:8]
|
|
}
|
|
|
|
// RemoveSha remove image sha from a string that are added in some docker outputs
|
|
func RemoveSha(str string) string {
|
|
return strings.Split(str, "@")[0]
|
|
}
|
|
|
|
// HumanDuration from docker/cli RunningFor() to be accessible outside of the class
|
|
func HumanDuration(timestamp int64) string {
|
|
date := time.Unix(timestamp, 0)
|
|
now := time.Now().UTC()
|
|
return units.HumanDuration(now.Sub(date)) + " ago"
|
|
}
|
|
|
|
// CreateTable prepares a table layout for output.
|
|
func CreateTable() (*table.Table, error) {
|
|
var (
|
|
renderer = lipgloss.NewRenderer(os.Stdout)
|
|
headerStyle = renderer.NewStyle().Bold(true).Align(lipgloss.Center)
|
|
cellStyle = renderer.NewStyle().Padding(0, 1)
|
|
borderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
|
|
)
|
|
|
|
table := table.New().
|
|
Border(lipgloss.ThickBorder()).
|
|
BorderStyle(borderStyle).
|
|
StyleFunc(func(row, col int) lipgloss.Style {
|
|
var style lipgloss.Style
|
|
|
|
switch {
|
|
case row == table.HeaderRow:
|
|
return headerStyle
|
|
default:
|
|
style = cellStyle
|
|
}
|
|
|
|
return style
|
|
})
|
|
|
|
return table, nil
|
|
}
|
|
|
|
func PrintTable(t *table.Table) error {
|
|
if isAbraCI, ok := os.LookupEnv("ABRA_CI"); ok && isAbraCI == "1" {
|
|
// NOTE(d1): no width limits for CI testing since we test against outputs
|
|
log.Debug("detected ABRA_CI=1")
|
|
fmt.Println(t)
|
|
return nil
|
|
}
|
|
|
|
tWidth, _ := lipgloss.Size(t.String())
|
|
|
|
width, _, err := term.GetSize(0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if tWidth > width {
|
|
t.Width(width - 10)
|
|
}
|
|
|
|
fmt.Println(t)
|
|
|
|
return nil
|
|
}
|
|
|
|
// horizontal is a JoinHorizontal helper function.
|
|
func horizontal(left, mid, right string) string {
|
|
return lipgloss.JoinHorizontal(lipgloss.Right, left, mid, right)
|
|
}
|
|
|
|
func CreateOverview(header string, rows [][]string) string {
|
|
var borderStyle = lipgloss.NewStyle().
|
|
BorderStyle(lipgloss.ThickBorder()).
|
|
Padding(0, 1, 0, 1).
|
|
BorderForeground(lipgloss.Color("63"))
|
|
|
|
var headerStyle = lipgloss.NewStyle().
|
|
Underline(true).
|
|
Bold(true).
|
|
PaddingBottom(1)
|
|
|
|
var leftStyle = lipgloss.NewStyle()
|
|
var rightStyle = lipgloss.NewStyle()
|
|
|
|
var longest int
|
|
for _, row := range rows {
|
|
if len(row[0]) > longest {
|
|
longest = len(row[0])
|
|
}
|
|
}
|
|
|
|
var renderedRows []string
|
|
for _, row := range rows {
|
|
if len(row) < 2 {
|
|
continue
|
|
}
|
|
|
|
if len(row) > 2 {
|
|
panic("CreateOverview: only accepts rows of len == 2")
|
|
}
|
|
|
|
lenOffset := 4
|
|
if len(row[0]) < longest {
|
|
lenOffset += longest - len(row[0])
|
|
}
|
|
|
|
offset := ""
|
|
for range lenOffset {
|
|
offset = offset + " "
|
|
}
|
|
|
|
rendered := horizontal(leftStyle.Render(row[0]), offset, rightStyle.Render(row[1]))
|
|
|
|
if row[1] == "---" {
|
|
rendered = horizontal(
|
|
leftStyle.
|
|
Bold(true).
|
|
Underline(true).
|
|
PaddingTop(1).
|
|
Render(row[0]),
|
|
offset,
|
|
rightStyle.Render(""),
|
|
)
|
|
}
|
|
|
|
renderedRows = append(renderedRows, rendered)
|
|
}
|
|
|
|
body := strings.Builder{}
|
|
body.WriteString(
|
|
borderStyle.Render(
|
|
lipgloss.JoinVertical(
|
|
lipgloss.Center,
|
|
headerStyle.Render(header),
|
|
lipgloss.JoinVertical(
|
|
lipgloss.Left,
|
|
renderedRows...,
|
|
),
|
|
),
|
|
),
|
|
)
|
|
|
|
return body.String()
|
|
}
|
|
|
|
// ToJSON converts a lipgloss.Table to JSON representation. It's not a robust
|
|
// implementation and mainly caters for our current use case which is basically
|
|
// a bunch of strings. See https://github.com/charmbracelet/lipgloss/issues/335
|
|
// for the real thing (hopefully).
|
|
func ToJSON(headers []string, rows [][]string) (string, error) {
|
|
var buff bytes.Buffer
|
|
|
|
buff.Write([]byte("["))
|
|
|
|
for idx, row := range rows {
|
|
payload := make(map[string]string)
|
|
|
|
for idx, header := range headers {
|
|
payload[strings.ToLower(header)] = row[idx]
|
|
}
|
|
|
|
serialized, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
buff.Write(serialized)
|
|
|
|
if idx < (len(rows) - 1) {
|
|
buff.Write([]byte(","))
|
|
}
|
|
}
|
|
|
|
buff.Write([]byte("]"))
|
|
|
|
return buff.String(), nil
|
|
}
|
|
|
|
// CreateProgressbar generates a progress bar
|
|
func CreateProgressbar(length int, title string) *progressbar.ProgressBar {
|
|
return progressbar.NewOptions(
|
|
length,
|
|
progressbar.OptionClearOnFinish(),
|
|
progressbar.OptionSetPredictTime(false),
|
|
progressbar.OptionShowCount(),
|
|
progressbar.OptionSetDescription(title),
|
|
)
|
|
}
|
|
|
|
// StripTagMeta strips front-matter image tag data that we don't need for parsing.
|
|
func StripTagMeta(image string) string {
|
|
originalImage := image
|
|
|
|
if strings.Contains(image, "docker.io") {
|
|
image = strings.Split(image, "/")[1]
|
|
}
|
|
|
|
if strings.Contains(image, "library") {
|
|
image = strings.Split(image, "/")[1]
|
|
}
|
|
|
|
if originalImage != image {
|
|
log.Debugf("stripped %s to %s for parsing", originalImage, image)
|
|
}
|
|
|
|
return image
|
|
}
|
|
|
|
// ByteCountSI presents a human friendly representation of a byte count. See
|
|
// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format.
|
|
func ByteCountSI(b uint64) string {
|
|
const unit = 1000
|
|
|
|
if b < unit {
|
|
return fmt.Sprintf("%d B", b)
|
|
}
|
|
|
|
div, exp := uint64(unit), 0
|
|
for n := b / unit; n >= unit; n /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
|
|
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
|
|
}
|
|
|
|
// BoldDirtyDefault ensures a dirty modifier is rendered in bold.
|
|
func BoldDirtyDefault(v string) string {
|
|
if strings.HasSuffix(v, config.DIRTY_DEFAULT) {
|
|
vBold := BoldStyle.Render(config.DIRTY_DEFAULT)
|
|
v = strings.Replace(v, config.DIRTY_DEFAULT, vBold, 1)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
// AddDirtyMarker adds the dirty marker to a version string.
|
|
func AddDirtyMarker(v string) string {
|
|
return fmt.Sprintf("%s%s", v, config.DIRTY_DEFAULT)
|
|
}
|