probo/cmd/filter/filter.go

327 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package filter
import (
"encoding/json"
"fmt"
"strings"
"git.andreafazzi.eu/andrea/probo/pkg/store/file"
"github.com/TylerBrock/colorjson"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/itchyny/gojq"
"github.com/remogatto/sugarfoam/components/group"
"github.com/remogatto/sugarfoam/components/header"
"github.com/remogatto/sugarfoam/components/statusbar"
"github.com/remogatto/sugarfoam/components/table"
"github.com/remogatto/sugarfoam/components/textinput"
"github.com/remogatto/sugarfoam/components/viewport"
"github.com/remogatto/sugarfoam/layout"
"github.com/remogatto/sugarfoam/layout/tiled"
)
type storeLoadedMsg struct {
store []any
}
type resultMsg struct {
result string
}
type errorMsg struct {
error error
}
type FilterModel struct {
// UI
textInput *textinput.Model
viewport *viewport.Model
group *group.Model
help help.Model
statusBar *statusbar.Model
spinner spinner.Model
// Layout
document *layout.Layout
// Key bindings
bindings *keyBindings
// file store
store []any
// jq
lastQuery string
state int
}
type keyBindings struct {
group *group.Model
quit key.Binding
}
func (k *keyBindings) ShortHelp() []key.Binding {
keys := make([]key.Binding, 0)
current := k.group.Current()
switch item := current.(type) {
case *table.Model:
keys = append(
keys,
item.KeyMap.LineUp,
item.KeyMap.LineDown,
)
}
keys = append(
keys,
k.quit,
)
return keys
}
func (k keyBindings) FullHelp() [][]key.Binding {
return [][]key.Binding{
{
k.quit,
},
}
}
func newBindings(g *group.Model) *keyBindings {
return &keyBindings{
group: g,
quit: key.NewBinding(
key.WithKeys("esc"), key.WithHelp("esc", "Quit app"),
),
}
}
func New() *FilterModel {
textInput := textinput.New(
textinput.WithPlaceholder("Write your jq filter here..."),
)
table := table.New()
viewport := viewport.New()
help := help.New()
group := group.New(
group.WithItems(textInput, viewport, table),
group.WithLayout(
layout.New(
layout.WithStyles(&layout.Styles{Container: lipgloss.NewStyle().Padding(1, 0, 1, 0)}),
layout.WithItem(textInput),
layout.WithItem(tiled.New(viewport, table)),
),
),
)
bindings := newBindings(group)
statusBar := statusbar.New(bindings)
s := spinner.New(
spinner.WithStyle(
lipgloss.NewStyle().Foreground(lipgloss.Color("265"))),
)
s.Spinner = spinner.Dot
header := header.New(
header.WithContent(
lipgloss.NewStyle().
Bold(true).
Border(lipgloss.NormalBorder(), false, false, true, false).
Render("Participants filter 👫"),
),
)
document := layout.New(
layout.WithStyles(&layout.Styles{Container: lipgloss.NewStyle().Margin(1)}),
layout.WithItem(header),
layout.WithItem(group),
layout.WithItem(statusBar),
)
return &FilterModel{
textInput: textInput,
viewport: viewport,
group: group,
statusBar: statusBar,
spinner: s,
document: document,
bindings: bindings,
help: help,
}
}
func (m *FilterModel) Init() tea.Cmd {
var cmds []tea.Cmd
cmds = append(cmds, m.group.Init(), m.loadStore(), m.spinner.Tick)
m.group.Focus()
return tea.Batch(cmds...)
}
func (m *FilterModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.document.SetSize(msg.Width, msg.Height)
case tea.KeyMsg:
switch {
case key.Matches(msg, m.bindings.quit):
return m, tea.Quit
}
case storeLoadedMsg:
m.handleStoreLoaded(msg)
case resultMsg:
m.handleFiltered(msg)
}
cmds = m.handleState(msg, cmds)
return m, tea.Batch(cmds...)
}
func (m *FilterModel) View() string {
return m.document.View()
}
func (m *FilterModel) handleStoreLoaded(msg tea.Msg) {
storeMsg := msg.(storeLoadedMsg)
m.store = storeMsg.store
m.state = FilterState
jsonStore, err := m.storeToJson()
if err != nil {
panic(err)
}
m.viewport.SetContent(jsonStore)
}
func (m *FilterModel) handleFiltered(msg tea.Msg) {
m.viewport.SetContent(sanitize(msg.(resultMsg).result))
}
func (m *FilterModel) handleState(msg tea.Msg, cmds []tea.Cmd) []tea.Cmd {
_, cmd := m.group.Update(msg)
if m.state == LoadingStoreState {
return m.updateSpinner(msg, cmd, cmds)
}
cmds = append(cmds, cmd, m.query(m.textInput.Value()))
m.statusBar.SetContent(formats[FilterState]...)
return cmds
}
func (m *FilterModel) updateSpinner(msg tea.Msg, cmd tea.Cmd, cmds []tea.Cmd) []tea.Cmd {
m.spinner, cmd = m.spinner.Update(msg)
m.statusBar.SetContent(fmt.Sprintf(formats[m.state][0], m.spinner.View()), formats[m.state][1], formats[m.state][2])
cmds = append(cmds, cmd)
return cmds
}
func (m *FilterModel) loadStore() tea.Cmd {
return func() tea.Msg {
pStore, err := file.NewDefaultParticipantFileStore()
if err != nil {
return errorMsg{err}
}
jsonStore, err := pStore.Storer.Json()
if err != nil {
return errorMsg{err}
}
v := make([]any, 0)
err = json.Unmarshal(jsonStore, &v)
if err != nil {
return errorMsg{err}
}
return storeLoadedMsg{v}
}
}
func (m *FilterModel) query(input string) tea.Cmd {
return func() tea.Msg {
if input == m.lastQuery {
return nil
}
if m.state == LoadingStoreState {
return nil
}
m.lastQuery = input
query, err := gojq.Parse(input)
if err != nil {
return errorMsg{err}
}
var result []string
iter := query.Run(m.store)
for {
v, ok := iter.Next()
if !ok {
break
}
if err, ok := v.(error); ok {
return errorMsg{err}
}
f := colorjson.NewFormatter()
f.Indent = 2
b, err := f.Marshal(v)
if err != nil {
return errorMsg{err}
}
result = append(result, string(b))
}
return resultMsg{strings.Join(result, "\n")}
}
}
func (m *FilterModel) storeToJson() (string, error) {
f := colorjson.NewFormatter()
f.Indent = 2
result, err := f.Marshal(m.store)
if err != nil {
return "", err
}
return sanitize(string(result)), nil
}
func sanitize(text string) string {
// FIXME: The use of a standard '-' character causes rendering
// issues within the viewport. Further investigation is
// required to resolve this problem.
return strings.Replace(text, "-", "", -1)
}