package main import ( "encoding/csv" "fmt" "os" "os/exec" "strings" "time" tea "charm.land/bubbletea/v2" ) type model struct { choices [][]string cursor int selected map[int]struct{} } type editorFinishedMsg struct { err error } func initialModel() model { out, err := exec.Command("hugo", "list", "drafts").Output() if err != nil { println("Error fetching Hugo drafts: Is this a Hugo site? Is Hugo installed?") os.Exit(1) } csvReader := csv.NewReader(strings.NewReader(string(out))) records, err := csvReader.ReadAll() if err != nil { println("Error parsing draft list.") os.Exit(1) } if len(records) <= 1 { println("No drafts found.") os.Exit(1) } return model{ choices: records, selected: make(map[int]struct{}), } } func (m model) Init() tea.Cmd { return nil } func openEditor(path string) tea.Cmd { editor := os.Getenv("EDITOR") if editor == "" { editor = "vim" } return tea.ExecProcess(exec.Command(editor, path), func(err error) tea.Msg { return editorFinishedMsg{err: err} }) } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyPressMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "up", "k": if m.cursor > 0 { m.cursor-- } case "down", "j": if m.cursor < len(m.choices)-1 { m.cursor++ } case "enter": choice := m.choices[m.cursor] path := choice[0] return m, openEditor(path) case "space": _, ok := m.selected[m.cursor] if ok { delete(m.selected, m.cursor) } else { m.selected[m.cursor] = struct{}{} } } case editorFinishedMsg: return m, tea.Quit } return m, nil } func (m model) View() tea.View { s := "Unpublished drafts of this Hugo site:\n\n" for i, choice := range m.choices { if i == 0 { continue } cursor := " " // no cursor if m.cursor == i { cursor = ">" } checked := " " // not checked if _, ok := m.selected[i]; ok { checked = "x" } title := choice[2] t, err := time.Parse(time.RFC3339, choice[3]) if err != nil { } datetime := t.Format("2006-01-02 15:04") s += fmt.Sprintf("%s [%s] %s \t %s\n", cursor, checked, datetime, title) } s += "\n[q] quit | [Enter] edit | [SPACE] select | [↑/k] up | [↓/j] down\n" return tea.NewView(s) } func main() { p := tea.NewProgram(initialModel()) if _, err := p.Run(); err != nil { fmt.Printf("Error: %v", err) os.Exit(1) } }