Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
leg100 committed Mar 22, 2024
1 parent 7fafb67 commit 1d5071a
Show file tree
Hide file tree
Showing 20 changed files with 237 additions and 138 deletions.
3 changes: 0 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,3 @@ require (
)

replace github.com/mattn/go-runewidth => github.com/leg100/go-runewidth v0.0.16-0.20240317085039-79cdd3ecf674

//replace github.com/charmbracelet/bubbletea => github.com/leg100/bubbletea v0.25.1-0.20240319155826-3bbfacbc5292
replace github.com/charmbracelet/bubbletea => ../bubbletea
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
Expand Down
2 changes: 1 addition & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func Start(args []string) error {
// Start daemons
go task.StartEnqueuer(ctx, tasks)
go task.StartRunner(ctx, tasks, cfg.MaxTasks)
go run.StartScheduler(ctx, runs)
go run.StartScheduler(ctx, runs, workspaces)

// Search directory for modules
if err := modules.Reload(); err != nil {
Expand Down
7 changes: 6 additions & 1 deletion internal/run/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/leg100/pug/internal"
"github.com/leg100/pug/internal/resource"
"github.com/leg100/pug/internal/workspace"
"golang.org/x/exp/maps"
)

Expand All @@ -21,13 +22,17 @@ type runLister interface {
// runs are not subject this rule).
//
// The scheduler attempts to schedule runs upon every run event it receives.
func StartScheduler(ctx context.Context, runs *Service) {
func StartScheduler(ctx context.Context, runs *Service, workspaces *workspace.Service) {
sub := runs.Broker.Subscribe(ctx)
s := &scheduler{runs: runs}
for range sub {
for _, run := range s.schedule() {
// Update status from pending to scheduled
run.updateStatus(Scheduled)
// Set run as workspace's current run if not a plan-only run.
if !run.PlanOnly {
workspaces.SetCurrent(run.Workspace().ID(), run.Resource)
}
// Trigger a plan task
_, _ = runs.plan(run)
}
Expand Down
5 changes: 5 additions & 0 deletions internal/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log/slog"
"os"
"os/exec"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -142,6 +143,10 @@ func (f *factory) newTask(opts CreateOptions) (*Task, error) {

func (t *Task) String() string { return t.Resource.String() }

func (t *Task) CommandString() string {
return strings.Join(t.Command, " ")
}

// NewReader provides a reader from which to read the task output from start to
// end.
func (t *Task) NewReader() io.Reader {
Expand Down
74 changes: 74 additions & 0 deletions internal/tui/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package tui

import (
"fmt"

tea "github.com/charmbracelet/bubbletea"
"github.com/leg100/pug/internal/resource"
"github.com/leg100/pug/internal/task"
)

// CreateTasks returns a command that creates one or more tasks using the given
// IDs. If a task fails to be created then no further tasks will be created, and
// an error notification is sent. If all tasks are successfully created then a status
// notification is sent accordingly.
func CreateTasks(fn task.Func, ids ...resource.ID) tea.Cmd {
// Handle the case where a user has pressed a key on an empty table with
// zero rows
//if len(ids) == 0 {
// return nil
//}

//// If items have been selected then clear the selection
//var deselectCmd tea.Cmd
//if len(ids) > 1 {
// deselectCmd = tui.CmdHandler(table.DeselectMsg{})
//}

return func() tea.Msg {
var (
task *task.Task
err error
)
for _, id := range ids {
if task, err = fn(id); err != nil {
return NewErrorMsg(err, "creating task")
}
}
return InfoMsg(fmt.Sprintf("Created %d %s tasks", len(ids), task.CommandString()))

//if len(ids) > 1 {
// // User has selected multiple rows, so send them to the task *list*
// // page
// //
// // TODO: pass in parameter specifying the parent resource for the
// // task listing, i.e. module, workspace, run, etc.
// return navigationMsg{
// target: page{kind: TaskListKind},
// }
//} else {
// // User has highlighted a single row, so send them to the task page.
// return navigationMsg{
// target: page{kind: TaskKind, resource: task.Resource},
// }
//}
}

//return tea.Batch(cmd, deselectCmd)
}

// NavigateTo sends an instruction to navigate to a page with the given model
// kind, and optionally parent resource.
func NavigateTo(kind Kind, parent *resource.Resource) tea.Cmd {
return func() tea.Msg {
page := Page{Kind: kind}
if parent != nil {
page.Parent = *parent
}
return NavigationMsg(page)
}
}

func ReportError(err error, msg string, args ...any) tea.Cmd {
return CmdHandler(NewErrorMsg(err, msg, args...))
}
21 changes: 1 addition & 20 deletions internal/tui/messages.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
package tui

import (
tea "github.com/charmbracelet/bubbletea"
"github.com/leg100/pug/internal/resource"
)

// NavigationMsg is an instruction to navigate to a page.
type NavigationMsg Page

// NavigateTo sends an instruction to navigate to a page with the given model
// kind, and optionally parent resource.
func NavigateTo(kind Kind, parent *resource.Resource) tea.Cmd {
return func() tea.Msg {
page := Page{Kind: kind}
if parent != nil {
page.Parent = *parent
}
return NavigationMsg(page)
}
}
type InfoMsg string

type ErrorMsg struct {
Error error
Expand All @@ -34,10 +19,6 @@ func NewErrorMsg(err error, msg string, args ...any) ErrorMsg {
}
}

func NewErrorCmd(err error, msg string, args ...any) tea.Cmd {
return CmdHandler(NewErrorMsg(err, msg, args...))
}

// BodyResizeMsg is sent whenever the user resizes the terminal window. The width
// and height refer to area available in the main body between the header and
// the footer.
Expand Down
19 changes: 9 additions & 10 deletions internal/tui/module/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/leg100/pug/internal/tui"
"github.com/leg100/pug/internal/tui/keys"
"github.com/leg100/pug/internal/tui/table"
tasktui "github.com/leg100/pug/internal/tui/task"
"github.com/leg100/pug/internal/workspace"
"golang.org/x/exp/maps"
)
Expand Down Expand Up @@ -116,12 +115,12 @@ func (m list) Update(msg tea.Msg) (tui.Model, tea.Cmd) {
//cmds = append(cmds, m.createRun(run.CreateOptions{}))
}
case resource.Event[*run.Run]:
switch msg.Type {
case resource.UpdatedEvent:
if msg.Payload.Status == run.Planned {
return m, tui.NavigateTo(tui.RunKind, &msg.Payload.Resource)
}
}
//switch msg.Type {
//case resource.UpdatedEvent:
// if msg.Payload.Status == run.Planned {
// // return m, tui.NavigateTo(tui.RunKind, &msg.Payload.Resource)
// }
//}
}

switch msg := msg.(type) {
Expand Down Expand Up @@ -149,11 +148,11 @@ func (m list) Update(msg tea.Msg) (tui.Model, tea.Cmd) {
})
}
case key.Matches(msg, localKeys.Init):
return m, tasktui.TaskCmd(m.ModuleService.Init, maps.Keys(m.table.HighlightedOrSelected())...)
return m, tui.CreateTasks(m.ModuleService.Init, maps.Keys(m.table.HighlightedOrSelected())...)
case key.Matches(msg, localKeys.Validate):
return m, tasktui.TaskCmd(m.ModuleService.Validate, maps.Keys(m.table.HighlightedOrSelected())...)
return m, tui.CreateTasks(m.ModuleService.Validate, maps.Keys(m.table.HighlightedOrSelected())...)
case key.Matches(msg, localKeys.Format):
return m, tasktui.TaskCmd(m.ModuleService.Format, maps.Keys(m.table.HighlightedOrSelected())...)
return m, tui.CreateTasks(m.ModuleService.Format, maps.Keys(m.table.HighlightedOrSelected())...)
case key.Matches(msg, localKeys.Plan):
return m, m.createRun(run.CreateOptions{})
}
Expand Down
25 changes: 20 additions & 5 deletions internal/tui/module/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import (
"github.com/leg100/pug/internal/workspace"
)

const (
workspacesTabTitle = "workspaces"
runsTabTitle = "runs"
tasksTabTitle = "tasks"
)

// Maker makes module models.
type Maker struct {
ModuleService *module.Service
Expand All @@ -34,24 +40,27 @@ func (mm *Maker) Make(mr resource.Resource, width, height int) (tui.Model, error
}

tabs := tui.NewTabSet(width, height)
if _, err := tabs.AddTab(mm.WorkspaceListMaker, mr, "workspaces"); err != nil {
if _, err := tabs.AddTab(mm.WorkspaceListMaker, mr, workspacesTabTitle); err != nil {
return nil, fmt.Errorf("adding workspaces tab: %w", err)
}
if _, err := tabs.AddTab(mm.RunListMaker, mr, "runs"); err != nil {
if _, err := tabs.AddTab(mm.RunListMaker, mr, runsTabTitle); err != nil {
return nil, fmt.Errorf("adding runs tab: %w", err)
}
if _, err := tabs.AddTab(mm.TaskListMaker, mr, "tasks"); err != nil {
if _, err := tabs.AddTab(mm.TaskListMaker, mr, tasksTabTitle); err != nil {
return nil, fmt.Errorf("adding tasks tab: %w", err)
}

m := model{
module: mod,
tabs: tabs,
ModuleService: mm.ModuleService,
module: mod,
tabs: tabs,
}
return m, nil
}

type model struct {
ModuleService *module.Service

module *module.Module
tabs tui.TabSet
}
Expand All @@ -64,6 +73,12 @@ func (m model) Update(msg tea.Msg) (tui.Model, tea.Cmd) {
var cmds []tea.Cmd

switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, localKeys.Init):
m.tabs.SetActiveTabWithTitle(tasksTabTitle)
return m, tui.CreateTasks(m.ModuleService.Init, m.module.ID())
}
case resource.Event[*module.Module]:
if msg.Payload.ID() == m.module.ID() {
m.module = msg.Payload
Expand Down
26 changes: 8 additions & 18 deletions internal/tui/run/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,6 @@ type ListMaker struct {
}

func (m *ListMaker) Make(parent resource.Resource, width, height int) (tui.Model, error) {
statusColumn := table.Column{
Key: "run_status",
Title: "STATUS",
Width: run.MaxStatusLen,
}
changesColumn := table.Column{
Key: "run_changes",
Title: "CHANGES",
Width: 10,
}
ageColumn := table.Column{
Key: "age",
Title: "AGE",
Expand All @@ -49,20 +39,20 @@ func (m *ListMaker) Make(parent resource.Resource, width, height int) (tui.Model
columns = append(columns, table.WorkspaceColumn)
}
columns = append(columns,
statusColumn,
changesColumn,
table.RunStatusColumn,
table.RunChangesColumn,
ageColumn,
table.IDColumn,
)

renderer := func(r *run.Run, style lipgloss.Style) table.RenderedRow {
row := table.RenderedRow{
table.ModuleColumn.Key: r.ModulePath(),
table.WorkspaceColumn.Key: r.WorkspaceName(),
statusColumn.Key: string(r.Status),
changesColumn.Key: r.PlanReport.String(),
ageColumn.Key: tui.Ago(time.Now(), r.Updated),
table.IDColumn.Key: r.ID().String(),
table.ModuleColumn.Key: r.ModulePath(),
table.WorkspaceColumn.Key: r.WorkspaceName(),
table.RunStatusColumn.Key: string(r.Status),
table.RunChangesColumn.Key: r.PlanReport.String(),
ageColumn.Key: tui.Ago(time.Now(), r.Updated),
table.IDColumn.Key: r.ID().String(),
}

// switch r.Status {
Expand Down
2 changes: 1 addition & 1 deletion internal/tui/run/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (m model) Update(msg tea.Msg) (tui.Model, tea.Cmd) {
}
cmd, err := m.addTab(msg.Payload)
if err != nil {
return m, tui.NewErrorCmd(err, "")
return m, tui.ReportError(err, "")
}
cmds = append(cmds, cmd)
}
Expand Down
14 changes: 12 additions & 2 deletions internal/tui/table/columns.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package table

import (
"github.com/leg100/pug/internal/resource"
"github.com/mattn/go-runewidth"
"github.com/leg100/pug/internal/run"
)

var (
Expand All @@ -14,7 +14,7 @@ var (
ModuleColumn = Column{
Key: "module",
Title: "MODULE",
TruncationFunc: runewidth.TruncateLeft,
TruncationFunc: TruncateLeft,
FlexFactor: 3,
}
WorkspaceColumn = Column{
Expand All @@ -34,4 +34,14 @@ var (
Width: resource.IDEncodedMaxLen,
FlexFactor: 1,
}
RunStatusColumn = Column{
Key: "run_status",
Title: "STATUS",
Width: run.MaxStatusLen,
}
RunChangesColumn = Column{
Key: "run_changes",
Title: "CHANGES",
Width: 10,
}
)
5 changes: 2 additions & 3 deletions internal/tui/table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/leg100/pug/internal/tui"
"github.com/leg100/pug/internal/tui/keys"
"github.com/mattn/go-runewidth"
"github.com/muesli/reflow/truncate"
"golang.org/x/exp/maps"
)

Expand Down Expand Up @@ -115,7 +114,7 @@ func New[T Item](columns []Column, fn RowRenderer[T], width, height int) Model[T
for _, col := range columns {
// Set default truncation function if unset
if col.TruncationFunc == nil {
col.TruncationFunc = runewidth.Truncate
col.TruncationFunc = defaultTruncationFunc
}
m.cols = append(m.cols, col)
}
Expand Down Expand Up @@ -509,7 +508,7 @@ func (m *Model[T]) renderRow(rowID int) string {
for i, col := range m.cols {
content := cells[col.Key]
// Truncate content if it is wider than column
truncated := truncate.StringWithTail(content, uint(col.Width), "…")
truncated := col.TruncationFunc(content, col.Width, "…")
// Ensure content is all on one line.
inlined := lipgloss.NewStyle().
Width(col.Width).
Expand Down
Loading

0 comments on commit 1d5071a

Please sign in to comment.