remove github.com/buger/goterm dependency

this is just a few bytes of escape codes, there is no need to depend on
a library for it. While it is not a big one it still seems better to
just write it ourselves.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger
2025-03-28 16:29:54 +01:00
parent 4264bf6876
commit 8ef234aedd
17 changed files with 22 additions and 1047 deletions

19
cmd/podman/common/term.go Normal file
View File

@ -0,0 +1,19 @@
package common
import (
"os"
"golang.org/x/term"
)
// ClearScreen clears the screen and puts the cursor back to position 1,1
// Useful when printing output in an interval like podman stats.
// When the stdout is not a terminal this is a NOP.
func ClearScreen() {
// Only write escape sequences when the output is a terminal.
if term.IsTerminal(int(os.Stdout.Fd())) {
// terminal escape control sequence to clear screen ([2J)
// followed by putting the cursor to position 1,1 ([1;1H)
os.Stdout.WriteString("\033[2J\033[1;1H")
}
}

View File

@ -10,7 +10,6 @@ import (
"strings"
"time"
tm "github.com/buger/goterm"
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/report"
@ -267,9 +266,7 @@ func ps(cmd *cobra.Command, _ []string) error {
responses = append(responses, psReporter{r})
}
tm.Clear()
tm.MoveCursor(1, 1)
tm.Flush()
common.ClearScreen()
if err := headers(); err != nil {
return err

View File

@ -6,7 +6,6 @@ import (
"os"
"strconv"
tm "github.com/buger/goterm"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v5/cmd/podman/common"
@ -152,9 +151,7 @@ func outputStats(cmd *cobra.Command, reports []define.ContainerStats) error {
"PIDS": "PIDS",
})
if !statsOptions.NoReset {
tm.Clear()
tm.MoveCursor(1, 1)
tm.Flush()
common.ClearScreen()
}
stats := make([]containerStats, 0, len(reports))
for _, r := range reports {

View File

@ -6,7 +6,6 @@ import (
"os"
"time"
"github.com/buger/goterm"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v5/cmd/podman/common"
"github.com/containers/podman/v5/cmd/podman/registry"
@ -93,9 +92,7 @@ func stats(cmd *cobra.Command, args []string) error {
err = printJSONPodStats(reports)
} else {
if !statsOptions.NoReset {
goterm.Clear()
goterm.MoveCursor(1, 1)
goterm.Flush()
common.ClearScreen()
}
if report.OriginUser == rpt.Origin {
err = userTemplate(rpt, reports)

1
go.mod
View File

@ -9,7 +9,6 @@ require (
github.com/BurntSushi/toml v1.5.0
github.com/Microsoft/go-winio v0.6.2
github.com/blang/semver/v4 v4.0.0
github.com/buger/goterm v1.0.4
github.com/checkpoint-restore/checkpointctl v1.3.0
github.com/checkpoint-restore/go-criu/v7 v7.2.0
github.com/containernetworking/plugins v1.6.2

3
go.sum
View File

@ -28,8 +28,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -605,7 +603,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -1,2 +0,0 @@
*.swp
.idea

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2016 Leonid Bugaev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,119 +0,0 @@
## Description
This library provides basic building blocks for building advanced console UIs.
Initially created for [Gor](http://github.com/buger/gor).
Full API documentation: http://godoc.org/github.com/buger/goterm
## Basic usage
Full screen console app, printing current time:
```go
import (
tm "github.com/buger/goterm"
"time"
)
func main() {
tm.Clear() // Clear current screen
for {
// By moving cursor to top-left position we ensure that console output
// will be overwritten each time, instead of adding new.
tm.MoveCursor(1,1)
tm.Println("Current Time:", time.Now().Format(time.RFC1123))
tm.Flush() // Call it every time at the end of rendering
time.Sleep(time.Second)
}
}
```
This can be seen in [examples/time_example.go](examples/time_example.go). To
run it yourself, go into your `$GOPATH/src/github.com/buger/goterm` directory
and run `go run ./examples/time_example.go`
Print red bold message on white background:
```go
tm.Println(tm.Background(tm.Color(tm.Bold("Important header"), tm.RED), tm.WHITE))
```
Create box and move it to center of the screen:
```go
tm.Clear()
// Create Box with 30% width of current screen, and height of 20 lines
box := tm.NewBox(30|tm.PCT, 20, 0)
// Add some content to the box
// Note that you can add ANY content, even tables
fmt.Fprint(box, "Some box content")
// Move Box to approx center of the screen
tm.Print(tm.MoveTo(box.String(), 40|tm.PCT, 40|tm.PCT))
tm.Flush()
```
This can be found in [examples/box_example.go](examples/box_example.go).
Draw table:
```go
// Based on http://golang.org/pkg/text/tabwriter
totals := tm.NewTable(0, 10, 5, ' ', 0)
fmt.Fprintf(totals, "Time\tStarted\tActive\tFinished\n")
fmt.Fprintf(totals, "%s\t%d\t%d\t%d\n", "All", started, started-finished, finished)
tm.Println(totals)
tm.Flush()
```
This can be found in [examples/table_example.go](examples/table_example.go).
## Line charts
Chart example:
![screen shot 2013-07-09 at 5 05 37 pm](https://f.cloud.github.com/assets/14009/767676/e3dd35aa-e887-11e2-9cd2-f6451eb26adc.png)
```go
import (
tm "github.com/buger/goterm"
)
chart := tm.NewLineChart(100, 20)
data := new(tm.DataTable)
data.AddColumn("Time")
data.AddColumn("Sin(x)")
data.AddColumn("Cos(x+1)")
for i := 0.1; i < 10; i += 0.1 {
data.AddRow(i, math.Sin(i), math.Cos(i+1))
}
tm.Println(chart.Draw(data))
```
This can be found in [examples/chart_example.go](examples/chart_example.go).
Drawing 2 separate graphs in different scales. Each graph have its own Y axe.
```go
chart.Flags = tm.DRAW_INDEPENDENT
```
Drawing graph with relative scale (Grapwh draw starting from min value instead of zero)
```go
chart.Flags = tm.DRAW_RELATIVE
```

176
vendor/github.com/buger/goterm/box.go generated vendored
View File

@ -1,176 +0,0 @@
package goterm
import (
"bytes"
"regexp"
"strings"
_ "unicode/utf8"
)
const DEFAULT_BORDER = "- │ ┌ ┐ └ ┘"
// Box allows you to create independent parts of screen, with its own buffer and borders.
// Can be used for creating modal windows
//
// Generates boxes likes this:
// ┌--------┐
// │hello │
// │world │
// │ │
// └--------┘
//
type Box struct {
Buf *bytes.Buffer
Width int
Height int
// To get even padding: PaddingX ~= PaddingY*4
PaddingX int
PaddingY int
// Should contain 6 border pieces separated by spaces
//
// Example border:
// "- │ ┌ ┐ └ ┘"
Border string
Flags int // Not used now
}
// Create new Box.
// Width and height can be relative:
//
// // Create box with 50% with of current screen and 10 lines height
// box := tm.NewBox(50|tm.PCT, 10, 0)
//
func NewBox(width, height int, flags int) *Box {
width, height = GetXY(width, height)
box := new(Box)
box.Buf = new(bytes.Buffer)
box.Width = width
box.Height = height
box.Border = DEFAULT_BORDER
box.PaddingX = 1
box.PaddingY = 0
box.Flags = flags
return box
}
func (b *Box) Write(p []byte) (int, error) {
return b.Buf.Write(p)
}
var ANSI_RE = regexp.MustCompile(`\\0\d+\[\d+(?:;\d+)?m`)
// String renders Box
func (b *Box) String() (out string) {
borders := strings.Split(b.Border, " ")
lines := strings.Split(b.Buf.String(), "\n")
// Border + padding
prefix := borders[1] + strings.Repeat(" ", b.PaddingX)
suffix := strings.Repeat(" ", b.PaddingX) + borders[1]
offset := b.PaddingY + 1 // 1 is border width
// Content width without borders and padding
contentWidth := b.Width - (b.PaddingX+1)*2
for y := 0; y < b.Height; y++ {
var line string
switch {
// Draw borders for first line
case y == 0:
line = borders[2] + strings.Repeat(borders[0], b.Width-2) + borders[3]
// Draw borders for last line
case y == (b.Height - 1):
line = borders[4] + strings.Repeat(borders[0], b.Width-2) + borders[5]
// Draw top and bottom padding
case y <= b.PaddingY || y >= (b.Height-b.PaddingY):
line = borders[1] + strings.Repeat(" ", b.Width-2) + borders[1]
// Render content
default:
if len(lines) > y-offset {
line = lines[y-offset]
} else {
line = ""
}
r := []rune(line)
lastAnsii := ""
withoutAnsii := []rune{}
withOffset := []rune{}
i := 0
for {
if i >= len(r) {
break
}
if r[i] == 27 {
lastAnsii = ""
withOffset = append(withOffset, r[i])
lastAnsii += string(r[i])
i++
for {
i++
if i > len(r) {
break
}
withOffset = append(withOffset, r[i])
lastAnsii += string(r[i])
if r[i] == 'm' {
i++
break
}
}
}
if i >= len(r) {
break
}
withoutAnsii = append(withoutAnsii, r[i])
if len(withoutAnsii) <= contentWidth {
withOffset = append(withOffset, r[i])
}
i++
}
if len(withoutAnsii) > contentWidth {
// If line is too large limit it
line = string(withOffset)
} else {
// If line is too small enlarge it by adding spaces
line += strings.Repeat(" ", contentWidth-len(withoutAnsii))
}
if lastAnsii != "" {
line += RESET
}
line = prefix + line + suffix
}
// Don't add newline for last element
if y != b.Height-1 {
line += "\n"
}
out += line
}
return out
}

View File

@ -1,329 +0,0 @@
package goterm
import (
"fmt"
"math"
"strings"
"unicode/utf8"
)
const (
AXIS_LEFT = iota
AXIS_RIGHT
)
const (
DRAW_INDEPENDENT = 1 << iota
DRAW_RELATIVE
)
type DataTable struct {
columns []string
rows [][]float64
}
func (d *DataTable) AddColumn(name string) {
d.columns = append(d.columns, name)
}
func (d *DataTable) AddRow(elms ...float64) {
d.rows = append(d.rows, elms)
}
type Chart interface {
Draw(data DataTable, flags int) string
}
type LineChart struct {
Buf []string
chartBuf []string
data *DataTable
Width int
Height int
chartHeight int
chartWidth int
paddingX int
paddingY int
Flags int
}
func genBuf(size int) []string {
buf := make([]string, size)
for i := 0; i < size; i++ {
buf[i] = " "
}
return buf
}
// Format float
func ff(num interface{}) string {
return fmt.Sprintf("%.1f", num)
}
func NewLineChart(width, height int) *LineChart {
chart := new(LineChart)
chart.Width = width
chart.Height = height
chart.Buf = genBuf(width * height)
// axis lines + axies text
chart.paddingY = 2
return chart
}
func (c *LineChart) DrawAxes(maxX, minX, maxY, minY float64, index int) {
side := AXIS_LEFT
if c.Flags&DRAW_INDEPENDENT != 0 {
if index%2 == 0 {
side = AXIS_RIGHT
}
c.DrawLine(c.paddingX-1, 1, c.Width-c.paddingX, 1, "-")
} else {
c.DrawLine(c.paddingX-1, 1, c.Width-1, 1, "-")
}
if side == AXIS_LEFT {
c.DrawLine(c.paddingX-1, 1, c.paddingX-1, c.Height-1, "│")
} else {
c.DrawLine(c.Width-c.paddingX, 1, c.Width-c.paddingX, c.Height-1, "│")
}
left := 0
if side == AXIS_RIGHT {
left = c.Width - c.paddingX + 1
}
if c.Flags&DRAW_RELATIVE != 0 {
c.writeText(ff(minY), left, 1)
} else {
if minY > 0 {
c.writeText("0", left, 1)
} else {
c.writeText(ff(minY), left, 1)
}
}
c.writeText(ff(maxY), left, c.Height-1)
c.writeText(ff(minX), c.paddingX, 0)
x_col := c.data.columns[0]
c.writeText(c.data.columns[0], c.Width/2-utf8.RuneCountInString(x_col)/2, 1)
if c.Flags&DRAW_INDEPENDENT != 0 || len(c.data.columns) < 3 {
col := c.data.columns[index]
for idx, char := range strings.Split(col, "") {
start_from := c.Height/2 + len(col)/2 - idx
if side == AXIS_LEFT {
c.writeText(char, c.paddingX-1, start_from)
} else {
c.writeText(char, c.Width-c.paddingX, start_from)
}
}
}
if c.Flags&DRAW_INDEPENDENT != 0 {
c.writeText(ff(maxX), c.Width-c.paddingX-len(ff(maxX)), 0)
} else {
c.writeText(ff(maxX), c.Width-len(ff(maxX)), 0)
}
}
func (c *LineChart) writeText(text string, x, y int) {
coord := y*c.Width + x
for idx, char := range strings.Split(text, "") {
c.Buf[coord+idx] = char
}
}
func (c *LineChart) Draw(data *DataTable) (out string) {
var scaleY, scaleX float64
c.data = data
if c.Flags&DRAW_INDEPENDENT != 0 && len(data.columns) > 3 {
fmt.Println("Error: Can't use DRAW_INDEPENDENT for more then 2 graphs")
return ""
}
charts := len(data.columns) - 1
prevPoint := [2]int{-1, -1}
maxX, minX, maxY, minY := getBoundaryValues(data, -1)
c.paddingX = int(math.Max(float64(len(ff(minY))), float64(len(ff(maxY))))) + 1
c.chartHeight = c.Height - c.paddingY
if c.Flags&DRAW_INDEPENDENT != 0 {
c.chartWidth = c.Width - 2*c.paddingX
} else {
c.chartWidth = c.Width - c.paddingX - 1
}
scaleX = float64(c.chartWidth) / (maxX - minX)
if c.Flags&DRAW_RELATIVE != 0 || minY < 0 {
scaleY = float64(c.chartHeight) / (maxY - minY)
} else {
scaleY = float64(c.chartHeight) / maxY
}
for i := 1; i < charts+1; i++ {
if c.Flags&DRAW_INDEPENDENT != 0 {
maxX, minX, maxY, minY = getBoundaryValues(data, i)
scaleX = float64(c.chartWidth-1) / (maxX - minX)
scaleY = float64(c.chartHeight) / maxY
if c.Flags&DRAW_RELATIVE != 0 || minY < 0 {
scaleY = float64(c.chartHeight) / (maxY - minY)
}
}
symbol := Color("•", i)
c_data := getChartData(data, i)
for _, point := range c_data {
x := int((point[0]-minX)*scaleX) + c.paddingX
y := int((point[1])*scaleY) + c.paddingY
if c.Flags&DRAW_RELATIVE != 0 || minY < 0 {
y = int((point[1]-minY)*scaleY) + c.paddingY
}
if prevPoint[0] == -1 {
prevPoint[0] = x
prevPoint[1] = y
}
if prevPoint[0] <= x {
c.DrawLine(prevPoint[0], prevPoint[1], x, y, symbol)
}
prevPoint[0] = x
prevPoint[1] = y
}
c.DrawAxes(maxX, minX, maxY, minY, i)
}
for row := c.Height - 1; row >= 0; row-- {
out += strings.Join(c.Buf[row*c.Width:(row+1)*c.Width], "") + "\n"
}
return
}
func (c *LineChart) DrawLine(x0, y0, x1, y1 int, symbol string) {
drawLine(x0, y0, x1, y1, func(x, y int) {
coord := y*c.Width + x
if coord > 0 && coord < len(c.Buf) {
c.Buf[coord] = symbol
}
})
}
func getBoundaryValues(data *DataTable, index int) (maxX, minX, maxY, minY float64) {
maxX = math.Inf(-1)
minX = math.Inf(1)
maxY = math.Inf(-1)
minY = math.Inf(1)
for _, r := range data.rows {
maxX = math.Max(maxX, r[0])
minX = math.Min(minX, r[0])
for idx, c := range r {
if idx > 0 {
if index == -1 || index == idx {
maxY = math.Max(maxY, c)
minY = math.Min(minY, c)
}
}
}
}
if maxY > 0 {
maxY = maxY * 1.1
} else {
maxY = maxY * 0.9
}
if minY > 0 {
minY = minY * 0.9
} else {
minY = minY * 1.1
}
return
}
// DataTable can contain data for multiple graphs, we need to extract only 1
func getChartData(data *DataTable, index int) (out [][]float64) {
for _, r := range data.rows {
out = append(out, []float64{r[0], r[index]})
}
return
}
// Algorithm for drawing line between two points
//
// http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
func drawLine(x0, y0, x1, y1 int, plot func(int, int)) {
dx := x1 - x0
if dx < 0 {
dx = -dx
}
dy := y1 - y0
if dy < 0 {
dy = -dy
}
var sx, sy int
if x0 < x1 {
sx = 1
} else {
sx = -1
}
if y0 < y1 {
sy = 1
} else {
sy = -1
}
err := dx - dy
for {
plot(x0, y0)
if x0 == x1 && y0 == y1 {
break
}
e2 := 2 * err
if e2 > -dy {
err -= dy
x0 += sx
}
if e2 < dx {
err += dx
y0 += sy
}
}
}

View File

@ -1,34 +0,0 @@
package goterm
import (
"bytes"
"text/tabwriter"
)
// Tabwriter with own buffer:
//
// totals := tm.NewTable(0, 10, 5, ' ', 0)
// fmt.Fprintf(totals, "Time\tStarted\tActive\tFinished\n")
// fmt.Fprintf(totals, "%s\t%d\t%d\t%d\n", "All", started, started-finished, finished)
// tm.Println(totals)
//
// Based on http://golang.org/pkg/text/tabwriter
type Table struct {
tabwriter.Writer
Buf *bytes.Buffer
}
// Same as here http://golang.org/pkg/text/tabwriter/#Writer.Init
func NewTable(minwidth, tabwidth, padding int, padchar byte, flags uint) *Table {
tbl := new(Table)
tbl.Buf = new(bytes.Buffer)
tbl.Init(tbl.Buf, minwidth, tabwidth, padding, padchar, flags)
return tbl
}
func (t *Table) String() string {
t.Flush()
return t.Buf.String()
}

View File

@ -1,249 +0,0 @@
// Provides basic bulding blocks for advanced console UI
//
// Coordinate system:
//
// 1/1---X---->
// |
// Y
// |
// v
//
// Documentation for ANSI codes: http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
//
// Inspired by: http://www.darkcoding.net/software/pretty-command-line-console-output-on-unix-in-python-and-go-lang/
package goterm
import (
"bufio"
"bytes"
"fmt"
"os"
"strings"
)
// Reset all custom styles
const RESET = "\033[0m"
// Reset to default color
const RESET_COLOR = "\033[32m"
// Return cursor to start of line and clean it
const RESET_LINE = "\r\033[K"
// List of possible colors
const (
BLACK = iota
RED
GREEN
YELLOW
BLUE
MAGENTA
CYAN
WHITE
)
var Output *bufio.Writer = bufio.NewWriter(os.Stdout)
func getColor(code int) string {
return fmt.Sprintf("\033[3%dm", code)
}
func getBgColor(code int) string {
return fmt.Sprintf("\033[4%dm", code)
}
// Set percent flag: num | PCT
//
// Check percent flag: num & PCT
//
// Reset percent flag: num & 0xFF
const shift = uint(^uint(0)>>63) << 4
const PCT = 0x8000 << shift
type winsize struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}
// Global screen buffer
// Its not recommended write to buffer dirrectly, use package Print,Printf,Println fucntions instead.
var Screen *bytes.Buffer = new(bytes.Buffer)
// GetXY gets relative or absolute coordinates
// To get relative, set PCT flag to number:
//
// // Get 10% of total width to `x` and 20 to y
// x, y = tm.GetXY(10|tm.PCT, 20)
//
func GetXY(x int, y int) (int, int) {
if y == -1 {
y = CurrentHeight() + 1
}
if x&PCT != 0 {
x = int((x & 0xFF) * Width() / 100)
}
if y&PCT != 0 {
y = int((y & 0xFF) * Height() / 100)
}
return x, y
}
type sf func(int, string) string
// Apply given transformation func for each line in string
func applyTransform(str string, transform sf) (out string) {
out = ""
for idx, line := range strings.Split(str, "\n") {
out += transform(idx, line)
}
return
}
// Clear screen
func Clear() {
Output.WriteString("\033[2J")
}
// Move cursor to given position
func MoveCursor(x int, y int) {
fmt.Fprintf(Screen, "\033[%d;%dH", y, x)
}
// Move cursor up relative the current position
func MoveCursorUp(bias int) {
fmt.Fprintf(Screen, "\033[%dA", bias)
}
// Move cursor down relative the current position
func MoveCursorDown(bias int) {
fmt.Fprintf(Screen, "\033[%dB", bias)
}
// Move cursor forward relative the current position
func MoveCursorForward(bias int) {
fmt.Fprintf(Screen, "\033[%dC", bias)
}
// Move cursor backward relative the current position
func MoveCursorBackward(bias int) {
fmt.Fprintf(Screen, "\033[%dD", bias)
}
// Move string to possition
func MoveTo(str string, x int, y int) (out string) {
x, y = GetXY(x, y)
return applyTransform(str, func(idx int, line string) string {
return fmt.Sprintf("\033[%d;%dH%s", y+idx, x, line)
})
}
// ResetLine returns carrier to start of line
func ResetLine(str string) (out string) {
return applyTransform(str, func(idx int, line string) string {
return fmt.Sprintf("%s%s", RESET_LINE, line)
})
}
// Make bold
func Bold(str string) string {
return applyTransform(str, func(idx int, line string) string {
return fmt.Sprintf("\033[1m%s\033[0m", line)
})
}
// Apply given color to string:
//
// tm.Color("RED STRING", tm.RED)
//
func Color(str string, color int) string {
return applyTransform(str, func(idx int, line string) string {
return fmt.Sprintf("%s%s%s", getColor(color), line, RESET)
})
}
func Highlight(str, substr string, color int) string {
hiSubstr := Color(substr, color)
return strings.Replace(str, substr, hiSubstr, -1)
}
func HighlightRegion(str string, from, to, color int) string {
return str[:from] + Color(str[from:to], color) + str[to:]
}
// Change background color of string:
//
// tm.Background("string", tm.RED)
//
func Background(str string, color int) string {
return applyTransform(str, func(idx int, line string) string {
return fmt.Sprintf("%s%s%s", getBgColor(color), line, RESET)
})
}
// Width gets console width
func Width() int {
ws, err := getWinsize()
if err != nil {
return -1
}
return int(ws.Col)
}
// CurrentHeight gets current height. Line count in Screen buffer.
func CurrentHeight() int {
return strings.Count(Screen.String(), "\n")
}
// Flush buffer and ensure that it will not overflow screen
func Flush() {
for idx, str := range strings.SplitAfter(Screen.String(), "\n") {
if idx > Height() {
return
}
Output.WriteString(str)
}
Output.Flush()
Screen.Reset()
}
func Print(a ...interface{}) (n int, err error) {
return fmt.Fprint(Screen, a...)
}
func Println(a ...interface{}) (n int, err error) {
return fmt.Fprintln(Screen, a...)
}
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(Screen, format, a...)
}
func Context(data string, idx, max int) string {
var start, end int
if len(data[:idx]) < (max / 2) {
start = 0
} else {
start = idx - max/2
}
if len(data)-idx < (max / 2) {
end = len(data) - 1
} else {
end = idx + max/2
}
return data[start:end]
}

View File

@ -1,22 +0,0 @@
//go:build plan9 || solaris
// +build plan9 solaris
package goterm
func getWinsize() (*winsize, error) {
ws := new(winsize)
ws.Col = 80
ws.Row = 24
return ws, nil
}
// Height gets console height
func Height() int {
ws, err := getWinsize()
if err != nil {
return -1
}
return int(ws.Row)
}

View File

@ -1,36 +0,0 @@
//go:build !windows && !plan9 && !solaris
// +build !windows,!plan9,!solaris
package goterm
import (
"errors"
"math"
"os"
"golang.org/x/sys/unix"
)
func getWinsize() (*unix.Winsize, error) {
ws, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
if err != nil {
return nil, os.NewSyscallError("GetWinsize", err)
}
return ws, nil
}
// Height gets console height
func Height() int {
ws, err := getWinsize()
if err != nil {
// returns math.MinInt32 if we could not retrieve the height of console window,
// like VSCode debugging console
if errors.Is(err, unix.EOPNOTSUPP) {
return math.MinInt32
}
return -1
}
return int(ws.Row)
}

View File

@ -1,40 +0,0 @@
//go:build windows
// +build windows
package goterm
import (
"errors"
"math"
"os"
"golang.org/x/sys/windows"
)
func getWinsize() (*winsize, error) {
ws := new(winsize)
fd := os.Stdout.Fd()
var info windows.ConsoleScreenBufferInfo
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil {
return nil, err
}
ws.Col = uint16(info.Window.Right - info.Window.Left + 1)
ws.Row = uint16(info.Window.Bottom - info.Window.Top + 1)
return ws, nil
}
// Height gets console height
func Height() int {
ws, err := getWinsize()
if err != nil {
// returns math.MinInt32 if we could not retrieve the height of console window,
// like VSCode debugging console
if errors.Is(err, windows.WSAEOPNOTSUPP) {
return math.MinInt32
}
return -1
}
return int(ws.Row)
}

3
vendor/modules.txt vendored
View File

@ -60,9 +60,6 @@ github.com/asaskevich/govalidator
# github.com/blang/semver/v4 v4.0.0
## explicit; go 1.14
github.com/blang/semver/v4
# github.com/buger/goterm v1.0.4
## explicit; go 1.15
github.com/buger/goterm
# github.com/checkpoint-restore/checkpointctl v1.3.0
## explicit; go 1.21
github.com/checkpoint-restore/checkpointctl/lib