mirror of https://github.com/docker/cli.git
cmd/image/tree: refactor
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
This commit is contained in:
parent
da9e984231
commit
6c86dcc219
|
@ -4,12 +4,9 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
|
@ -22,13 +19,6 @@ type treeOptions struct {
|
|||
filters filters.Args
|
||||
}
|
||||
|
||||
type treeView struct {
|
||||
images []topImage
|
||||
|
||||
// imageSpacing indicates whether there should be extra spacing between images.
|
||||
imageSpacing bool
|
||||
}
|
||||
|
||||
func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error {
|
||||
images, err := dockerCLI.Client().ImageList(ctx, imagetypes.ListOptions{
|
||||
All: opts.all,
|
||||
|
@ -39,28 +29,73 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
|
|||
return err
|
||||
}
|
||||
|
||||
view := treeView{
|
||||
images: make([]topImage, 0, len(images)),
|
||||
warningColor := aec.LightYellowF
|
||||
if !dockerCLI.Out().IsTerminal() {
|
||||
warningColor = noColor{}
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), warningColor.Apply("WARNING: This is an experimental feature. The output may change and shouldn't be depended on."))
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), "")
|
||||
|
||||
out := dockerCLI.Out()
|
||||
_, width := out.GetTtySize()
|
||||
if width == 0 {
|
||||
width = 80
|
||||
}
|
||||
if width < 20 {
|
||||
width = 20
|
||||
}
|
||||
|
||||
headerColor := aec.NewBuilder(aec.DefaultF, aec.Bold).ANSI
|
||||
topNameColor := aec.NewBuilder(aec.BlueF, aec.Bold).ANSI
|
||||
normalColor := aec.NewBuilder(aec.DefaultF).ANSI
|
||||
greenColor := aec.NewBuilder(aec.GreenF).ANSI
|
||||
untaggedColor := aec.NewBuilder(aec.Faint).ANSI
|
||||
if !out.IsTerminal() {
|
||||
headerColor = noColor{}
|
||||
topNameColor = noColor{}
|
||||
normalColor = noColor{}
|
||||
greenColor = noColor{}
|
||||
untaggedColor = noColor{}
|
||||
}
|
||||
|
||||
columns := buildTableColumns(int(width), greenColor)
|
||||
imageRows, anyImageHasChildren := buildTableRows(images)
|
||||
columns = formatColumnsForOutput(int(width), columns, imageRows)
|
||||
table := imageTreeTable{
|
||||
columns: columns,
|
||||
headerColor: headerColor,
|
||||
indexNameColor: topNameColor,
|
||||
untaggedColor: untaggedColor,
|
||||
normalColor: normalColor,
|
||||
spacing: anyImageHasChildren,
|
||||
}
|
||||
|
||||
table.printTable(out, imageRows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildTableRows(images []imagetypes.Summary) ([]ImageIndexRow, bool) {
|
||||
imageRows := make([]ImageIndexRow, 0, len(images))
|
||||
var hasChildren bool
|
||||
for _, img := range images {
|
||||
details := imageDetails{
|
||||
details := rowDetails{
|
||||
ID: img.ID,
|
||||
DiskUsage: units.HumanSizeWithPrecision(float64(img.Size), 3),
|
||||
InUse: img.Containers > 0,
|
||||
}
|
||||
|
||||
var totalContent int64
|
||||
children := make([]subImage, 0, len(img.Manifests))
|
||||
children := make([]ImageManifestRow, 0, len(img.Manifests))
|
||||
for _, im := range img.Manifests {
|
||||
if im.Kind != imagetypes.ManifestKindImage {
|
||||
continue
|
||||
}
|
||||
|
||||
im := im
|
||||
sub := subImage{
|
||||
sub := ImageManifestRow{
|
||||
Platform: platforms.Format(im.ImageData.Platform),
|
||||
Available: im.Available,
|
||||
Details: imageDetails{
|
||||
Details: rowDetails{
|
||||
ID: im.ID,
|
||||
DiskUsage: units.HumanSizeWithPrecision(float64(im.Size.Total), 3),
|
||||
InUse: len(im.ImageData.Containers) > 0,
|
||||
|
@ -77,12 +112,12 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
|
|||
children = append(children, sub)
|
||||
|
||||
// Add extra spacing between images if there's at least one entry with children.
|
||||
view.imageSpacing = true
|
||||
hasChildren = true
|
||||
}
|
||||
|
||||
details.ContentSize = units.HumanSizeWithPrecision(float64(totalContent), 3)
|
||||
|
||||
view.images = append(view.images, topImage{
|
||||
imageRows = append(imageRows, ImageIndexRow{
|
||||
Names: img.RepoTags,
|
||||
Details: details,
|
||||
Children: children,
|
||||
|
@ -90,64 +125,14 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
|
|||
})
|
||||
}
|
||||
|
||||
sort.Slice(view.images, func(i, j int) bool {
|
||||
return view.images[i].created > view.images[j].created
|
||||
sort.Slice(imageRows, func(i, j int) bool {
|
||||
return imageRows[i].created > imageRows[j].created
|
||||
})
|
||||
|
||||
return printImageTree(dockerCLI, view)
|
||||
return imageRows, hasChildren
|
||||
}
|
||||
|
||||
type imageDetails struct {
|
||||
ID string
|
||||
DiskUsage string
|
||||
InUse bool
|
||||
ContentSize string
|
||||
}
|
||||
|
||||
type topImage struct {
|
||||
Names []string
|
||||
Details imageDetails
|
||||
Children []subImage
|
||||
|
||||
created int64
|
||||
}
|
||||
|
||||
type subImage struct {
|
||||
Platform string
|
||||
Available bool
|
||||
Details imageDetails
|
||||
}
|
||||
|
||||
const columnSpacing = 3
|
||||
|
||||
func printImageTree(dockerCLI command.Cli, view treeView) error {
|
||||
out := dockerCLI.Out()
|
||||
_, width := out.GetTtySize()
|
||||
if width == 0 {
|
||||
width = 80
|
||||
}
|
||||
if width < 20 {
|
||||
width = 20
|
||||
}
|
||||
|
||||
warningColor := aec.LightYellowF
|
||||
headerColor := aec.NewBuilder(aec.DefaultF, aec.Bold).ANSI
|
||||
topNameColor := aec.NewBuilder(aec.BlueF, aec.Bold).ANSI
|
||||
normalColor := aec.NewBuilder(aec.DefaultF).ANSI
|
||||
greenColor := aec.NewBuilder(aec.GreenF).ANSI
|
||||
untaggedColor := aec.NewBuilder(aec.Faint).ANSI
|
||||
if !out.IsTerminal() {
|
||||
headerColor = noColor{}
|
||||
topNameColor = noColor{}
|
||||
normalColor = noColor{}
|
||||
greenColor = noColor{}
|
||||
warningColor = noColor{}
|
||||
untaggedColor = noColor{}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(out, warningColor.Apply("WARNING: This is an experimental feature. The output may change and shouldn't be depended on."))
|
||||
_, _ = fmt.Fprintln(out, "")
|
||||
|
||||
func buildTableColumns(ttyWidth int, usedColumnColor aec.ANSI) []imgColumn {
|
||||
columns := []imgColumn{
|
||||
{
|
||||
Title: "Image",
|
||||
|
@ -158,7 +143,7 @@ func printImageTree(dockerCLI command.Cli, view treeView) error {
|
|||
Title: "ID",
|
||||
Align: alignLeft,
|
||||
Width: 12,
|
||||
DetailsValue: func(d *imageDetails) string {
|
||||
DetailsValue: func(d *rowDetails) string {
|
||||
return stringid.TruncateID(d.ID)
|
||||
},
|
||||
},
|
||||
|
@ -166,7 +151,7 @@ func printImageTree(dockerCLI command.Cli, view treeView) error {
|
|||
Title: "Disk usage",
|
||||
Align: alignRight,
|
||||
Width: 10,
|
||||
DetailsValue: func(d *imageDetails) string {
|
||||
DetailsValue: func(d *rowDetails) string {
|
||||
return d.DiskUsage
|
||||
},
|
||||
},
|
||||
|
@ -174,7 +159,7 @@ func printImageTree(dockerCLI command.Cli, view treeView) error {
|
|||
Title: "Content size",
|
||||
Align: alignRight,
|
||||
Width: 12,
|
||||
DetailsValue: func(d *imageDetails) string {
|
||||
DetailsValue: func(d *rowDetails) string {
|
||||
return d.ContentSize
|
||||
},
|
||||
},
|
||||
|
@ -182,8 +167,8 @@ func printImageTree(dockerCLI command.Cli, view treeView) error {
|
|||
Title: "In Use",
|
||||
Align: alignCenter,
|
||||
Width: 6,
|
||||
Color: &greenColor,
|
||||
DetailsValue: func(d *imageDetails) string {
|
||||
Color: &usedColumnColor,
|
||||
DetailsValue: func(d *rowDetails) string {
|
||||
if d.InUse {
|
||||
return "✔"
|
||||
}
|
||||
|
@ -192,202 +177,5 @@ func printImageTree(dockerCLI command.Cli, view treeView) error {
|
|||
},
|
||||
}
|
||||
|
||||
nameWidth := int(width)
|
||||
for idx, h := range columns {
|
||||
if h.Width == 0 {
|
||||
continue
|
||||
}
|
||||
d := h.Width
|
||||
if idx > 0 {
|
||||
d += columnSpacing
|
||||
}
|
||||
// If the first column gets too short, remove remaining columns
|
||||
if nameWidth-d < 12 {
|
||||
columns = columns[:idx]
|
||||
break
|
||||
}
|
||||
nameWidth -= d
|
||||
}
|
||||
|
||||
images := view.images
|
||||
// Try to make the first column as narrow as possible
|
||||
widest := widestFirstColumnValue(columns, images)
|
||||
if nameWidth > widest {
|
||||
nameWidth = widest
|
||||
}
|
||||
columns[0].Width = nameWidth
|
||||
|
||||
// Print columns
|
||||
for i, h := range columns {
|
||||
if i > 0 {
|
||||
_, _ = fmt.Fprint(out, strings.Repeat(" ", columnSpacing))
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(out, h.Print(headerColor, strings.ToUpper(h.Title)))
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(out)
|
||||
|
||||
// Print images
|
||||
for _, img := range images {
|
||||
printNames(out, columns, img, topNameColor, untaggedColor)
|
||||
printDetails(out, columns, normalColor, img.Details)
|
||||
|
||||
if len(img.Children) > 0 || view.imageSpacing {
|
||||
_, _ = fmt.Fprintln(out)
|
||||
}
|
||||
printChildren(out, columns, img, normalColor)
|
||||
_, _ = fmt.Fprintln(out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printDetails(out *streams.Out, headers []imgColumn, defaultColor aec.ANSI, details imageDetails) {
|
||||
for _, h := range headers {
|
||||
if h.DetailsValue == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(out, strings.Repeat(" ", columnSpacing))
|
||||
clr := defaultColor
|
||||
if h.Color != nil {
|
||||
clr = *h.Color
|
||||
}
|
||||
val := h.DetailsValue(&details)
|
||||
_, _ = fmt.Fprint(out, h.Print(clr, val))
|
||||
}
|
||||
}
|
||||
|
||||
func printChildren(out *streams.Out, headers []imgColumn, img topImage, normalColor aec.ANSI) {
|
||||
for idx, sub := range img.Children {
|
||||
clr := normalColor
|
||||
if !sub.Available {
|
||||
clr = normalColor.With(aec.Faint)
|
||||
}
|
||||
|
||||
if idx != len(img.Children)-1 {
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(clr, "├─ "+sub.Platform))
|
||||
} else {
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(clr, "└─ "+sub.Platform))
|
||||
}
|
||||
|
||||
printDetails(out, headers, clr, sub.Details)
|
||||
_, _ = fmt.Fprintln(out, "")
|
||||
}
|
||||
}
|
||||
|
||||
func printNames(out *streams.Out, headers []imgColumn, img topImage, color, untaggedColor aec.ANSI) {
|
||||
if len(img.Names) == 0 {
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(untaggedColor, "<untagged>"))
|
||||
}
|
||||
|
||||
for nameIdx, name := range img.Names {
|
||||
if nameIdx != 0 {
|
||||
_, _ = fmt.Fprintln(out, "")
|
||||
}
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(color, name))
|
||||
}
|
||||
}
|
||||
|
||||
type alignment int
|
||||
|
||||
const (
|
||||
alignLeft alignment = iota
|
||||
alignCenter
|
||||
alignRight
|
||||
)
|
||||
|
||||
type imgColumn struct {
|
||||
Title string
|
||||
Width int
|
||||
Align alignment
|
||||
|
||||
DetailsValue func(*imageDetails) string
|
||||
Color *aec.ANSI
|
||||
}
|
||||
|
||||
func truncateRunes(s string, length int) string {
|
||||
runes := []rune(s)
|
||||
if len(runes) > length {
|
||||
return string(runes[:length-3]) + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (h imgColumn) Print(clr aec.ANSI, s string) string {
|
||||
switch h.Align {
|
||||
case alignCenter:
|
||||
return h.PrintC(clr, s)
|
||||
case alignRight:
|
||||
return h.PrintR(clr, s)
|
||||
case alignLeft:
|
||||
}
|
||||
return h.PrintL(clr, s)
|
||||
}
|
||||
|
||||
func (h imgColumn) PrintC(clr aec.ANSI, s string) string {
|
||||
ln := utf8.RuneCountInString(s)
|
||||
|
||||
if ln > h.Width {
|
||||
return clr.Apply(truncateRunes(s, h.Width))
|
||||
}
|
||||
|
||||
fill := h.Width - ln
|
||||
|
||||
l := fill / 2
|
||||
r := fill - l
|
||||
|
||||
return strings.Repeat(" ", l) + clr.Apply(s) + strings.Repeat(" ", r)
|
||||
}
|
||||
|
||||
func (h imgColumn) PrintL(clr aec.ANSI, s string) string {
|
||||
ln := utf8.RuneCountInString(s)
|
||||
if ln > h.Width {
|
||||
return clr.Apply(truncateRunes(s, h.Width))
|
||||
}
|
||||
|
||||
return clr.Apply(s) + strings.Repeat(" ", h.Width-ln)
|
||||
}
|
||||
|
||||
func (h imgColumn) PrintR(clr aec.ANSI, s string) string {
|
||||
ln := utf8.RuneCountInString(s)
|
||||
if ln > h.Width {
|
||||
return clr.Apply(truncateRunes(s, h.Width))
|
||||
}
|
||||
|
||||
return strings.Repeat(" ", h.Width-ln) + clr.Apply(s)
|
||||
}
|
||||
|
||||
type noColor struct{}
|
||||
|
||||
func (a noColor) With(_ ...aec.ANSI) aec.ANSI {
|
||||
return a
|
||||
}
|
||||
|
||||
func (a noColor) Apply(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (a noColor) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// widestFirstColumnValue calculates the width needed to fully display the image names and platforms.
|
||||
func widestFirstColumnValue(headers []imgColumn, images []topImage) int {
|
||||
width := len(headers[0].Title)
|
||||
for _, img := range images {
|
||||
for _, name := range img.Names {
|
||||
if len(name) > width {
|
||||
width = len(name)
|
||||
}
|
||||
}
|
||||
for _, sub := range img.Children {
|
||||
pl := len(sub.Platform) + len("└─ ")
|
||||
if pl > width {
|
||||
width = pl
|
||||
}
|
||||
}
|
||||
}
|
||||
return width
|
||||
return columns
|
||||
}
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/morikuni/aec"
|
||||
)
|
||||
|
||||
type imageTreeTable struct {
|
||||
columns []imgColumn
|
||||
|
||||
headerColor aec.ANSI
|
||||
indexNameColor aec.ANSI
|
||||
untaggedColor aec.ANSI
|
||||
normalColor aec.ANSI
|
||||
highlightColor aec.ANSI
|
||||
|
||||
spacing bool
|
||||
}
|
||||
|
||||
type ImageIndexRow struct {
|
||||
Names []string
|
||||
Details rowDetails
|
||||
Children []ImageManifestRow
|
||||
|
||||
created int64
|
||||
}
|
||||
|
||||
type ImageManifestRow struct {
|
||||
Platform string
|
||||
Available bool
|
||||
Highlight bool
|
||||
Details rowDetails
|
||||
}
|
||||
|
||||
// rowDetails is used by both ImageIndexRow and ImageManifestRow
|
||||
type rowDetails struct {
|
||||
ID string
|
||||
DiskUsage string
|
||||
InUse bool
|
||||
ContentSize string
|
||||
}
|
||||
|
||||
func (t *imageTreeTable) printTable(out io.Writer, imgs []ImageIndexRow) {
|
||||
t.printHeaders(out)
|
||||
for _, img := range imgs {
|
||||
t.printIndex(out, img)
|
||||
_, _ = fmt.Fprintln(out)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *imageTreeTable) printHeaders(out io.Writer) {
|
||||
for i, h := range t.columns {
|
||||
if i > 0 {
|
||||
_, _ = fmt.Fprint(out, strings.Repeat(" ", columnSpacing))
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(out, h.Print(t.headerColor, strings.ToUpper(h.Title)))
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(out)
|
||||
}
|
||||
|
||||
func (t *imageTreeTable) printIndex(out io.Writer, img ImageIndexRow) {
|
||||
// print the names for the index
|
||||
printNames(out, t.columns, img, t.indexNameColor, t.untaggedColor)
|
||||
// print the rest of the columns/details for the header
|
||||
printDetails(out, t.columns, t.normalColor, img.Details)
|
||||
|
||||
// print the manifest rows, with their details
|
||||
if len(img.Children) > 0 || t.spacing {
|
||||
_, _ = fmt.Fprintln(out)
|
||||
}
|
||||
|
||||
printChildren(out, t.columns, img, t.normalColor)
|
||||
}
|
||||
|
||||
func printDetails(out io.Writer, headers []imgColumn, defaultColor aec.ANSI, details rowDetails) {
|
||||
for _, h := range headers {
|
||||
if h.DetailsValue == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(out, strings.Repeat(" ", columnSpacing))
|
||||
clr := defaultColor
|
||||
if h.Color != nil {
|
||||
clr = *h.Color
|
||||
}
|
||||
val := h.DetailsValue(&details)
|
||||
_, _ = fmt.Fprint(out, h.Print(clr, val))
|
||||
}
|
||||
}
|
||||
|
||||
func printChildren(out io.Writer, headers []imgColumn, img ImageIndexRow, normalColor aec.ANSI) {
|
||||
for idx, sub := range img.Children {
|
||||
clr := normalColor
|
||||
if !sub.Available {
|
||||
clr = normalColor.With(aec.Faint)
|
||||
}
|
||||
if sub.Highlight {
|
||||
clr = normalColor.With(aec.Bold)
|
||||
}
|
||||
|
||||
if idx != len(img.Children)-1 {
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(clr, "├─ "+sub.Platform))
|
||||
} else {
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(clr, "└─ "+sub.Platform))
|
||||
}
|
||||
|
||||
printDetails(out, headers, clr, sub.Details)
|
||||
_, _ = fmt.Fprintln(out)
|
||||
}
|
||||
}
|
||||
|
||||
func printNames(out io.Writer, headers []imgColumn, img ImageIndexRow, color, untaggedColor aec.ANSI) {
|
||||
if len(img.Names) == 0 {
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(untaggedColor, "<untagged>"))
|
||||
}
|
||||
|
||||
for nameIdx, name := range img.Names {
|
||||
if nameIdx != 0 {
|
||||
_, _ = fmt.Fprintln(out)
|
||||
}
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(color, name))
|
||||
}
|
||||
}
|
||||
|
||||
type alignment int
|
||||
|
||||
const (
|
||||
alignLeft alignment = iota
|
||||
alignCenter
|
||||
alignRight
|
||||
)
|
||||
|
||||
type imgColumn struct {
|
||||
Title string
|
||||
Width int
|
||||
Align alignment
|
||||
|
||||
DetailsValue func(*rowDetails) string
|
||||
Color *aec.ANSI
|
||||
}
|
||||
|
||||
// formatColumnsForOutput resizes the table columns for the provided tty
|
||||
// size. The first column is made as narrow as possible, and columns are
|
||||
// removed from the table output if the tty is not wide enough to
|
||||
// accomodate the entire table.
|
||||
func formatColumnsForOutput(ttyWidth int, columns []imgColumn, images []ImageIndexRow) []imgColumn {
|
||||
nameWidth := ttyWidth
|
||||
for idx, h := range columns {
|
||||
if h.Width == 0 {
|
||||
continue
|
||||
}
|
||||
d := h.Width
|
||||
if idx > 0 {
|
||||
d += columnSpacing
|
||||
}
|
||||
// If the first column gets too short, remove remaining columns
|
||||
if nameWidth-d < 12 {
|
||||
columns = columns[:idx]
|
||||
break
|
||||
}
|
||||
nameWidth -= d
|
||||
}
|
||||
|
||||
// Try to make the first column as narrow as possible
|
||||
widest := widestFirstColumnValue(columns, images)
|
||||
if nameWidth > widest {
|
||||
nameWidth = widest
|
||||
}
|
||||
columns[0].Width = nameWidth
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
const columnSpacing = 3
|
||||
|
||||
func truncateRunes(s string, length int) string {
|
||||
runes := []rune(s)
|
||||
if len(runes) > length {
|
||||
return string(runes[:length-3]) + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (h imgColumn) Print(clr aec.ANSI, s string) string {
|
||||
switch h.Align {
|
||||
case alignCenter:
|
||||
return h.PrintC(clr, s)
|
||||
case alignRight:
|
||||
return h.PrintR(clr, s)
|
||||
case alignLeft:
|
||||
}
|
||||
return h.PrintL(clr, s)
|
||||
}
|
||||
|
||||
func (h imgColumn) PrintC(clr aec.ANSI, s string) string {
|
||||
ln := utf8.RuneCountInString(s)
|
||||
|
||||
if ln > h.Width {
|
||||
return clr.Apply(truncateRunes(s, h.Width))
|
||||
}
|
||||
|
||||
fill := h.Width - ln
|
||||
|
||||
l := fill / 2
|
||||
r := fill - l
|
||||
|
||||
return strings.Repeat(" ", l) + clr.Apply(s) + strings.Repeat(" ", r)
|
||||
}
|
||||
|
||||
func (h imgColumn) PrintL(clr aec.ANSI, s string) string {
|
||||
ln := utf8.RuneCountInString(s)
|
||||
if ln > h.Width {
|
||||
return clr.Apply(truncateRunes(s, h.Width))
|
||||
}
|
||||
|
||||
return clr.Apply(s) + strings.Repeat(" ", h.Width-ln)
|
||||
}
|
||||
|
||||
func (h imgColumn) PrintR(clr aec.ANSI, s string) string {
|
||||
ln := utf8.RuneCountInString(s)
|
||||
if ln > h.Width {
|
||||
return clr.Apply(truncateRunes(s, h.Width))
|
||||
}
|
||||
|
||||
return strings.Repeat(" ", h.Width-ln) + clr.Apply(s)
|
||||
}
|
||||
|
||||
type noColor struct{}
|
||||
|
||||
func (a noColor) With(_ ...aec.ANSI) aec.ANSI {
|
||||
return a
|
||||
}
|
||||
|
||||
func (a noColor) Apply(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (a noColor) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// widestFirstColumnValue calculates the width needed to fully display the image names and platforms.
|
||||
func widestFirstColumnValue(headers []imgColumn, images []ImageIndexRow) int {
|
||||
width := len(headers[0].Title)
|
||||
for _, img := range images {
|
||||
for _, name := range img.Names {
|
||||
if len(name) > width {
|
||||
width = len(name)
|
||||
}
|
||||
}
|
||||
for _, sub := range img.Children {
|
||||
pl := len(sub.Platform) + len("└─ ")
|
||||
if pl > width {
|
||||
width = pl
|
||||
}
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
Loading…
Reference in New Issue