276 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			276 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package formatter
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"coopcloud.tech/abra/pkg/i18n"
 | 
						|
	"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)) + i18n.G(" 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(i18n.G("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.Top, 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(i18n.G("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.Debug(i18n.G("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)
 | 
						|
}
 |