• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mindersec / minder / 24252456381

10 Apr 2026 04:12PM UTC coverage: 59.314% (-0.2%) from 59.505%
24252456381

push

github

web-flow
feat: enhance table UI (#6291)

* fixed the table output in CLI

Signed-off-by: DharunMR <maddharun56@gmail.com>

* improves history list

Signed-off-by: DharunMR <maddharun56@gmail.com>

* enhanced the table with dynamic resize using data in it

Signed-off-by: DharunMR <maddharun56@gmail.com>

* changed render to use cmd.Out

Signed-off-by: DharunMR <maddharun56@gmail.com>

* add changes as suggested

Signed-off-by: DharunMR <maddharun56@gmail.com>

---------

Signed-off-by: DharunMR <maddharun56@gmail.com>

0 of 158 new or added lines in 2 files covered. (0.0%)

3 existing lines in 1 file now uncovered.

19665 of 33154 relevant lines covered (59.31%)

36.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/internal/util/cli/table/table.go
1
// SPDX-FileCopyrightText: Copyright 2023 The Minder Authors
2
// SPDX-License-Identifier: Apache-2.0
3

4
package table
5

6
import (
7
        "io"
8
        "os"
9
        "strconv"
10
        "strings"
11
        "unicode/utf8"
12

13
        "github.com/jedib0t/go-pretty/v6/table"
14
        "github.com/jedib0t/go-pretty/v6/text"
15
        "golang.org/x/term"
16

17
        "github.com/mindersec/minder/internal/util/cli/table/layouts"
18
)
19

20
const (
21
        // Simple defines the standard table layout used across the Minder CLI.
22
        Simple               = "simple"
23
        defaultTerminalWidth = 100
24
)
25

26
// Table is the interface for rendering CLI tables using the go-pretty engine.
27
type Table interface {
28
        AddRow(row ...string)
29
        AddRowWithColor(row ...layouts.ColoredColumn)
30
        Render()
31
        // SeparateRows ensures each row is clearly separated (probably because it is multi-line)
32
        SeparateRows() Table
33
        SetAutoMerge(merge bool) Table
34
        SetEqualColumns(equal bool) Table
35
}
36

37
// New creates a new table.
NEW
38
func New(_ string, layout layouts.TableLayout, out io.Writer, header []string) Table {
×
NEW
39
        t := table.NewWriter()
×
NEW
40

×
NEW
41
        switch layout {
×
NEW
42
        case layouts.Condensed:
×
NEW
43
                t.SetStyle(table.StyleLight) // Thin lines for tight spaces
×
NEW
44
        case layouts.Heavy:
×
NEW
45
                t.SetStyle(table.StyleBold) // Thick lines for importance
×
NEW
46
        case layouts.Default:
×
NEW
47
                t.SetStyle(table.StyleRounded) // Rounded corner for table
×
NEW
48
        default:
×
NEW
49
                t.SetStyle(table.StyleRounded) // The standard Minder look
×
50
        }
51

NEW
52
        t.SetOutputMirror(out)
×
NEW
53

×
NEW
54
        headerRow := make(table.Row, len(header))
×
NEW
55
        maxColWidths := make([]int, len(header))
×
NEW
56

×
NEW
57
        for i, h := range header {
×
NEW
58
                headerRow[i] = h
×
NEW
59
                maxColWidths[i] = utf8.RuneCountInString(h)
×
60
        }
×
NEW
61
        t.AppendHeader(headerRow)
×
NEW
62

×
NEW
63
        t.Style().Options.DrawBorder = false
×
NEW
64
        t.Style().Options.SeparateColumns = true
×
NEW
65
        t.Style().Options.SeparateHeader = true
×
NEW
66
        t.Style().Options.SeparateRows = true
×
NEW
67
        t.Style().Box.PaddingLeft = " "
×
NEW
68
        t.Style().Box.PaddingRight = " "
×
NEW
69

×
NEW
70
        return &goPrettyTable{
×
NEW
71
                t:            t,
×
NEW
72
                numCols:      len(header),
×
NEW
73
                headers:      header,
×
NEW
74
                maxColWidths: maxColWidths,
×
UNCOV
75
        }
×
76
}
77

78
type goPrettyTable struct {
79
        t            table.Writer
80
        autoMerge    bool
81
        equalColumns bool
82
        numCols      int
83
        headers      []string
84
        maxColWidths []int
85
}
86

NEW
87
func (g *goPrettyTable) SetAutoMerge(merge bool) Table {
×
NEW
88
        g.autoMerge = merge
×
NEW
89
        return g
×
UNCOV
90
}
×
91

NEW
92
func (g *goPrettyTable) SetEqualColumns(equal bool) Table {
×
NEW
93
        g.equalColumns = equal
×
NEW
94
        return g
×
NEW
95
}
×
96

NEW
97
func (g *goPrettyTable) updateWidths(row []string) {
×
NEW
98
        for i, val := range row {
×
NEW
99
                if i >= g.numCols {
×
NEW
100
                        break
×
101
                }
NEW
102
                w := 0
×
NEW
103
                for _, line := range strings.Split(val, "\n") {
×
NEW
104
                        lw := utf8.RuneCountInString(line)
×
NEW
105
                        if lw > w {
×
NEW
106
                                w = lw
×
107
                        }
×
108
                }
NEW
109
                if w > g.maxColWidths[i] {
×
NEW
110
                        g.maxColWidths[i] = w
×
UNCOV
111
                }
×
112
        }
113
}
114

NEW
115
func (g *goPrettyTable) AddRow(row ...string) {
×
NEW
116
        r := make(table.Row, len(row))
×
NEW
117
        for i, val := range row {
×
NEW
118
                r[i] = val
×
NEW
119
        }
×
NEW
120
        g.updateWidths(row)
×
NEW
121
        g.t.AppendRow(r)
×
122
}
123

NEW
124
func (g *goPrettyTable) AddRowWithColor(row ...layouts.ColoredColumn) {
×
NEW
125
        r := make(table.Row, len(row))
×
NEW
126
        rawRow := make([]string, len(row))
×
NEW
127

×
NEW
128
        for i, cell := range row {
×
NEW
129
                val := cell.Column
×
NEW
130
                rawRow[i] = val
×
NEW
131

×
NEW
132
                switch cell.Color {
×
133
                case layouts.ColorRed:
×
NEW
134
                        val = text.FgRed.Sprint(val)
×
135
                case layouts.ColorGreen:
×
NEW
136
                        val = text.FgGreen.Sprint(val)
×
137
                case layouts.ColorYellow:
×
NEW
138
                        val = text.FgYellow.Sprint(val)
×
139
                }
NEW
140
                r[i] = val
×
141
        }
142

NEW
143
        g.updateWidths(rawRow)
×
NEW
144
        g.t.AppendRow(r)
×
145
}
146

NEW
147
func (g *goPrettyTable) SeparateRows() Table {
×
NEW
148
        g.t.Style().Options.SeparateRows = true
×
NEW
149
        return g
×
NEW
150
}
×
151

NEW
152
func (g *goPrettyTable) Render() {
×
NEW
153
        w := getTerminalWidth()
×
NEW
154

×
NEW
155
        // With DrawBorder = false and SeparateColumns = true:
×
NEW
156
        barsAndPadding := (g.numCols - 1) + (g.numCols * 2)
×
NEW
157
        usableWidth := w - barsAndPadding
×
NEW
158
        if usableWidth < 10 {
×
NEW
159
                usableWidth = 10
×
NEW
160
        }
×
161

NEW
162
        assignedWidths := make([]int, g.numCols)
×
NEW
163

×
NEW
164
        if g.equalColumns && g.numCols > 0 {
×
NEW
165
                equalWidth := usableWidth / g.numCols
×
NEW
166
                for i := range assignedWidths {
×
NEW
167
                        assignedWidths[i] = equalWidth
×
NEW
168
                }
×
NEW
169
                assignedWidths[g.numCols-1] += (usableWidth % g.numCols)
×
NEW
170
        } else {
×
NEW
171
                totalRequested := 0
×
NEW
172
                for _, req := range g.maxColWidths {
×
NEW
173
                        totalRequested += req
×
NEW
174
                }
×
175

NEW
176
                if totalRequested > 0 {
×
NEW
177
                        currentTotal := 0
×
NEW
178
                        for i, req := range g.maxColWidths {
×
NEW
179
                                // Calculate share: (column_req / total_req) * usable_width
×
NEW
180
                                share := int(float64(req) / float64(totalRequested) * float64(usableWidth))
×
NEW
181

×
NEW
182
                                // Ensure a minimum width so columns don't disappear
×
NEW
183
                                if share < 5 {
×
NEW
184
                                        share = 5
×
NEW
185
                                }
×
NEW
186
                                assignedWidths[i] = share
×
NEW
187
                                currentTotal += share
×
188
                        }
189

NEW
190
                        diff := usableWidth - currentTotal
×
NEW
191
                        if g.numCols > 0 {
×
NEW
192
                                if assignedWidths[g.numCols-1]+diff > 5 {
×
NEW
193
                                        assignedWidths[g.numCols-1] += diff
×
NEW
194
                                } else {
×
NEW
195
                                        assignedWidths[g.numCols-1] = 5
×
NEW
196
                                }
×
197
                        }
NEW
198
                } else {
×
NEW
199
                        // Fallback if no rows were added: default to equal
×
NEW
200
                        equalWidth := usableWidth / g.numCols
×
NEW
201
                        for i := range assignedWidths {
×
NEW
202
                                assignedWidths[i] = equalWidth
×
NEW
203
                        }
×
204
                }
205
        }
206

NEW
207
        configs := make([]table.ColumnConfig, len(g.headers))
×
NEW
208
        for i := range g.headers {
×
NEW
209
                configs[i] = table.ColumnConfig{
×
NEW
210
                        Number:           i + 1,
×
NEW
211
                        WidthMax:         assignedWidths[i],
×
NEW
212
                        WidthMin:         assignedWidths[i], // Forcing Min to match Max ensures full stretch
×
NEW
213
                        WidthMaxEnforcer: text.WrapSoft,
×
NEW
214
                        AutoMerge:        g.autoMerge,
×
NEW
215
                        VAlign:           text.VAlignTop,
×
NEW
216
                }
×
NEW
217
        }
×
218

NEW
219
        g.t.SetColumnConfigs(configs)
×
NEW
220

×
NEW
221
        g.t.Style().Size.WidthMax = w
×
222

×
NEW
223
        g.t.Render()
×
224
}
225

NEW
226
func getTerminalWidth() int {
×
NEW
227
        if cols := os.Getenv("COLUMNS"); cols != "" {
×
NEW
228
                if w, err := strconv.Atoi(cols); err == nil && w > 0 {
×
NEW
229
                        return w
×
NEW
230
                }
×
231
        }
232

233
        //nolint:gosec // G115
NEW
234
        if w, _, err := term.GetSize(int(os.Stdout.Fd())); err == nil && w > 0 {
×
NEW
235
                return w
×
NEW
236
        }
×
NEW
237
        return defaultTerminalWidth
×
238
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc