mirror of
https://github.com/charmbracelet/lipgloss.git
synced 2024-08-17 01:40:36 +03:00
Feature: Tables (#218)
* feat: tables * fix(table): examples * docs(table): `any` -> `string` * chore: go mod tidy * docs(table): update image * fix(table): lint * fix: remove binary * chore: color adjustments to pokemon example (#231) * fix(table): support rendering empty data sets * chore(table): simplify table's data interface * fix(table): correct GoDoc + add doc comments to Data methods --------- Co-authored-by: Christian Rocha <christian@rocha.is> Co-authored-by: Christian Muehlhaeuser <muesli@gmail.com>
This commit is contained in:
parent
408dcf3b9e
commit
4476263d05
53
README.md
53
README.md
@ -391,7 +391,6 @@ height := lipgloss.Height(block)
|
||||
w, h := lipgloss.Size(block)
|
||||
```
|
||||
|
||||
|
||||
### Placing Text in Whitespace
|
||||
|
||||
Sometimes you’ll simply want to place a block of text in whitespace.
|
||||
@ -411,6 +410,58 @@ block := lipgloss.Place(30, 80, lipgloss.Right, lipgloss.Bottom, fancyStyledPara
|
||||
|
||||
You can also style the whitespace. For details, see [the docs][docs].
|
||||
|
||||
### Rendering Tables
|
||||
|
||||
Lip Gloss ships with a table rendering sub-package.
|
||||
|
||||
```go
|
||||
import "github.com/charmbracelet/lipgloss/table"
|
||||
```
|
||||
|
||||
Define some rows of data.
|
||||
|
||||
```go
|
||||
rows := [][]string{
|
||||
{"Chinese", "您好", "你好"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Arabic", "أهلين", "أهلا"},
|
||||
{"Russian", "Здравствуйте", "Привет"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
```
|
||||
|
||||
Use the table package to style and render the table.
|
||||
|
||||
```go
|
||||
t := table.New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))).
|
||||
StyleFunc(func(row, col int) lipgloss.Style {
|
||||
switch {
|
||||
case row == 0:
|
||||
return HeaderStyle
|
||||
case row%2 == 0:
|
||||
return EvenRowStyle
|
||||
default:
|
||||
return OddRowStyle
|
||||
}
|
||||
}).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
// You can also add tables row-by-row
|
||||
t.Row("English", "You look absolutely fabulous.", "How's it going?")
|
||||
```
|
||||
|
||||
Print the table.
|
||||
|
||||
```go
|
||||
fmt.Println(t)
|
||||
```
|
||||
|
||||
![Table Example](https://github.com/charmbracelet/lipgloss/assets/42545625/6e4b70c4-f494-45da-a467-bdd27df30d5d)
|
||||
|
||||
For more on tables see [the docs](https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc).
|
||||
|
||||
***
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
module examples
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
replace github.com/charmbracelet/lipgloss => ../
|
||||
|
||||
@ -20,10 +20,11 @@ require (
|
||||
github.com/caarlos0/sshmarshal v0.1.0 // indirect
|
||||
github.com/charmbracelet/keygen v0.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
)
|
||||
|
110
examples/go.sum
110
examples/go.sum
@ -1,145 +1,47 @@
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA=
|
||||
github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I=
|
||||
github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA=
|
||||
github.com/charmbracelet/bubbletea v0.20.0/go.mod h1:zpkze1Rioo4rJELjRyGlm9T2YNou1Fm4LIJQSa5QMEM=
|
||||
github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBcsan2Y=
|
||||
github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM=
|
||||
github.com/charmbracelet/wish v0.5.0 h1:FkkdNBFqrLABR1ciNrAL2KCxoyWfKhXnIGZw6GfAtPg=
|
||||
github.com/charmbracelet/wish v0.5.0/go.mod h1:5GAn5SrDSZ7cgKjnC+3kDmiIo7I6k4/AYiRzC4+tpCk=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw=
|
||||
github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
|
||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
|
||||
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
40
examples/table/chess/main.go
Normal file
40
examples/table/chess/main.go
Normal file
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/lipgloss/table"
|
||||
)
|
||||
|
||||
func main() {
|
||||
re := lipgloss.NewRenderer(os.Stdout)
|
||||
labelStyle := re.NewStyle().Foreground(lipgloss.Color("241"))
|
||||
|
||||
board := [][]string{
|
||||
{"♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"},
|
||||
{"♟", "♟", "♟", "♟", "♟", "♟", "♟", "♟"},
|
||||
{" ", " ", " ", " ", " ", " ", " ", " "},
|
||||
{" ", " ", " ", " ", " ", " ", " ", " "},
|
||||
{" ", " ", " ", " ", " ", " ", " ", " "},
|
||||
{" ", " ", " ", " ", " ", " ", " ", " "},
|
||||
{"♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"},
|
||||
{"♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"},
|
||||
}
|
||||
|
||||
t := table.New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
BorderRow(true).
|
||||
BorderColumn(true).
|
||||
Rows(board...).
|
||||
StyleFunc(func(row, col int) lipgloss.Style {
|
||||
return lipgloss.NewStyle().Padding(0, 1)
|
||||
})
|
||||
|
||||
ranks := labelStyle.Render(strings.Join([]string{" A", "B", "C", "D", "E", "F", "G", "H "}, " "))
|
||||
files := labelStyle.Render(strings.Join([]string{" 1", "2", "3", "4", "5", "6", "7", "8 "}, "\n\n "))
|
||||
|
||||
fmt.Println(lipgloss.JoinVertical(lipgloss.Right, lipgloss.JoinHorizontal(lipgloss.Center, files, t.Render()), ranks) + "\n")
|
||||
}
|
29
examples/table/demo.tape
Normal file
29
examples/table/demo.tape
Normal file
@ -0,0 +1,29 @@
|
||||
Output table.gif
|
||||
|
||||
Set Height 900
|
||||
Set Width 1600
|
||||
Set Padding 80
|
||||
Set FontSize 42
|
||||
|
||||
Hide
|
||||
Type "go build -o table"
|
||||
Enter
|
||||
Ctrl+L
|
||||
Show
|
||||
|
||||
Sleep 0.5s
|
||||
Type "clear && ./table"
|
||||
Sleep 0.5s
|
||||
Enter
|
||||
Sleep 1s
|
||||
|
||||
Screenshot "table.png"
|
||||
|
||||
Sleep 1s
|
||||
|
||||
Hide
|
||||
Type "rm table"
|
||||
Enter
|
||||
Show
|
||||
|
||||
Sleep 1s
|
73
examples/table/languages/main.go
Normal file
73
examples/table/languages/main.go
Normal file
@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/lipgloss/table"
|
||||
)
|
||||
|
||||
const (
|
||||
purple = lipgloss.Color("99")
|
||||
gray = lipgloss.Color("245")
|
||||
lightGray = lipgloss.Color("241")
|
||||
)
|
||||
|
||||
func main() {
|
||||
re := lipgloss.NewRenderer(os.Stdout)
|
||||
|
||||
var (
|
||||
// HeaderStyle is the lipgloss style used for the table headers.
|
||||
HeaderStyle = re.NewStyle().Foreground(purple).Bold(true).Align(lipgloss.Center)
|
||||
// CellStyle is the base lipgloss style used for the table rows.
|
||||
CellStyle = re.NewStyle().Padding(0, 1).Width(14)
|
||||
// OddRowStyle is the lipgloss style used for odd-numbered table rows.
|
||||
OddRowStyle = CellStyle.Copy().Foreground(gray)
|
||||
// EvenRowStyle is the lipgloss style used for even-numbered table rows.
|
||||
EvenRowStyle = CellStyle.Copy().Foreground(lightGray)
|
||||
// BorderStyle is the lipgloss style used for the table border.
|
||||
BorderStyle = lipgloss.NewStyle().Foreground(purple)
|
||||
)
|
||||
|
||||
rows := [][]string{
|
||||
{"Chinese", "您好", "你好"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Arabic", "أهلين", "أهلا"},
|
||||
{"Russian", "Здравствуйте", "Привет"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
{"English", "You look absolutely fabulous.", "How's it going?"},
|
||||
}
|
||||
|
||||
t := table.New().
|
||||
Border(lipgloss.ThickBorder()).
|
||||
BorderStyle(BorderStyle).
|
||||
StyleFunc(func(row, col int) lipgloss.Style {
|
||||
var style lipgloss.Style
|
||||
|
||||
switch {
|
||||
case row == 0:
|
||||
return HeaderStyle
|
||||
case row%2 == 0:
|
||||
style = EvenRowStyle
|
||||
default:
|
||||
style = OddRowStyle
|
||||
}
|
||||
|
||||
// Make the second column a little wider.
|
||||
if col == 1 {
|
||||
style = style.Copy().Width(22)
|
||||
}
|
||||
|
||||
// Arabic is a right-to-left language, so right align the text.
|
||||
if rows[row-1][0] == "Arabic" && col != 0 {
|
||||
style = style.Copy().Align(lipgloss.Right)
|
||||
}
|
||||
|
||||
return style
|
||||
}).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
fmt.Println(t)
|
||||
}
|
65
examples/table/mindy/main.go
Normal file
65
examples/table/mindy/main.go
Normal file
@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/lipgloss/table"
|
||||
)
|
||||
|
||||
func main() {
|
||||
re := lipgloss.NewRenderer(os.Stdout)
|
||||
labelStyle := re.NewStyle().Width(3).Align(lipgloss.Right)
|
||||
swatchStyle := re.NewStyle().Width(6)
|
||||
|
||||
data := [][]string{}
|
||||
for i := 0; i < 13; i += 8 {
|
||||
data = append(data, makeRow(i, i+5))
|
||||
}
|
||||
data = append(data, makeEmptyRow())
|
||||
for i := 6; i < 15; i += 8 {
|
||||
data = append(data, makeRow(i, i+1))
|
||||
}
|
||||
data = append(data, makeEmptyRow())
|
||||
for i := 16; i < 231; i += 6 {
|
||||
data = append(data, makeRow(i, i+5))
|
||||
}
|
||||
data = append(data, makeEmptyRow())
|
||||
for i := 232; i < 256; i += 6 {
|
||||
data = append(data, makeRow(i, i+5))
|
||||
}
|
||||
|
||||
t := table.New().
|
||||
Border(lipgloss.HiddenBorder()).
|
||||
Rows(data...).
|
||||
StyleFunc(func(row, col int) lipgloss.Style {
|
||||
color := lipgloss.Color(fmt.Sprint(data[row-1][col-col%2]))
|
||||
switch {
|
||||
case col%2 == 0:
|
||||
return labelStyle.Foreground(color)
|
||||
default:
|
||||
return swatchStyle.Background(color)
|
||||
}
|
||||
})
|
||||
|
||||
fmt.Println(t)
|
||||
}
|
||||
|
||||
const rowLength = 12
|
||||
|
||||
func makeRow(start, end int) []string {
|
||||
var row []string
|
||||
for i := start; i <= end; i++ {
|
||||
row = append(row, fmt.Sprint(i))
|
||||
row = append(row, "")
|
||||
}
|
||||
for i := len(row); i < rowLength; i++ {
|
||||
row = append(row, "")
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
func makeEmptyRow() []string {
|
||||
return makeRow(0, -1)
|
||||
}
|
113
examples/table/pokemon/main.go
Normal file
113
examples/table/pokemon/main.go
Normal file
@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/lipgloss/table"
|
||||
)
|
||||
|
||||
func main() {
|
||||
re := lipgloss.NewRenderer(os.Stdout)
|
||||
baseStyle := re.NewStyle().Padding(0, 1)
|
||||
headerStyle := baseStyle.Copy().Foreground(lipgloss.Color("252")).Bold(true)
|
||||
selectedStyle := baseStyle.Copy().Foreground(lipgloss.Color("#01BE85")).Background(lipgloss.Color("#00432F"))
|
||||
typeColors := map[string]lipgloss.Color{
|
||||
"Bug": lipgloss.Color("#D7FF87"),
|
||||
"Electric": lipgloss.Color("#FDFF90"),
|
||||
"Fire": lipgloss.Color("#FF7698"),
|
||||
"Flying": lipgloss.Color("#FF87D7"),
|
||||
"Grass": lipgloss.Color("#75FBAB"),
|
||||
"Ground": lipgloss.Color("#FF875F"),
|
||||
"Normal": lipgloss.Color("#929292"),
|
||||
"Poison": lipgloss.Color("#7D5AFC"),
|
||||
"Water": lipgloss.Color("#00E2C7"),
|
||||
}
|
||||
dimTypeColors := map[string]lipgloss.Color{
|
||||
"Bug": lipgloss.Color("#97AD64"),
|
||||
"Electric": lipgloss.Color("#FCFF5F"),
|
||||
"Fire": lipgloss.Color("#BA5F75"),
|
||||
"Flying": lipgloss.Color("#C97AB2"),
|
||||
"Grass": lipgloss.Color("#59B980"),
|
||||
"Ground": lipgloss.Color("#C77252"),
|
||||
"Normal": lipgloss.Color("#727272"),
|
||||
"Poison": lipgloss.Color("#634BD0"),
|
||||
"Water": lipgloss.Color("#439F8E"),
|
||||
}
|
||||
|
||||
headers := []any{"#", "Name", "Type 1", "Type 2", "Japanese", "Official Rom."}
|
||||
data := [][]string{
|
||||
{"1", "Bulbasaur", "Grass", "Poison", "フシギダネ", "Bulbasaur"},
|
||||
{"2", "Ivysaur", "Grass", "Poison", "フシギソウ", "Ivysaur"},
|
||||
{"3", "Venusaur", "Grass", "Poison", "フシギバナ", "Venusaur"},
|
||||
{"4", "Charmander", "Fire", "", "ヒトカゲ", "Hitokage"},
|
||||
{"5", "Charmeleon", "Fire", "", "リザード", "Lizardo"},
|
||||
{"6", "Charizard", "Fire", "Flying", "リザードン", "Lizardon"},
|
||||
{"7", "Squirtle", "Water", "", "ゼニガメ", "Zenigame"},
|
||||
{"8", "Wartortle", "Water", "", "カメール", "Kameil"},
|
||||
{"9", "Blastoise", "Water", "", "カメックス", "Kamex"},
|
||||
{"10", "Caterpie", "Bug", "", "キャタピー", "Caterpie"},
|
||||
{"11", "Metapod", "Bug", "", "トランセル", "Trancell"},
|
||||
{"12", "Butterfree", "Bug", "Flying", "バタフリー", "Butterfree"},
|
||||
{"13", "Weedle", "Bug", "Poison", "ビードル", "Beedle"},
|
||||
{"14", "Kakuna", "Bug", "Poison", "コクーン", "Cocoon"},
|
||||
{"15", "Beedrill", "Bug", "Poison", "スピアー", "Spear"},
|
||||
{"16", "Pidgey", "Normal", "Flying", "ポッポ", "Poppo"},
|
||||
{"17", "Pidgeotto", "Normal", "Flying", "ピジョン", "Pigeon"},
|
||||
{"18", "Pidgeot", "Normal", "Flying", "ピジョット", "Pigeot"},
|
||||
{"19", "Rattata", "Normal", "", "コラッタ", "Koratta"},
|
||||
{"20", "Raticate", "Normal", "", "ラッタ", "Ratta"},
|
||||
{"21", "Spearow", "Normal", "Flying", "オニスズメ", "Onisuzume"},
|
||||
{"22", "Fearow", "Normal", "Flying", "オニドリル", "Onidrill"},
|
||||
{"23", "Ekans", "Poison", "", "アーボ", "Arbo"},
|
||||
{"24", "Arbok", "Poison", "", "アーボック", "Arbok"},
|
||||
{"25", "Pikachu", "Electric", "", "ピカチュウ", "Pikachu"},
|
||||
{"26", "Raichu", "Electric", "", "ライチュウ", "Raichu"},
|
||||
{"27", "Sandshrew", "Ground", "", "サンド", "Sand"},
|
||||
{"28", "Sandslash", "Ground", "", "サンドパン", "Sandpan"},
|
||||
}
|
||||
|
||||
CapitalizeHeaders := func(data []any) []any {
|
||||
for i := range data {
|
||||
data[i] = strings.ToUpper(data[i].(string))
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
t := table.New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
BorderStyle(re.NewStyle().Foreground(lipgloss.Color("238"))).
|
||||
Headers(CapitalizeHeaders(headers)...).
|
||||
Width(80).
|
||||
Rows(data...).
|
||||
StyleFunc(func(row, col int) lipgloss.Style {
|
||||
if row == 0 {
|
||||
return headerStyle
|
||||
}
|
||||
|
||||
if data[row-1][1] == "Pikachu" {
|
||||
return selectedStyle
|
||||
}
|
||||
|
||||
even := row%2 == 0
|
||||
|
||||
switch col {
|
||||
case 2, 3: // Type 1 + 2
|
||||
c := typeColors
|
||||
if even {
|
||||
c = dimTypeColors
|
||||
}
|
||||
|
||||
color := c[fmt.Sprint(data[row-1][col])]
|
||||
return baseStyle.Copy().Foreground(color)
|
||||
}
|
||||
|
||||
if even {
|
||||
return baseStyle.Copy().Foreground(lipgloss.Color("245"))
|
||||
}
|
||||
return baseStyle.Copy().Foreground(lipgloss.Color("252"))
|
||||
})
|
||||
fmt.Println(t)
|
||||
}
|
3
go.mod
3
go.mod
@ -8,6 +8,7 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.15
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/muesli/termenv v0.15.2
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
)
|
||||
|
||||
require (
|
||||
@ -15,5 +16,5 @@ require (
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
)
|
||||
|
7
go.sum
7
go.sum
@ -5,7 +5,6 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
@ -15,6 +14,8 @@ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1n
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
113
table/rows.go
Normal file
113
table/rows.go
Normal file
@ -0,0 +1,113 @@
|
||||
package table
|
||||
|
||||
// Data is the interface that wraps the basic methods of a table model.
|
||||
type Data interface {
|
||||
// At returns the contents of the cell at the given index.
|
||||
At(row, cell int) string
|
||||
|
||||
// Rows returns the number of rows in the table.
|
||||
Rows() int
|
||||
|
||||
// Columns returns the number of columns in the table.
|
||||
Columns() int
|
||||
}
|
||||
|
||||
// StringData is a string-based implementation of the Data interface.
|
||||
type StringData struct {
|
||||
rows [][]string
|
||||
columns int
|
||||
}
|
||||
|
||||
// NewStringData creates a new StringData with the given number of columns.
|
||||
func NewStringData(rows ...[]string) *StringData {
|
||||
m := StringData{columns: 0}
|
||||
|
||||
for _, row := range rows {
|
||||
m.columns = max(m.columns, len(row))
|
||||
m.rows = append(m.rows, row)
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
// Append appends the given row to the table.
|
||||
func (m *StringData) Append(row []string) {
|
||||
m.columns = max(m.columns, len(row))
|
||||
m.rows = append(m.rows, row)
|
||||
}
|
||||
|
||||
// At returns the contents of the cell at the given index.
|
||||
func (m *StringData) At(row, cell int) string {
|
||||
if row >= len(m.rows) || cell >= len(m.rows[row]) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return m.rows[row][cell]
|
||||
}
|
||||
|
||||
// Columns returns the number of columns in the table.
|
||||
func (m *StringData) Columns() int {
|
||||
return m.columns
|
||||
}
|
||||
|
||||
// Item appends the given row to the table.
|
||||
func (m *StringData) Item(rows ...string) *StringData {
|
||||
m.columns = max(m.columns, len(rows))
|
||||
m.rows = append(m.rows, rows)
|
||||
return m
|
||||
}
|
||||
|
||||
// Rows returns the number of rows in the table.
|
||||
func (m *StringData) Rows() int {
|
||||
return len(m.rows)
|
||||
}
|
||||
|
||||
// Filter applies a filter on some data.
|
||||
type Filter struct {
|
||||
data Data
|
||||
filter func(row int) bool
|
||||
}
|
||||
|
||||
// NewFilter initializes a new Filter.
|
||||
func NewFilter(data Data) *Filter {
|
||||
return &Filter{data: data}
|
||||
}
|
||||
|
||||
// Filter applies the given filter function to the data.
|
||||
func (m *Filter) Filter(f func(row int) bool) *Filter {
|
||||
m.filter = f
|
||||
return m
|
||||
}
|
||||
|
||||
// Row returns the row at the given index.
|
||||
func (m *Filter) At(row, cell int) string {
|
||||
j := 0
|
||||
for i := 0; i < m.data.Rows(); i++ {
|
||||
if m.filter(i) {
|
||||
if j == row {
|
||||
return m.data.At(i, cell)
|
||||
}
|
||||
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Columns returns the number of columns in the table.
|
||||
func (m *Filter) Columns() int {
|
||||
return m.data.Columns()
|
||||
}
|
||||
|
||||
// Rows returns the number of rows in the table.
|
||||
func (m *Filter) Rows() int {
|
||||
j := 0
|
||||
for i := 0; i < m.data.Rows(); i++ {
|
||||
if m.filter(i) {
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
return j
|
||||
}
|
522
table/table.go
Normal file
522
table/table.go
Normal file
@ -0,0 +1,522 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// StyleFunc is the style function that determines the style of a Cell.
|
||||
//
|
||||
// It takes the row and column of the cell as an input and determines the
|
||||
// lipgloss Style to use for that cell position.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// t := table.New().
|
||||
// Headers("Name", "Age").
|
||||
// Row("Kini", 4).
|
||||
// Row("Eli", 1).
|
||||
// Row("Iris", 102).
|
||||
// StyleFunc(func(row, col int) lipgloss.Style {
|
||||
// switch {
|
||||
// case row == 0:
|
||||
// return HeaderStyle
|
||||
// case row%2 == 0:
|
||||
// return EvenRowStyle
|
||||
// default:
|
||||
// return OddRowStyle
|
||||
// }
|
||||
// })
|
||||
type StyleFunc func(row, col int) lipgloss.Style
|
||||
|
||||
// DefaultStyles is a TableStyleFunc that returns a new Style with no attributes.
|
||||
func DefaultStyles(_, _ int) lipgloss.Style {
|
||||
return lipgloss.NewStyle()
|
||||
}
|
||||
|
||||
// Table is a type for rendering tables.
|
||||
type Table struct {
|
||||
styleFunc StyleFunc
|
||||
border lipgloss.Border
|
||||
|
||||
borderTop bool
|
||||
borderBottom bool
|
||||
borderLeft bool
|
||||
borderRight bool
|
||||
borderHeader bool
|
||||
borderColumn bool
|
||||
borderRow bool
|
||||
|
||||
borderStyle lipgloss.Style
|
||||
headers []any
|
||||
data Data
|
||||
|
||||
width int
|
||||
height int
|
||||
offset int
|
||||
|
||||
// widths tracks the width of each column.
|
||||
widths []int
|
||||
|
||||
// heights tracks the height of each row.
|
||||
heights []int
|
||||
}
|
||||
|
||||
// New returns a new Table that can be modified through different
|
||||
// attributes.
|
||||
//
|
||||
// By default, a table has no border, no styling, and no rows.
|
||||
func New() *Table {
|
||||
return &Table{
|
||||
styleFunc: DefaultStyles,
|
||||
border: lipgloss.RoundedBorder(),
|
||||
borderBottom: true,
|
||||
borderColumn: true,
|
||||
borderHeader: true,
|
||||
borderLeft: true,
|
||||
borderRight: true,
|
||||
borderTop: true,
|
||||
data: NewStringData(),
|
||||
}
|
||||
}
|
||||
|
||||
// ClearRows clears the table rows.
|
||||
func (t *Table) ClearRows() *Table {
|
||||
t.data = nil
|
||||
return t
|
||||
}
|
||||
|
||||
// StyleFunc sets the style for a cell based on it's position (row, column).
|
||||
func (t *Table) StyleFunc(style StyleFunc) *Table {
|
||||
t.styleFunc = style
|
||||
return t
|
||||
}
|
||||
|
||||
// style returns the style for a cell based on it's position (row, column).
|
||||
func (t *Table) style(row, col int) lipgloss.Style {
|
||||
if t.styleFunc == nil {
|
||||
return lipgloss.NewStyle()
|
||||
}
|
||||
return t.styleFunc(row, col)
|
||||
}
|
||||
|
||||
// Data sets the table data.
|
||||
func (t *Table) Data(data Data) *Table {
|
||||
t.data = data
|
||||
return t
|
||||
}
|
||||
|
||||
// Rows appends rows to the table data.
|
||||
func (t *Table) Rows(rows ...[]string) *Table {
|
||||
for _, row := range rows {
|
||||
switch t.data.(type) {
|
||||
case *StringData:
|
||||
t.data.(*StringData).Append(row)
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Row appends a row to the table data.
|
||||
func (t *Table) Row(row ...string) *Table {
|
||||
switch t.data.(type) {
|
||||
case *StringData:
|
||||
t.data.(*StringData).Append(row)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Headers sets the table headers.
|
||||
func (t *Table) Headers(headers ...any) *Table {
|
||||
t.headers = headers
|
||||
return t
|
||||
}
|
||||
|
||||
// Border sets the table border.
|
||||
func (t *Table) Border(border lipgloss.Border) *Table {
|
||||
t.border = border
|
||||
return t
|
||||
}
|
||||
|
||||
// BorderTop sets the top border.
|
||||
func (t *Table) BorderTop(v bool) *Table {
|
||||
t.borderTop = v
|
||||
return t
|
||||
}
|
||||
|
||||
// BorderBottom sets the bottom border.
|
||||
func (t *Table) BorderBottom(v bool) *Table {
|
||||
t.borderBottom = v
|
||||
return t
|
||||
}
|
||||
|
||||
// BorderLeft sets the left border.
|
||||
func (t *Table) BorderLeft(v bool) *Table {
|
||||
t.borderLeft = v
|
||||
return t
|
||||
}
|
||||
|
||||
// BorderRight sets the right border.
|
||||
func (t *Table) BorderRight(v bool) *Table {
|
||||
t.borderRight = v
|
||||
return t
|
||||
}
|
||||
|
||||
// BorderHeader sets the header separator border.
|
||||
func (t *Table) BorderHeader(v bool) *Table {
|
||||
t.borderHeader = v
|
||||
return t
|
||||
}
|
||||
|
||||
// BorderColumn sets the column border separator.
|
||||
func (t *Table) BorderColumn(v bool) *Table {
|
||||
t.borderColumn = v
|
||||
return t
|
||||
}
|
||||
|
||||
// BorderRow sets the row border separator.
|
||||
func (t *Table) BorderRow(v bool) *Table {
|
||||
t.borderRow = v
|
||||
return t
|
||||
}
|
||||
|
||||
// BorderStyle sets the style for the table border.
|
||||
func (t *Table) BorderStyle(style lipgloss.Style) *Table {
|
||||
t.borderStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// Width sets the table width, this auto-sizes the columns to fit the width by
|
||||
// either expanding or contracting the widths of each column as a best effort
|
||||
// approach.
|
||||
func (t *Table) Width(w int) *Table {
|
||||
t.width = w
|
||||
return t
|
||||
}
|
||||
|
||||
// Height sets the table height.
|
||||
func (t *Table) Height(h int) *Table {
|
||||
t.height = h
|
||||
return t
|
||||
}
|
||||
|
||||
// Offset sets the table rendering offset.
|
||||
func (t *Table) Offset(o int) *Table {
|
||||
t.offset = o
|
||||
return t
|
||||
}
|
||||
|
||||
// String returns the table as a string.
|
||||
func (t *Table) String() string {
|
||||
hasHeaders := t.headers != nil && len(t.headers) > 0
|
||||
hasRows := t.data != nil && t.data.Rows() > 0
|
||||
|
||||
if !hasHeaders && !hasRows {
|
||||
return ""
|
||||
}
|
||||
|
||||
var s strings.Builder
|
||||
|
||||
// Add empty cells to the headers, until it's the same length as the longest
|
||||
// row (only if there are at headers in the first place).
|
||||
if hasHeaders {
|
||||
for i := len(t.headers); i < t.data.Columns(); i++ {
|
||||
t.headers = append(t.headers, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the widths.
|
||||
t.widths = make([]int, max(len(t.headers), t.data.Columns()))
|
||||
t.heights = make([]int, btoi(hasHeaders)+t.data.Rows())
|
||||
|
||||
// The style function may affect width of the table. It's possible to set
|
||||
// the StyleFunc after the headers and rows. Update the widths for a final
|
||||
// time.
|
||||
for i, cell := range t.headers {
|
||||
t.widths[i] = max(t.widths[i], lipgloss.Width(t.style(0, i).Render(fmt.Sprint(cell))))
|
||||
t.heights[0] = max(t.heights[0], lipgloss.Height(t.style(0, i).Render(fmt.Sprint(cell))))
|
||||
}
|
||||
|
||||
for r := 0; r < t.data.Rows(); r++ {
|
||||
for i := 0; i < t.data.Columns(); i++ {
|
||||
cell := t.data.At(r, i)
|
||||
|
||||
rendered := t.style(r+1, i).Render(cell)
|
||||
t.heights[r+btoi(hasHeaders)] = max(t.heights[r+btoi(hasHeaders)], lipgloss.Height(rendered))
|
||||
t.widths[i] = max(t.widths[i], lipgloss.Width(rendered))
|
||||
}
|
||||
}
|
||||
|
||||
// Table Resizing Logic.
|
||||
//
|
||||
// Given a user defined table width, we must ensure the table is exactly that
|
||||
// width. This must account for all borders, column, separators, and column
|
||||
// data.
|
||||
//
|
||||
// In the case where the table is narrower than the specified table width,
|
||||
// we simply expand the columns evenly to fit the width.
|
||||
// For example, a table with 3 columns takes up 50 characters total, and the
|
||||
// width specified is 80, we expand each column by 10 characters, adding 30
|
||||
// to the total width.
|
||||
//
|
||||
// In the case where the table is wider than the specified table width, we
|
||||
// _could_ simply shrink the columns evenly but this would result in data
|
||||
// being truncated (perhaps unnecessarily). The naive approach could result
|
||||
// in very poor cropping of the table data. So, instead of shrinking columns
|
||||
// evenly, we calculate the median non-whitespace length of each column, and
|
||||
// shrink the columns based on the largest median.
|
||||
//
|
||||
// For example,
|
||||
// ┌──────┬───────────────┬──────────┐
|
||||
// │ Name │ Age of Person │ Location │
|
||||
// ├──────┼───────────────┼──────────┤
|
||||
// │ Kini │ 40 │ New York │
|
||||
// │ Eli │ 30 │ London │
|
||||
// │ Iris │ 20 │ Paris │
|
||||
// └──────┴───────────────┴──────────┘
|
||||
//
|
||||
// Median non-whitespace length vs column width of each column:
|
||||
//
|
||||
// Name: 4 / 5
|
||||
// Age of Person: 2 / 15
|
||||
// Location: 6 / 10
|
||||
//
|
||||
// The biggest difference is 15 - 2, so we can shrink the 2nd column by 13.
|
||||
|
||||
width := t.computeWidth()
|
||||
|
||||
if width < t.width && t.width > 0 {
|
||||
// Table is too narrow, expand the columns evenly until it reaches the
|
||||
// desired width.
|
||||
var i int
|
||||
for width < t.width {
|
||||
t.widths[i]++
|
||||
width++
|
||||
i = (i + 1) % len(t.widths)
|
||||
}
|
||||
} else if width > t.width && t.width > 0 {
|
||||
// Table is too wide, calculate the median non-whitespace length of each
|
||||
// column, and shrink the columns based on the largest difference.
|
||||
columnMedians := make([]int, len(t.widths))
|
||||
for c := range t.widths {
|
||||
trimmedWidth := make([]int, t.data.Rows())
|
||||
for r := 0; r < t.data.Rows(); r++ {
|
||||
renderedCell := t.style(r+btoi(hasHeaders), c).Render(t.data.At(r, c))
|
||||
nonWhitespaceChars := lipgloss.Width(strings.TrimRight(renderedCell, " "))
|
||||
trimmedWidth[r] = nonWhitespaceChars + 1
|
||||
}
|
||||
|
||||
columnMedians[c] = median(trimmedWidth)
|
||||
}
|
||||
|
||||
// Find the biggest differences between the median and the column width.
|
||||
// Shrink the columns based on the largest difference.
|
||||
differences := make([]int, len(t.widths))
|
||||
for i := range t.widths {
|
||||
differences[i] = t.widths[i] - columnMedians[i]
|
||||
}
|
||||
|
||||
for width > t.width {
|
||||
index, _ := largest(differences)
|
||||
if differences[index] < 1 {
|
||||
break
|
||||
}
|
||||
|
||||
shrink := min(differences[index], width-t.width)
|
||||
t.widths[index] -= shrink
|
||||
width -= shrink
|
||||
differences[index] = 0
|
||||
}
|
||||
|
||||
// Table is still too wide, begin shrinking the columns based on the
|
||||
// largest column.
|
||||
for width > t.width {
|
||||
index, _ := largest(t.widths)
|
||||
if t.widths[index] < 1 {
|
||||
break
|
||||
}
|
||||
t.widths[index]--
|
||||
width--
|
||||
}
|
||||
}
|
||||
|
||||
if t.borderTop {
|
||||
s.WriteString(t.constructTopBorder())
|
||||
s.WriteString("\n")
|
||||
}
|
||||
|
||||
if hasHeaders {
|
||||
s.WriteString(t.constructHeaders())
|
||||
s.WriteString("\n")
|
||||
}
|
||||
|
||||
for r := t.offset; r < t.data.Rows(); r++ {
|
||||
s.WriteString(t.constructRow(r))
|
||||
}
|
||||
|
||||
if t.borderBottom {
|
||||
s.WriteString(t.constructBottomBorder())
|
||||
}
|
||||
|
||||
return lipgloss.NewStyle().
|
||||
MaxHeight(t.computeHeight()).
|
||||
MaxWidth(t.width).Render(s.String())
|
||||
}
|
||||
|
||||
// computeWidth computes the width of the table in it's current configuration.
|
||||
func (t *Table) computeWidth() int {
|
||||
width := sum(t.widths) + btoi(t.borderLeft) + btoi(t.borderRight)
|
||||
if t.borderColumn {
|
||||
width += len(t.widths) - 1
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
// computeHeight computes the height of the table in it's current configuration.
|
||||
func (t *Table) computeHeight() int {
|
||||
hasHeaders := t.headers != nil && len(t.headers) > 0
|
||||
return sum(t.heights) - 1 + btoi(hasHeaders) +
|
||||
btoi(t.borderTop) + btoi(t.borderBottom) +
|
||||
btoi(t.borderHeader) + t.data.Rows()*btoi(t.borderRow)
|
||||
}
|
||||
|
||||
// Render returns the table as a string.
|
||||
func (t *Table) Render() string {
|
||||
return t.String()
|
||||
}
|
||||
|
||||
// constructTopBorder constructs the top border for the table given it's current
|
||||
// border configuration and data.
|
||||
func (t *Table) constructTopBorder() string {
|
||||
var s strings.Builder
|
||||
if t.borderLeft {
|
||||
s.WriteString(t.borderStyle.Render(t.border.TopLeft))
|
||||
}
|
||||
for i := 0; i < len(t.widths); i++ {
|
||||
s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Top, t.widths[i])))
|
||||
if i < len(t.widths)-1 && t.borderColumn {
|
||||
s.WriteString(t.borderStyle.Render(t.border.MiddleTop))
|
||||
}
|
||||
}
|
||||
if t.borderRight {
|
||||
s.WriteString(t.borderStyle.Render(t.border.TopRight))
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// constructBottomBorder constructs the bottom border for the table given it's current
|
||||
// border configuration and data.
|
||||
func (t *Table) constructBottomBorder() string {
|
||||
var s strings.Builder
|
||||
if t.borderLeft {
|
||||
s.WriteString(t.borderStyle.Render(t.border.BottomLeft))
|
||||
}
|
||||
for i := 0; i < len(t.widths); i++ {
|
||||
s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[i])))
|
||||
if i < len(t.widths)-1 && t.borderColumn {
|
||||
s.WriteString(t.borderStyle.Render(t.border.MiddleBottom))
|
||||
}
|
||||
}
|
||||
if t.borderRight {
|
||||
s.WriteString(t.borderStyle.Render(t.border.BottomRight))
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// constructHeaders constructs the headers for the table given it's current
|
||||
// header configuration and data.
|
||||
func (t *Table) constructHeaders() string {
|
||||
var s strings.Builder
|
||||
if t.borderLeft {
|
||||
s.WriteString(t.borderStyle.Render(t.border.Left))
|
||||
}
|
||||
for i, header := range t.headers {
|
||||
s.WriteString(t.style(0, i).
|
||||
MaxHeight(1).
|
||||
Width(t.widths[i]).
|
||||
MaxWidth(t.widths[i]).
|
||||
Render(runewidth.Truncate(fmt.Sprint(header), t.widths[i], "…")))
|
||||
if i < len(t.headers)-1 && t.borderColumn {
|
||||
s.WriteString(t.borderStyle.Render(t.border.Left))
|
||||
}
|
||||
}
|
||||
if t.borderHeader {
|
||||
if t.borderRight {
|
||||
s.WriteString(t.borderStyle.Render(t.border.Right))
|
||||
}
|
||||
s.WriteString("\n")
|
||||
if t.borderLeft {
|
||||
s.WriteString(t.borderStyle.Render(t.border.MiddleLeft))
|
||||
}
|
||||
for i := 0; i < len(t.headers); i++ {
|
||||
s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Top, t.widths[i])))
|
||||
if i < len(t.headers)-1 && t.borderColumn {
|
||||
s.WriteString(t.borderStyle.Render(t.border.Middle))
|
||||
}
|
||||
}
|
||||
if t.borderRight {
|
||||
s.WriteString(t.borderStyle.Render(t.border.MiddleRight))
|
||||
}
|
||||
}
|
||||
if t.borderRight && !t.borderHeader {
|
||||
s.WriteString(t.borderStyle.Render(t.border.Right))
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// constructRow constructs the row for the table given an index and row data
|
||||
// based on the current configuration.
|
||||
func (t *Table) constructRow(index int) string {
|
||||
var s strings.Builder
|
||||
|
||||
hasHeaders := t.headers != nil && len(t.headers) > 0
|
||||
height := t.heights[index+btoi(hasHeaders)]
|
||||
|
||||
var cells []string
|
||||
left := strings.Repeat(t.borderStyle.Render(t.border.Left)+"\n", height)
|
||||
if t.borderLeft {
|
||||
cells = append(cells, left)
|
||||
}
|
||||
|
||||
for c := 0; c < t.data.Columns(); c++ {
|
||||
cell := t.data.At(index, c)
|
||||
|
||||
cells = append(cells, t.style(index+1, c).
|
||||
Height(height).
|
||||
MaxHeight(height).
|
||||
Width(t.widths[c]).
|
||||
MaxWidth(t.widths[c]).
|
||||
Render(runewidth.Truncate(cell, t.widths[c]*height, "…")))
|
||||
|
||||
if c < t.data.Columns()-1 && t.borderColumn {
|
||||
cells = append(cells, left)
|
||||
}
|
||||
}
|
||||
|
||||
if t.borderRight {
|
||||
right := strings.Repeat(t.borderStyle.Render(t.border.Right)+"\n", height)
|
||||
cells = append(cells, right)
|
||||
}
|
||||
|
||||
for i, cell := range cells {
|
||||
cells[i] = strings.TrimRight(cell, "\n")
|
||||
}
|
||||
|
||||
s.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, cells...) + "\n")
|
||||
|
||||
if t.borderRow && index < t.data.Rows()-1 {
|
||||
s.WriteString(t.borderStyle.Render(t.border.MiddleLeft))
|
||||
for i := 0; i < len(t.widths); i++ {
|
||||
s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[i])))
|
||||
if i < len(t.widths)-1 && t.borderColumn {
|
||||
s.WriteString(t.borderStyle.Render(t.border.Middle))
|
||||
}
|
||||
}
|
||||
s.WriteString(t.borderStyle.Render(t.border.MiddleRight) + "\n")
|
||||
}
|
||||
|
||||
return s.String()
|
||||
}
|
898
table/table_test.go
Normal file
898
table/table_test.go
Normal file
@ -0,0 +1,898 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
var TableStyle = func(row, col int) lipgloss.Style {
|
||||
switch {
|
||||
case row == 0:
|
||||
return lipgloss.NewStyle().Padding(0, 1).Align(lipgloss.Center)
|
||||
case row%2 == 0:
|
||||
return lipgloss.NewStyle().Padding(0, 1)
|
||||
default:
|
||||
return lipgloss.NewStyle().Padding(0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTable(t *testing.T) {
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Row("Chinese", "Nǐn hǎo", "Nǐ hǎo").
|
||||
Row("French", "Bonjour", "Salut").
|
||||
Row("Japanese", "こんにちは", "やあ").
|
||||
Row("Russian", "Zdravstvuyte", "Privet").
|
||||
Row("Spanish", "Hola", "¿Qué tal?")
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬──────────────┬───────────┐
|
||||
│ LANGUAGE │ FORMAL │ INFORMAL │
|
||||
├──────────┼──────────────┼───────────┤
|
||||
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
|
||||
│ French │ Bonjour │ Salut │
|
||||
│ Japanese │ こんにちは │ やあ │
|
||||
│ Russian │ Zdravstvuyte │ Privet │
|
||||
│ Spanish │ Hola │ ¿Qué tal? │
|
||||
└──────────┴──────────────┴───────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableEmpty(t *testing.T) {
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL")
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬────────┬──────────┐
|
||||
│ LANGUAGE │ FORMAL │ INFORMAL │
|
||||
├──────────┼────────┼──────────┤
|
||||
└──────────┴────────┴──────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableOffset(t *testing.T) {
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Row("Chinese", "Nǐn hǎo", "Nǐ hǎo").
|
||||
Row("French", "Bonjour", "Salut").
|
||||
Row("Japanese", "こんにちは", "やあ").
|
||||
Row("Russian", "Zdravstvuyte", "Privet").
|
||||
Row("Spanish", "Hola", "¿Qué tal?").
|
||||
Offset(1)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬──────────────┬───────────┐
|
||||
│ LANGUAGE │ FORMAL │ INFORMAL │
|
||||
├──────────┼──────────────┼───────────┤
|
||||
│ French │ Bonjour │ Salut │
|
||||
│ Japanese │ こんにちは │ やあ │
|
||||
│ Russian │ Zdravstvuyte │ Privet │
|
||||
│ Spanish │ Hola │ ¿Qué tal? │
|
||||
└──────────┴──────────────┴───────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableBorder(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.DoubleBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
╔══════════╦══════════════╦═══════════╗
|
||||
║ LANGUAGE ║ FORMAL ║ INFORMAL ║
|
||||
╠══════════╬══════════════╬═══════════╣
|
||||
║ Chinese ║ Nǐn hǎo ║ Nǐ hǎo ║
|
||||
║ French ║ Bonjour ║ Salut ║
|
||||
║ Japanese ║ こんにちは ║ やあ ║
|
||||
║ Russian ║ Zdravstvuyte ║ Privet ║
|
||||
║ Spanish ║ Hola ║ ¿Qué tal? ║
|
||||
╚══════════╩══════════════╩═══════════╝
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableSetRows(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬──────────────┬───────────┐
|
||||
│ LANGUAGE │ FORMAL │ INFORMAL │
|
||||
├──────────┼──────────────┼───────────┤
|
||||
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
|
||||
│ French │ Bonjour │ Salut │
|
||||
│ Japanese │ こんにちは │ やあ │
|
||||
│ Russian │ Zdravstvuyte │ Privet │
|
||||
│ Spanish │ Hola │ ¿Qué tal? │
|
||||
└──────────┴──────────────┴───────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoreCellsThanHeaders(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬──────────────┬───────────┐
|
||||
│ LANGUAGE │ FORMAL │ │
|
||||
├──────────┼──────────────┼───────────┤
|
||||
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
|
||||
│ French │ Bonjour │ Salut │
|
||||
│ Japanese │ こんにちは │ やあ │
|
||||
│ Russian │ Zdravstvuyte │ Privet │
|
||||
│ Spanish │ Hola │ ¿Qué tal? │
|
||||
└──────────┴──────────────┴───────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoreCellsThanHeadersExtra(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet", "Privet", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬──────────────┬───────────┬────────┬────────┐
|
||||
│ LANGUAGE │ FORMAL │ │ │ │
|
||||
├──────────┼──────────────┼───────────┼────────┼────────┤
|
||||
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ │ │
|
||||
│ French │ Bonjour │ Salut │ Salut │ │
|
||||
│ Japanese │ こんにちは │ やあ │ │ │
|
||||
│ Russian │ Zdravstvuyte │ Privet │ Privet │ Privet │
|
||||
│ Spanish │ Hola │ ¿Qué tal? │ │ │
|
||||
└──────────┴──────────────┴───────────┴────────┴────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableNoHeaders(t *testing.T) {
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Row("Chinese", "Nǐn hǎo", "Nǐ hǎo").
|
||||
Row("French", "Bonjour", "Salut").
|
||||
Row("Japanese", "こんにちは", "やあ").
|
||||
Row("Russian", "Zdravstvuyte", "Privet").
|
||||
Row("Spanish", "Hola", "¿Qué tal?")
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬──────────────┬───────────┐
|
||||
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
|
||||
│ French │ Bonjour │ Salut │
|
||||
│ Japanese │ こんにちは │ やあ │
|
||||
│ Russian │ Zdravstvuyte │ Privet │
|
||||
│ Spanish │ Hola │ ¿Qué tal? │
|
||||
└──────────┴──────────────┴───────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableNoColumnSeparators(t *testing.T) {
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
BorderColumn(false).
|
||||
StyleFunc(TableStyle).
|
||||
Row("Chinese", "Nǐn hǎo", "Nǐ hǎo").
|
||||
Row("French", "Bonjour", "Salut").
|
||||
Row("Japanese", "こんにちは", "やあ").
|
||||
Row("Russian", "Zdravstvuyte", "Privet").
|
||||
Row("Spanish", "Hola", "¿Qué tal?")
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌───────────────────────────────────┐
|
||||
│ Chinese Nǐn hǎo Nǐ hǎo │
|
||||
│ French Bonjour Salut │
|
||||
│ Japanese こんにちは やあ │
|
||||
│ Russian Zdravstvuyte Privet │
|
||||
│ Spanish Hola ¿Qué tal? │
|
||||
└───────────────────────────────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableNoColumnSeparatorsWithHeaders(t *testing.T) {
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
BorderColumn(false).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Row("Chinese", "Nǐn hǎo", "Nǐ hǎo").
|
||||
Row("French", "Bonjour", "Salut").
|
||||
Row("Japanese", "こんにちは", "やあ").
|
||||
Row("Russian", "Zdravstvuyte", "Privet").
|
||||
Row("Spanish", "Hola", "¿Qué tal?")
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌───────────────────────────────────┐
|
||||
│ LANGUAGE FORMAL INFORMAL │
|
||||
├───────────────────────────────────┤
|
||||
│ Chinese Nǐn hǎo Nǐ hǎo │
|
||||
│ French Bonjour Salut │
|
||||
│ Japanese こんにちは やあ │
|
||||
│ Russian Zdravstvuyte Privet │
|
||||
│ Spanish Hola ¿Qué tal? │
|
||||
└───────────────────────────────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBorderColumnsWithExtraRows(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet", "Privet", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
BorderColumn(false).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ LANGUAGE FORMAL │
|
||||
├───────────────────────────────────────────────────┤
|
||||
│ Chinese Nǐn hǎo Nǐ hǎo │
|
||||
│ French Bonjour Salut Salut │
|
||||
│ Japanese こんにちは やあ │
|
||||
│ Russian Zdravstvuyte Privet Privet Privet │
|
||||
│ Spanish Hola ¿Qué tal? │
|
||||
└───────────────────────────────────────────────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
table := New()
|
||||
expected := ""
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableUnsetBorders(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...).
|
||||
BorderTop(false).
|
||||
BorderBottom(false).
|
||||
BorderLeft(false).
|
||||
BorderRight(false)
|
||||
|
||||
expected := strings.TrimPrefix(`
|
||||
LANGUAGE │ FORMAL │ INFORMAL
|
||||
──────────┼──────────────┼───────────
|
||||
Chinese │ Nǐn hǎo │ Nǐ hǎo
|
||||
French │ Bonjour │ Salut
|
||||
Japanese │ こんにちは │ やあ
|
||||
Russian │ Zdravstvuyte │ Privet
|
||||
Spanish │ Hola │ ¿Qué tal? `, "\n")
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", debug(expected), debug(table.String()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableUnsetHeaderSeparator(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...).
|
||||
BorderHeader(false).
|
||||
BorderTop(false).
|
||||
BorderBottom(false).
|
||||
BorderLeft(false).
|
||||
BorderRight(false)
|
||||
|
||||
expected := strings.TrimPrefix(`
|
||||
LANGUAGE │ FORMAL │ INFORMAL
|
||||
Chinese │ Nǐn hǎo │ Nǐ hǎo
|
||||
French │ Bonjour │ Salut
|
||||
Japanese │ こんにちは │ やあ
|
||||
Russian │ Zdravstvuyte │ Privet
|
||||
Spanish │ Hola │ ¿Qué tal? `, "\n")
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", debug(expected), debug(table.String()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableUnsetHeaderSeparatorWithBorder(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...).
|
||||
BorderHeader(false)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬──────────────┬───────────┐
|
||||
│ LANGUAGE │ FORMAL │ INFORMAL │
|
||||
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
|
||||
│ French │ Bonjour │ Salut │
|
||||
│ Japanese │ こんにちは │ やあ │
|
||||
│ Russian │ Zdravstvuyte │ Privet │
|
||||
│ Spanish │ Hola │ ¿Qué tal? │
|
||||
└──────────┴──────────────┴───────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableRowSeparators(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
BorderRow(true).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬──────────────┬───────────┐
|
||||
│ LANGUAGE │ FORMAL │ INFORMAL │
|
||||
├──────────┼──────────────┼───────────┤
|
||||
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
|
||||
├──────────┼──────────────┼───────────┤
|
||||
│ French │ Bonjour │ Salut │
|
||||
├──────────┼──────────────┼───────────┤
|
||||
│ Japanese │ こんにちは │ やあ │
|
||||
├──────────┼──────────────┼───────────┤
|
||||
│ Russian │ Zdravstvuyte │ Privet │
|
||||
├──────────┼──────────────┼───────────┤
|
||||
│ Spanish │ Hola │ ¿Qué tal? │
|
||||
└──────────┴──────────────┴───────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableHeights(t *testing.T) {
|
||||
styleFunc := func(row, col int) lipgloss.Style {
|
||||
if row == 0 {
|
||||
return lipgloss.NewStyle().Padding(0, 1)
|
||||
}
|
||||
if col == 0 {
|
||||
return lipgloss.NewStyle().Width(18).Padding(1)
|
||||
}
|
||||
return lipgloss.NewStyle().Width(25).Padding(1, 2)
|
||||
}
|
||||
|
||||
rows := [][]string{
|
||||
{"Chutar o balde", `Literally translates to "kick the bucket." It's used when someone gives up or loses patience.`},
|
||||
{"Engolir sapos", `Literally means "to swallow frogs." It's used to describe someone who has to tolerate or endure unpleasant situations.`},
|
||||
{"Arroz de festa", `Literally means "party rice." It´s used to refer to someone who shows up everywhere.`},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(styleFunc).
|
||||
Headers("EXPRESSION", "MEANING").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────────────┬─────────────────────────┐
|
||||
│ EXPRESSION │ MEANING │
|
||||
├──────────────────┼─────────────────────────┤
|
||||
│ │ │
|
||||
│ Chutar o balde │ Literally translates │
|
||||
│ │ to "kick the bucket." │
|
||||
│ │ It's used when │
|
||||
│ │ someone gives up or │
|
||||
│ │ loses patience. │
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ Engolir sapos │ Literally means "to │
|
||||
│ │ swallow frogs." It's │
|
||||
│ │ used to describe │
|
||||
│ │ someone who has to │
|
||||
│ │ tolerate or endure │
|
||||
│ │ unpleasant │
|
||||
│ │ situations. │
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ Arroz de festa │ Literally means │
|
||||
│ │ "party rice." It´s │
|
||||
│ │ used to refer to │
|
||||
│ │ someone who shows up │
|
||||
│ │ everywhere. │
|
||||
│ │ │
|
||||
└──────────────────┴─────────────────────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableMultiLineRowSeparator(t *testing.T) {
|
||||
styleFunc := func(row, col int) lipgloss.Style {
|
||||
if row == 0 {
|
||||
return lipgloss.NewStyle().Padding(0, 1)
|
||||
}
|
||||
if col == 0 {
|
||||
return lipgloss.NewStyle().Width(18).Padding(1)
|
||||
}
|
||||
return lipgloss.NewStyle().Width(25).Padding(1, 2)
|
||||
}
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(styleFunc).
|
||||
Headers("EXPRESSION", "MEANING").
|
||||
BorderRow(true).
|
||||
Row("Chutar o balde", `Literally translates to "kick the bucket." It's used when someone gives up or loses patience.`).
|
||||
Row("Engolir sapos", `Literally means "to swallow frogs." It's used to describe someone who has to tolerate or endure unpleasant situations.`).
|
||||
Row("Arroz de festa", `Literally means "party rice." It´s used to refer to someone who shows up everywhere.`)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────────────┬─────────────────────────┐
|
||||
│ EXPRESSION │ MEANING │
|
||||
├──────────────────┼─────────────────────────┤
|
||||
│ │ │
|
||||
│ Chutar o balde │ Literally translates │
|
||||
│ │ to "kick the bucket." │
|
||||
│ │ It's used when │
|
||||
│ │ someone gives up or │
|
||||
│ │ loses patience. │
|
||||
│ │ │
|
||||
├──────────────────┼─────────────────────────┤
|
||||
│ │ │
|
||||
│ Engolir sapos │ Literally means "to │
|
||||
│ │ swallow frogs." It's │
|
||||
│ │ used to describe │
|
||||
│ │ someone who has to │
|
||||
│ │ tolerate or endure │
|
||||
│ │ unpleasant │
|
||||
│ │ situations. │
|
||||
│ │ │
|
||||
├──────────────────┼─────────────────────────┤
|
||||
│ │ │
|
||||
│ Arroz de festa │ Literally means │
|
||||
│ │ "party rice." It´s │
|
||||
│ │ used to refer to │
|
||||
│ │ someone who shows up │
|
||||
│ │ everywhere. │
|
||||
│ │ │
|
||||
└──────────────────┴─────────────────────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableWidthExpand(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Width(80).
|
||||
StyleFunc(TableStyle).
|
||||
Border(lipgloss.NormalBorder()).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌────────────────────────┬────────────────────────────┬────────────────────────┐
|
||||
│ LANGUAGE │ FORMAL │ INFORMAL │
|
||||
├────────────────────────┼────────────────────────────┼────────────────────────┤
|
||||
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
|
||||
│ French │ Bonjour │ Salut │
|
||||
│ Japanese │ こんにちは │ やあ │
|
||||
│ Russian │ Zdravstvuyte │ Privet │
|
||||
│ Spanish │ Hola │ ¿Qué tal? │
|
||||
└────────────────────────┴────────────────────────────┴────────────────────────┘
|
||||
`)
|
||||
|
||||
if lipgloss.Width(table.String()) != 80 {
|
||||
t.Fatalf("expected table width to be 80, got %d", lipgloss.Width(table.String()))
|
||||
}
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableWidthShrink(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Width(30).
|
||||
StyleFunc(TableStyle).
|
||||
Border(lipgloss.NormalBorder()).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌─────────┬─────────┬────────┐
|
||||
│ LANGUAG │ FORMAL │ INFORM │
|
||||
├─────────┼─────────┼────────┤
|
||||
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
|
||||
│ French │ Bonjour │ Salut │
|
||||
│ Japanes │ こんに │ やあ │
|
||||
│ Russian │ Zdravst │ Privet │
|
||||
│ Spanish │ Hola │ ¿Qué │
|
||||
└─────────┴─────────┴────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableWidthSmartCrop(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Kini", "40", "New York"},
|
||||
{"Eli", "30", "London"},
|
||||
{"Iris", "20", "Paris"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Width(25).
|
||||
StyleFunc(TableStyle).
|
||||
Border(lipgloss.NormalBorder()).
|
||||
Headers("Name", "Age of Person", "Location").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────┬─────┬──────────┐
|
||||
│ Name │ Age │ Location │
|
||||
├──────┼─────┼──────────┤
|
||||
│ Kini │ 40 │ New York │
|
||||
│ Eli │ 30 │ London │
|
||||
│ Iris │ 20 │ Paris │
|
||||
└──────┴─────┴──────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableWidthSmartCropExtensive(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "您好", "你好"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Arabic", "أهلين", "أهلا"},
|
||||
{"Russian", "Здравствуйте", "Привет"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
{"English", "You look absolutely fabulous.", "How's it going?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Width(18).
|
||||
StyleFunc(TableStyle).
|
||||
Border(lipgloss.ThickBorder()).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┏━━━━┳━━━━━┳━━━━━┓
|
||||
┃ LA ┃ FOR ┃ INF ┃
|
||||
┣━━━━╋━━━━━╋━━━━━┫
|
||||
┃ Ch ┃ 您 ┃ 你 ┃
|
||||
┃ Ja ┃ こ ┃ や ┃
|
||||
┃ Ar ┃ أهل ┃ أهل ┃
|
||||
┃ Ru ┃ Здр ┃ При ┃
|
||||
┃ Sp ┃ Hol ┃ ¿Qu ┃
|
||||
┃ En ┃ You ┃ How ┃
|
||||
┗━━━━┻━━━━━┻━━━━━┛
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableWidthSmartCropTiny(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "您好", "你好"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Здравствуйте", "Привет"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
{"English", "You look absolutely fabulous.", "How's it going?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Width(1).
|
||||
StyleFunc(TableStyle).
|
||||
Border(lipgloss.NormalBorder()).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌
|
||||
│
|
||||
├
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
└
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableWidths(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Width(30).
|
||||
StyleFunc(TableStyle).
|
||||
BorderLeft(false).
|
||||
BorderRight(false).
|
||||
Border(lipgloss.NormalBorder()).
|
||||
BorderColumn(false).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
──────────────────────────────
|
||||
LANGUAGE FORMAL INFORMAL
|
||||
──────────────────────────────
|
||||
Chinese Nǐn hǎo Nǐ hǎo
|
||||
French Bonjour Salut
|
||||
Japanese こんに やあ
|
||||
Russian Zdravst Privet
|
||||
Spanish Hola ¿Qué tal?
|
||||
──────────────────────────────
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableWidthShrinkNoBorders(t *testing.T) {
|
||||
rows := [][]string{
|
||||
{"Chinese", "Nǐn hǎo", "Nǐ hǎo"},
|
||||
{"French", "Bonjour", "Salut"},
|
||||
{"Japanese", "こんにちは", "やあ"},
|
||||
{"Russian", "Zdravstvuyte", "Privet"},
|
||||
{"Spanish", "Hola", "¿Qué tal?"},
|
||||
}
|
||||
|
||||
table := New().
|
||||
Width(30).
|
||||
StyleFunc(TableStyle).
|
||||
BorderLeft(false).
|
||||
BorderRight(false).
|
||||
Border(lipgloss.NormalBorder()).
|
||||
BorderColumn(false).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Rows(rows...)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
──────────────────────────────
|
||||
LANGUAGE FORMAL INFORMAL
|
||||
──────────────────────────────
|
||||
Chinese Nǐn hǎo Nǐ hǎo
|
||||
French Bonjour Salut
|
||||
Japanese こんに やあ
|
||||
Russian Zdravst Privet
|
||||
Spanish Hola ¿Qué tal?
|
||||
──────────────────────────────
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
data := NewStringData().
|
||||
Item("Chinese", "Nǐn hǎo", "Nǐ hǎo").
|
||||
Item("French", "Bonjour", "Salut").
|
||||
Item("Japanese", "こんにちは", "やあ").
|
||||
Item("Russian", "Zdravstvuyte", "Privet").
|
||||
Item("Spanish", "Hola", "¿Qué tal?")
|
||||
|
||||
filter := NewFilter(data).Filter(func(row int) bool {
|
||||
return data.At(row, 0) != "French"
|
||||
})
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Data(filter)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬──────────────┬───────────┐
|
||||
│ LANGUAGE │ FORMAL │ INFORMAL │
|
||||
├──────────┼──────────────┼───────────┤
|
||||
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
|
||||
│ Japanese │ こんにちは │ やあ │
|
||||
│ Russian │ Zdravstvuyte │ Privet │
|
||||
│ Spanish │ Hola │ ¿Qué tal? │
|
||||
└──────────┴──────────────┴───────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterInverse(t *testing.T) {
|
||||
data := NewStringData().
|
||||
Item("Chinese", "Nǐn hǎo", "Nǐ hǎo").
|
||||
Item("French", "Bonjour", "Salut").
|
||||
Item("Japanese", "こんにちは", "やあ").
|
||||
Item("Russian", "Zdravstvuyte", "Privet").
|
||||
Item("Spanish", "Hola", "¿Qué tal?")
|
||||
|
||||
filter := NewFilter(data).Filter(func(row int) bool {
|
||||
return data.At(row, 0) == "French"
|
||||
})
|
||||
|
||||
table := New().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
StyleFunc(TableStyle).
|
||||
Headers("LANGUAGE", "FORMAL", "INFORMAL").
|
||||
Data(filter)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
┌──────────┬─────────┬──────────┐
|
||||
│ LANGUAGE │ FORMAL │ INFORMAL │
|
||||
├──────────┼─────────┼──────────┤
|
||||
│ French │ Bonjour │ Salut │
|
||||
└──────────┴─────────┴──────────┘
|
||||
`)
|
||||
|
||||
if table.String() != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
|
||||
}
|
||||
}
|
||||
|
||||
func debug(s string) string {
|
||||
return strings.ReplaceAll(s, " ", ".")
|
||||
}
|
62
table/util.go
Normal file
62
table/util.go
Normal file
@ -0,0 +1,62 @@
|
||||
package table
|
||||
|
||||
import "golang.org/x/exp/slices"
|
||||
|
||||
// btoi converts a boolean to an integer, 1 if true, 0 if false.
|
||||
func btoi(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// max returns the greater of two integers.
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// min returns the greater of two integers.
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// sum returns the sum of all integers in a slice.
|
||||
func sum(n []int) int {
|
||||
var sum int
|
||||
for _, i := range n {
|
||||
sum += i
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// median returns the median of a slice of integers.
|
||||
func median(n []int) int {
|
||||
slices.Sort(n)
|
||||
|
||||
if len(n) <= 0 {
|
||||
return 0
|
||||
}
|
||||
if len(n)%2 == 0 {
|
||||
h := len(n) / 2 //nolint:gomnd
|
||||
return (n[h-1] + n[h]) / 2 //nolint:gomnd
|
||||
}
|
||||
return n[len(n)/2]
|
||||
}
|
||||
|
||||
// largest returns the largest element and it's index from a slice of integers.
|
||||
func largest(n []int) (int, int) { //nolint:unparam
|
||||
var largest, index int
|
||||
for i, e := range n {
|
||||
if n[i] > n[index] {
|
||||
largest = e
|
||||
index = i
|
||||
}
|
||||
}
|
||||
return index, largest
|
||||
}
|
Loading…
Reference in New Issue
Block a user