Skip to content

Commit

Permalink
feat(table): gum table for tabular data
Browse files Browse the repository at this point in the history
  • Loading branch information
maaslalani committed Oct 7, 2022
1 parent a82d5af commit bdd86d5
Show file tree
Hide file tree
Showing 13 changed files with 362 additions and 20 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/alecthomas/kong v0.6.1
github.com/alecthomas/mango-kong v0.1.0
github.com/charmbracelet/bubbles v0.14.1-0.20221006154229-d1775121146a
github.com/charmbracelet/bubbletea v0.22.2-0.20221006105051-f406999cba69
github.com/charmbracelet/bubbletea v0.22.2-0.20221007181357-2696b2f3399f
github.com/charmbracelet/glamour v0.5.1-0.20220727184942-e70ff2d969da
github.com/charmbracelet/lipgloss v0.6.0
github.com/mattn/go-runewidth v0.0.14
Expand Down
14 changes: 2 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,14 @@ github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZs
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og=
github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
github.com/charmbracelet/bubbles v0.14.1-0.20220926062606-e857875f2a75 h1:rjgidQdLMPMe7wQOs+ki2cD69keSKd8nZFPGIDxv4ZI=
github.com/charmbracelet/bubbles v0.14.1-0.20220926062606-e857875f2a75/go.mod h1:5rZgJTHmgWISQnxnzzIJtQt3GC1bfJfNmr4SEtRDtTQ=
github.com/charmbracelet/bubbles v0.14.1-0.20221006154229-d1775121146a h1:/prXWlDbR4CWT1FaTvkU8WhLfpZv3eOrN9PtL8oDdDU=
github.com/charmbracelet/bubbles v0.14.1-0.20221006154229-d1775121146a/go.mod h1:5rZgJTHmgWISQnxnzzIJtQt3GC1bfJfNmr4SEtRDtTQ=
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
github.com/charmbracelet/bubbletea v0.22.1 h1:z66q0LWdJNOWEH9zadiAIXp2GN1AWrwNXU8obVY9X24=
github.com/charmbracelet/bubbletea v0.22.1/go.mod h1:8/7hVvbPN6ZZPkczLiB8YpLkLJ0n7DMho5Wvfd2X1C0=
github.com/charmbracelet/bubbletea v0.22.2-0.20221006105051-f406999cba69 h1:GpZktjqyEQjuvFtFb0UlMlbZCOwOhk/bpKb6+quLz+E=
github.com/charmbracelet/bubbletea v0.22.2-0.20221006105051-f406999cba69/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
github.com/charmbracelet/bubbletea v0.22.2-0.20221007181357-2696b2f3399f h1:mbSd0Sdm2wXUWtXJ81o86G9V+9IhddX0qQcGK6bMbKo=
github.com/charmbracelet/bubbletea v0.22.2-0.20221007181357-2696b2f3399f/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
github.com/charmbracelet/glamour v0.5.1-0.20220727184942-e70ff2d969da h1:FGz53GWQRiKQ/5xUsoCCkewSQIC7u81Scaxx2nUy3nM=
github.com/charmbracelet/glamour v0.5.1-0.20220727184942-e70ff2d969da/go.mod h1:HXz79SMFnF9arKxqeoHWxmo1BhplAH7wehlRhKQIL94=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
Expand Down Expand Up @@ -58,7 +51,6 @@ github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P
github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/mango v0.1.1-0.20220205060214-77e2058169ab h1:m7QFONkzLK0fVXCjwX5tANcnj1yXxTnYQtnfJiY3tcA=
Expand All @@ -70,7 +62,6 @@ github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
github.com/muesli/termenv v0.11.0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
Expand Down Expand Up @@ -101,7 +92,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down
25 changes: 25 additions & 0 deletions gum.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
"github.com/charmbracelet/gum/input"
"github.com/charmbracelet/gum/join"
"github.com/charmbracelet/gum/man"
"github.com/charmbracelet/gum/pager"
"github.com/charmbracelet/gum/spin"
"github.com/charmbracelet/gum/style"
"github.com/charmbracelet/gum/table"
"github.com/charmbracelet/gum/write"
)

Expand Down Expand Up @@ -100,6 +102,12 @@ type Gum struct {
//
Join join.Options `cmd:"" help:"Join text vertically or horizontally"`

// Pager provides a shell script interface for the viewport bubble.
// https://github.com/charmbracelet/bubbles/tree/master/viewport
//
// It allows the user to scroll through content like a pager.
Pager pager.Options `cmd:"" help:"Scroll through a file"`

// Spin provides a shell script interface for the spinner bubble.
// https://github.com/charmbracelet/bubbles/tree/master/spinner
//
Expand Down Expand Up @@ -142,6 +150,23 @@ type Gum struct {
//
Style style.Options `cmd:"" help:"Apply coloring, borders, spacing to text"`

// Table provides a shell script interface for the table bubble.
// https://github.com/charmbracelet/bubbles/tree/master/table
//
// It is useful to render tabular (CSV) data in a terminal and allows
// the user to select a row from the table.
//
// Let's render a table of gum flavors:
//
// $ gum table <<< "Flavor,Price\nStrawberry,$0.50\nBanana,$0.99\nCherry,$0.75"
//
// Flavor Price
// Strawberry $0.50
// Banana $0.99
// Cherry $0.75
//
Table table.Options `cmd:"" help:"Render a table of data"`

// Write provides a shell script interface for the text area bubble.
// https://github.com/charmbracelet/bubbles/tree/master/textarea
//
Expand Down
23 changes: 16 additions & 7 deletions internal/stdin/stdin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,8 @@ import (

// Read reads input from an stdin pipe.
func Read() (string, error) {
stat, err := os.Stdin.Stat()
if err != nil {
return "", fmt.Errorf("failed to stat stdin: %w", err)
}

if stat.Mode()&os.ModeNamedPipe == 0 && stat.Size() == 0 {
return "", nil
if IsEmpty() {
return "", fmt.Errorf("stdin is empty")
}

reader := bufio.NewReader(os.Stdin)
Expand All @@ -35,3 +30,17 @@ func Read() (string, error) {

return b.String(), nil
}

// IsEmpty returns whether stdin is empty.
func IsEmpty() bool {
stat, err := os.Stdin.Stat()
if err != nil {
return true
}

if stat.Mode()&os.ModeNamedPipe == 0 && stat.Size() == 0 {
return true
}

return false
}
42 changes: 42 additions & 0 deletions pager/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package pager

import (
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/gum/internal/stdin"
)

// Run provides a shell script interface for the viewport bubble.
// https://github.com/charmbracelet/bubbles/viewport
func (o Options) Run() error {
vp := viewport.New(o.Style.Width, o.Style.Height)
vp.Style = o.Style.ToLipgloss()
var err error

if o.Content == "" {
o.Content, err = stdin.Read()
if err != nil {
return err
}
}

renderer, err := glamour.NewTermRenderer(
glamour.WithWordWrap(80),
)
if err != nil {
return err
}
md, err := renderer.Render(o.Content)
vp.SetContent(md)

model := model{
viewport: vp,
helpStyle: o.HelpStyle.ToLipgloss(),
}
if err != nil {
return err
}

return tea.NewProgram(model, tea.WithAltScreen()).Start()
}
10 changes: 10 additions & 0 deletions pager/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package pager

import "github.com/charmbracelet/gum/style"

// Options are the options for the pager.
type Options struct {
Style style.Styles `embed:"" help:"Style the pager" envprefix:"GUM_PAGER_"`
HelpStyle style.Styles `embed:"" prefix:"help." help:"Style the help text" set:"defaultForeground=241" envprefix:"GUM_PAGER_HELP_"`
Content string `arg:"" optional:"" help:"Display content to scroll"`
}
39 changes: 39 additions & 0 deletions pager/pager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Package pager provides a pager (similar to less) for the terminal.
//
// $ cat file.txt | gum page
package pager

import (
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

type model struct {
viewport viewport.Model
helpStyle lipgloss.Style
}

func (m model) Init() tea.Cmd {
return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.viewport.Width = msg.Width
m.viewport.Height = msg.Height - 1
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c", "esc":
return m, tea.Quit
}
}
var cmd tea.Cmd
m.viewport, cmd = m.viewport.Update(msg)
return m, cmd
}

func (m model) View() string {
return m.viewport.View() + "\n" + m.helpStyle.Render("↑/↓: Navigate • q: Quit")
}
5 changes: 5 additions & 0 deletions table/comma.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Bubble Gum,Price,Ingredients
Strawberry,$0.88,"Water,Sugar"
Guava,$1.00,"Guava Flavoring,Food Coloring,Xanthan Gum"
Orange,$0.99,"Sugar,Dextrose,Glucose"
Cinnamon,$0.50,"Cin""na""mon"
111 changes: 111 additions & 0 deletions table/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package table

import (
"encoding/csv"
"fmt"
"os"
"strings"

"github.com/alecthomas/kong"
"github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea"
"github.com/mattn/go-runewidth"

"github.com/charmbracelet/gum/internal/stdin"
"github.com/charmbracelet/gum/style"
)

// Run provides a shell script interface for rendering tabular data (CSV)
func (o Options) Run() error {
var reader *csv.Reader
if o.File != "" {
file, err := os.OpenFile(o.File, os.O_RDONLY, 0600)
if err != nil {
return fmt.Errorf("could not find file at path %s", o.File)
}
reader = csv.NewReader(file)
} else {
if stdin.IsEmpty() {
return fmt.Errorf("no data provided")
}
reader = csv.NewReader(os.Stdin)
}

separatorRunes := []rune(o.Separator)
if len(separatorRunes) != 1 {
return fmt.Errorf("separator must be single character")
}
reader.Comma = separatorRunes[0]

var columnNames []string
var err error
// If no columns are provided we'll use the first row of the CSV as the
// column names.
if len(o.Columns) <= 0 {
columnNames, err = reader.Read()
if err != nil {
return fmt.Errorf("unable to parse columns")
}
} else {
columnNames = o.Columns
}

data, err := reader.ReadAll()
if err != nil {
return fmt.Errorf("invalid data provided")
}
var columns []table.Column

for i, title := range columnNames {
width := runewidth.StringWidth(title)
if len(o.Widths) > i {
width = o.Widths[i]
}
columns = append(columns, table.Column{
Title: title,
Width: width,
})
}

defaultStyles := table.DefaultStyles()

styles := table.Styles{
Cell: defaultStyles.Cell.Inherit(o.CellStyle.ToLipgloss()),
Header: defaultStyles.Header.Inherit(o.HeaderStyle.ToLipgloss()),
Selected: defaultStyles.Selected.Inherit(o.SelectedStyle.ToLipgloss()),
}

var rows []table.Row
for _, row := range data {
rows = append(rows, table.Row(row))
}

table := table.New(
table.WithColumns(columns),
table.WithFocused(true),
table.WithHeight(o.Height),
table.WithRows(rows),
table.WithStyles(styles),
)

tm, err := tea.NewProgram(model{table: table}, tea.WithOutput(os.Stderr)).StartReturningModel()

if err != nil {
return fmt.Errorf("failed to start tea program: %w", err)
}

if tm == nil {
return fmt.Errorf("failed to get selection")
}

m := tm.(model)
fmt.Println(strings.Join([]string(m.selected), string(o.Separator)))

return nil
}

// BeforeReset hook. Used to unclutter style flags.
func (o Options) BeforeReset(ctx *kong.Context) error {
style.HideFlags(ctx)
return nil
}
19 changes: 19 additions & 0 deletions table/example.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Bubble Gum Flavor,Price
Strawberry,$0.99
Cherry,$0.50
Banana,$0.75
Orange,$0.25
Lemon,$0.50
Lime,$0.50
Grape,$0.50
Watermelon,$0.50
Pineapple,$0.50
Blueberry,$0.50
Raspberry,$0.50
Cranberry,$0.50
Peach,$0.50
Apple,$0.50
Mango,$0.50
Pomegranate,$0.50
Coconut,$0.50
Cinnamon,$0.50
19 changes: 19 additions & 0 deletions table/invalid.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Bubble Gum Flavor
Strawberry,$0.99
Cherry,$0.50
Banana,$0.75
Orange
Lemon,$0.50
Lime,$0.50
Grape,$0.50
Watermelon,$0.50
Pineapple,$0.50
Blueberry,$0.50
Raspberry,$0.50
Cranberry,$0.50
Peach,$0.50
Apple,$0.50
Mango,$0.50
Pomegranate,$0.50
Coconut,$0.50
Cinnamon,$0.50
Loading

0 comments on commit bdd86d5

Please sign in to comment.