From 2bea4dc0301cdc3e89535a7bde078ebf4ccb4062 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Fri, 30 Sep 2022 18:40:10 -0400 Subject: [PATCH] feat(file): gum file to pick files --- file/command.go | 58 +++++++++++ file/file.go | 226 ++++++++++++++++++++++++++++++++++++++++ file/hidden_unix.go | 10 ++ file/options.go | 21 ++++ go.mod | 3 +- go.sum | 26 ++++- gum.go | 28 +++++ internal/stack/stack.go | 26 +++++ main.go | 8 ++ style/options.go | 16 +-- 10 files changed, 408 insertions(+), 14 deletions(-) create mode 100644 file/command.go create mode 100644 file/file.go create mode 100644 file/hidden_unix.go create mode 100644 file/options.go create mode 100644 internal/stack/stack.go diff --git a/file/command.go b/file/command.go new file mode 100644 index 0000000..bdade02 --- /dev/null +++ b/file/command.go @@ -0,0 +1,58 @@ +package file + +import ( + "fmt" + "os" + "path/filepath" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/gum/internal/stack" +) + +// Run is the interface to picking a file. +func (o Options) Run() error { + if o.Path == "" { + o.Path = "." + } + + path, err := filepath.Abs(o.Path) + if err != nil { + return err + } + + m := model{ + path: path, + cursor: o.Cursor, + selected: 0, + showHidden: o.All, + autoHeight: o.Height == 0, + height: o.Height, + max: 0, + min: 0, + selectedStack: stack.NewStack(), + minStack: stack.NewStack(), + maxStack: stack.NewStack(), + cursorStyle: o.CursorStyle.ToLipgloss(), + symlinkStyle: o.SymlinkStyle.ToLipgloss(), + directoryStyle: o.DirectoryStyle.ToLipgloss(), + fileStyle: o.FileStyle.ToLipgloss(), + permissionStyle: o.PermissionsStyle.ToLipgloss(), + selectedStyle: o.SelectedStyle.ToLipgloss(), + fileSizeStyle: o.FileSizeStyle.ToLipgloss(), + } + + tm, err := tea.NewProgram(&m, tea.WithOutput(os.Stderr)).StartReturningModel() + if err != nil { + return err + } + + m = tm.(model) + + if m.path == "" { + os.Exit(1) + } + + fmt.Println(m.path) + + return nil +} diff --git a/file/file.go b/file/file.go new file mode 100644 index 0000000..06e0e10 --- /dev/null +++ b/file/file.go @@ -0,0 +1,226 @@ +// Package file provides an interface to pick a file from a folder (tree). +// The user is provided a file manager-like interface to navigate, to +// select a file. +// +// Let's pick a file from the current directory: +// +// $ gum file +// $ gum file . +// +// Let's pick a file from the home directory: +// +// $ gum file $HOME +package file + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" + "sort" + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/gum/internal/stack" + "github.com/charmbracelet/lipgloss" + "github.com/dustin/go-humanize" +) + +const marginBottom = 5 + +type model struct { + quitting bool + path string + files []os.DirEntry + showHidden bool + + selected int + selectedStack stack.Stack + + min int + max int + maxStack stack.Stack + minStack stack.Stack + height int + autoHeight bool + + cursor string + cursorStyle lipgloss.Style + symlinkStyle lipgloss.Style + directoryStyle lipgloss.Style + fileStyle lipgloss.Style + permissionStyle lipgloss.Style + selectedStyle lipgloss.Style + fileSizeStyle lipgloss.Style +} + +type readDirMsg []os.DirEntry + +func readDir(path string, showHidden bool) tea.Cmd { + return func() tea.Msg { + dirEntries, err := os.ReadDir(path) + if err != nil { + return tea.Quit + } + + sort.Slice(dirEntries, func(i, j int) bool { + if dirEntries[i].IsDir() == dirEntries[j].IsDir() { + return dirEntries[i].Name() < dirEntries[j].Name() + } + return dirEntries[i].IsDir() + }) + + if showHidden { + return readDirMsg(dirEntries) + } + + var sanitizedDirEntries []fs.DirEntry + for _, dirEntry := range dirEntries { + isHidden, _ := IsHidden(dirEntry.Name()) + if isHidden { + continue + } + sanitizedDirEntries = append(sanitizedDirEntries, dirEntry) + } + return readDirMsg(sanitizedDirEntries) + } +} + +func (m model) Init() tea.Cmd { + return readDir(m.path, m.showHidden) +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case readDirMsg: + m.files = msg + case tea.WindowSizeMsg: + if m.autoHeight { + m.height = msg.Height - marginBottom + } + m.max = m.height + case tea.KeyMsg: + switch msg.String() { + case "g": + m.selected = 0 + m.min = 0 + m.max = m.height - 1 + case "G": + m.selected = len(m.files) - 1 + m.min = len(m.files) - m.height + m.max = len(m.files) - 1 + case "j", "down": + m.selected++ + if m.selected >= len(m.files) { + m.selected = len(m.files) - 1 + } + if m.selected > m.max { + m.min++ + m.max++ + } + case "k", "up": + m.selected-- + if m.selected < 0 { + m.selected = 0 + } + if m.selected < m.min { + m.min-- + m.max-- + } + case "ctrl+c", "q": + m.path = "" + m.quitting = true + return m, tea.Quit + case "backspace", "h", "left": + m.path = filepath.Dir(m.path) + if m.selectedStack.Length() > 0 { + m.selected, m.min, m.max = m.popView() + } else { + m.selected = 0 + m.min = 0 + m.max = m.height - 1 + } + return m, readDir(m.path, m.showHidden) + case "l", "right", "enter": + if len(m.files) == 0 { + break + } + if !m.files[m.selected].IsDir() { + if msg.String() == "enter" { + m.path = filepath.Join(m.path, m.files[m.selected].Name()) + m.quitting = true + return m, tea.Quit + } + break + } + m.path = filepath.Join(m.path, m.files[m.selected].Name()) + m.pushView() + m.selected = 0 + m.min = 0 + m.max = m.height - 1 + return m, readDir(m.path, m.showHidden) + } + } + return m, nil +} + +func (m model) pushView() { + m.minStack.Push(m.min) + m.maxStack.Push(m.max) + m.selectedStack.Push(m.selected) +} + +func (m model) popView() (int, int, int) { + return m.selectedStack.Pop(), m.minStack.Pop(), m.maxStack.Pop() +} + +func (m model) View() string { + if m.quitting { + return "" + } + if len(m.files) == 0 { + return "Bummer. No files found." + } + var s strings.Builder + + for i, f := range m.files { + if i < m.min { + continue + } + if i > m.max { + break + } + + var symlinkPath string + info, _ := f.Info() + isSymlink := info.Mode()&fs.ModeSymlink != 0 + size := humanize.Bytes(uint64(info.Size())) + name := f.Name() + if isSymlink { + symlinkPath, _ = filepath.EvalSymlinks(filepath.Join(m.path, name)) + } + if m.selected == i { + selected := fmt.Sprintf(" %s %"+fmt.Sprint(m.fileSizeStyle.GetWidth())+"s %s", info.Mode().String(), size, name) + if isSymlink { + selected = fmt.Sprintf("%s → %s", selected, symlinkPath) + } + s.WriteString(m.cursorStyle.Render(m.cursor) + m.selectedStyle.Render(selected)) + } else { + var style = m.fileStyle + if f.IsDir() { + style = m.directoryStyle + } else if isSymlink { + style = m.symlinkStyle + } + + fileName := style.Render(name) + if isSymlink { + fileName = fmt.Sprintf("%s → %s", fileName, symlinkPath) + } + s.WriteString(fmt.Sprintf(" %s %s %s", m.permissionStyle.Render(info.Mode().String()), m.fileSizeStyle.Render(size), fileName)) + } + s.WriteString("\n") + } + + return s.String() +} diff --git a/file/hidden_unix.go b/file/hidden_unix.go new file mode 100644 index 0000000..ada959b --- /dev/null +++ b/file/hidden_unix.go @@ -0,0 +1,10 @@ +//go:build !windows + +package file + +import "strings" + +// IsHidden reports whether a file is hidden or not +func IsHidden(file string) (bool, error) { + return strings.HasPrefix(file, "."), nil +} diff --git a/file/options.go b/file/options.go new file mode 100644 index 0000000..415fbf4 --- /dev/null +++ b/file/options.go @@ -0,0 +1,21 @@ +package file + +import "github.com/charmbracelet/gum/style" + +// Options are the options for the file command. +type Options struct { + // Path is the path to the folder / directory to begin traversing. + Path string `arg:"" optional:"" name:"path" help:"The path to the folder to begin traversing"` + // Cursor is the character to display in front of the current selected items. + Cursor string `short:"c" help:"The cursor character" default:">"` + All bool `short:"a" help:"Show hidden and 'dot' files" default:"true"` + + Height int `help:"Maximum number of files to display" default:"0"` + CursorStyle style.Styles `embed:"" prefix:"cursor." help:"The cursor style" set:"defaultForeground=212" envprefix:"GUM_FILE_CURSOR_"` + SymlinkStyle style.Styles `embed:"" prefix:"symlink." help:"The style to use for symlinks" set:"defaultForeground=36" envprefix:"GUM_FILE_SYMLINK_"` + DirectoryStyle style.Styles `embed:"" prefix:"directory." help:"The style to use for directories" set:"defaultForeground=99" envprefix:"GUM_FILE_DIRECTORY_"` + FileStyle style.Styles `embed:"" prefix:"file." help:"The style to use for files" envprefix:"GUM_FILE_FILE_"` + PermissionsStyle style.Styles `embed:"" prefix:"permissions." help:"The style to use for permissions" set:"defaultForeground=244" envprefix:"GUM_FILE_PERMISSIONS_"` + SelectedStyle style.Styles `embed:"" prefix:"selected." help:"The style to use for the selected item" set:"defaultBold=true" set:"defaultForeground=212" envprefix:"GUM_FILE_SELECTED_"` + FileSizeStyle style.Styles `embed:"" prefix:"file-size." help:"The style to use for file sizes" set:"defaultWidth=8" set:"defaultAlign=right" set:"defaultForeground=240" envprefix:"GUM_FILE_FILE_SIZE_"` +} diff --git a/go.mod b/go.mod index b76db07..a343c70 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,8 @@ require ( github.com/charmbracelet/bubbles v0.14.1-0.20221006154229-d1775121146a 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/charmbracelet/lipgloss v0.6.1-0.20220930064401-ae7c84f7b158 + github.com/dustin/go-humanize v1.0.0 github.com/mattn/go-runewidth v0.0.14 github.com/muesli/roff v0.1.0 github.com/muesli/termenv v0.13.0 diff --git a/go.sum b/go.sum index 31fab29..4f1b376 100644 --- a/go.sum +++ b/go.sum @@ -13,16 +13,25 @@ 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.1-0.20221006154229-d1775121146a h1:/prXWlDbR4CWT1FaTvkU8WhLfpZv3eOrN9PtL8oDdDU= -github.com/charmbracelet/bubbles v0.14.1-0.20221006154229-d1775121146a/go.mod h1:5rZgJTHmgWISQnxnzzIJtQt3GC1bfJfNmr4SEtRDtTQ= +github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og= +github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= +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= +<<<<<<< HEAD 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= +||||||| parent of c5917c0 (feat(file): gum file to pick files) +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= +======= +>>>>>>> c5917c0 (feat(file): gum file to pick files) 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.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= -github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= +github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= +github.com/charmbracelet/lipgloss v0.6.1-0.20220930064401-ae7c84f7b158 h1:uuY3ti70rfEiLw3rHKSRiJ+cWfq4KWScgjxhoVRf0eE= +github.com/charmbracelet/lipgloss v0.6.1-0.20220930064401-ae7c84f7b158/go.mod h1:5EY1dcRQX7kPSA5ssoYjq2qEDhpS4cdtmdYY1OlAdMs= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -30,6 +39,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -51,6 +62,7 @@ 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= @@ -76,9 +88,12 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs= github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= @@ -92,6 +107,7 @@ 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= diff --git a/gum.go b/gum.go index a330b6d..0a4308d 100644 --- a/gum.go +++ b/gum.go @@ -6,6 +6,7 @@ import ( "github.com/charmbracelet/gum/choose" "github.com/charmbracelet/gum/completion" "github.com/charmbracelet/gum/confirm" + "github.com/charmbracelet/gum/file" "github.com/charmbracelet/gum/filter" "github.com/charmbracelet/gum/format" "github.com/charmbracelet/gum/input" @@ -56,6 +57,20 @@ type Gum struct { // Confirm confirm.Options `cmd:"" help:"Ask a user to confirm an action"` + // File provides an interface to pick a file from a folder (tree). + // The user is provided a file manager-like interface to navigate, to + // select a file. + // + // Let's pick a file from the current directory: + // + // $ gum file + // $ gum file . + // + // Let's pick a file from the home directory: + // + // $ gum file $HOME + File file.Options `cmd:"" help:"Pick a file from a folder"` + // Filter provides a fuzzy searching text input to allow filtering a list of // options to select one option. // @@ -106,6 +121,19 @@ type Gum struct { // https://github.com/charmbracelet/bubbles/tree/master/viewport // // It allows the user to scroll through content like a pager. + // + // ╭────────────────────────────────────────────────╮ + // │ 1 │ Gum Pager │ + // │ 2 │ ========= │ + // │ 3 │ │ + // │ 4 │ ``` │ + // │ 5 │ gum pager --height 10 --width 25 < text │ + // │ 6 │ ``` │ + // │ 7 │ │ + // │ 8 │ │ + // ╰────────────────────────────────────────────────╯ + // ↑/↓: Navigate • q: Quit + // Pager pager.Options `cmd:"" help:"Scroll through a file"` // Spin provides a shell script interface for the spinner bubble. diff --git a/internal/stack/stack.go b/internal/stack/stack.go new file mode 100644 index 0000000..af90413 --- /dev/null +++ b/internal/stack/stack.go @@ -0,0 +1,26 @@ +package stack + +// Stack is a stack interface for integers. +type Stack struct { + Push func(int) + Pop func() int + Length func() int +} + +// NewStack returns a new stack of integers +func NewStack() Stack { + slice := make([]int, 0) + return Stack{ + Push: func(i int) { + slice = append(slice, i) + }, + Pop: func() int { + res := slice[len(slice)-1] + slice = slice[:len(slice)-1] + return res + }, + Length: func() int { + return len(slice) + }, + } +} diff --git a/main.go b/main.go index 1d6f223..b104cba 100644 --- a/main.go +++ b/main.go @@ -53,13 +53,21 @@ func main() { }), kong.Vars{ "version": version, + "defaultHeight": "0", + "defaultWidth": "0", + "defaultAlign": "left", "defaultBorder": "none", "defaultBorderForeground": "", + "defaultBorderBackground": "", "defaultBackground": "", "defaultForeground": "", "defaultMargin": "0 0", "defaultPadding": "0 0", "defaultUnderline": "false", + "defaultBold": "false", + "defaultFaint": "false", + "defaultItalic": "false", + "defaultStrikethrough": "false", }, ) if err := ctx.Run(); err != nil { diff --git a/style/options.go b/style/options.go index 97b730c..0839ef8 100644 --- a/style/options.go +++ b/style/options.go @@ -23,20 +23,20 @@ type Styles struct { // Border Border string `help:"Border Style" enum:"none,hidden,normal,rounded,thick,double" default:"${defaultBorder}" group:"Style Flags" env:"BORDER"` - BorderBackground string `help:"Border Background Color" group:"Style Flags" env:"BORDER_BACKGROUND"` + BorderBackground string `help:"Border Background Color" group:"Style Flags" default:"${defaultBorderBackground}" env:"BORDER_BACKGROUND"` BorderForeground string `help:"Border Foreground Color" group:"Style Flags" default:"${defaultBorderForeground}" env:"BORDER_FOREGROUND"` // Layout - Align string `help:"Text Alignment" enum:"left,center,right,bottom,middle,top" default:"left" group:"Style Flags" env:"ALIGN"` - Height int `help:"Text height" group:"Style Flags" env:"HEIGHT"` - Width int `help:"Text width" group:"Style Flags" env:"WIDTH"` + Align string `help:"Text Alignment" enum:"left,center,right,bottom,middle,top" default:"${defaultAlign}" group:"Style Flags" env:"ALIGN"` + Height int `help:"Text height" default:"${defaultHeight}" group:"Style Flags" env:"HEIGHT"` + Width int `help:"Text width" default:"${defaultWidth}" group:"Style Flags" env:"WIDTH"` Margin string `help:"Text margin" default:"${defaultMargin}" group:"Style Flags" env:"MARGIN"` Padding string `help:"Text padding" default:"${defaultPadding}" group:"Style Flags" env:"PADDING"` // Format - Bold bool `help:"Bold text" group:"Style Flags" env:"BOLD"` - Faint bool `help:"Faint text" group:"Style Flags" env:"FAINT"` - Italic bool `help:"Italicize text" group:"Style Flags" env:"ITALIC"` - Strikethrough bool `help:"Strikethrough text" group:"Style Flags" env:"STRIKETHROUGH"` + Bold bool `help:"Bold text" default:"${defaultBold}" group:"Style Flags" env:"BOLD"` + Faint bool `help:"Faint text" default:"${defaultFaint}" group:"Style Flags" env:"FAINT"` + Italic bool `help:"Italicize text" default:"${defaultItalic}" group:"Style Flags" env:"ITALIC"` + Strikethrough bool `help:"Strikethrough text" default:"${defaultStrikethrough}" group:"Style Flags" env:"STRIKETHROUGH"` Underline bool `help:"Underline text" default:"${defaultUnderline}" group:"Style Flags" env:"UNDERLINE"` }