From 6e5736df630776df7062f7734bbe3ae744f57a41 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 15:19:51 -0500 Subject: [PATCH 01/17] =?UTF-8?q?=F0=9F=9A=A7=20start=20with=20adding=20an?= =?UTF-8?q?=20error=20example=20to=20test=20against?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/examples.yml | 2 ++ examples/error/main.go | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 examples/error/main.go diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index eb372fe..fa16b0a 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -21,6 +21,8 @@ jobs: - name: Run Simple run: go run examples/simple/main.go + - name: Run Error + run: go run examples/error/main.go - name: Run Progress run: go run examples/progress/main.go - name: Run Multi diff --git a/examples/error/main.go b/examples/error/main.go new file mode 100644 index 0000000..483ae41 --- /dev/null +++ b/examples/error/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "github.com/fumeapp/taskin" + "time" +) + +func main() { + + tasks := taskin.New(taskin.Tasks{ + { + Title: "Task 1", + Task: func(t *taskin.Task) error { + for i := 0; i < 3; i++ { + t.Title = fmt.Sprintf("Task 1: [%d/3] seconds have passed", i+1) + time.Sleep(500 * time.Millisecond) + } + return nil + }, + }, + { + Title: "Task 2: Error", + Task: func(t *taskin.Task) error { + return fmt.Errorf("task 2 failed") + }, + }, + { + Title: "Task 3", + Task: func(t *taskin.Task) error { + for i := 0; i < 3; i++ { + t.Title = fmt.Sprintf("Task 3: [%d/3] seconds have passed", i+1) + time.Sleep(500 * time.Millisecond) + } + return nil + }, + }, + }, taskin.Defaults) + err := tasks.Run() + + if err != nil { + panic(err) + } +} From d6a885831239cc21481f3eebc76e18906d6bf841 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 16:48:05 -0500 Subject: [PATCH 02/17] =?UTF-8?q?=E2=9C=A8=20finally=20exiting=20with=20st?= =?UTF-8?q?atus=201=20and=20erroring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/error/main.go | 1 + models.go | 10 ++++++ mvc.go | 70 +++++++++++++++++++++++++----------------- taskin.go | 19 +++++++++--- 4 files changed, 67 insertions(+), 33 deletions(-) diff --git a/examples/error/main.go b/examples/error/main.go index 483ae41..d7d4ef9 100644 --- a/examples/error/main.go +++ b/examples/error/main.go @@ -36,6 +36,7 @@ func main() { }, }, }, taskin.Defaults) + // }, taskin.Config{Options: taskin.ConfigOptions{ExitOnFailure: false}}) err := tasks.Run() if err != nil { diff --git a/models.go b/models.go index 8b4d4cd..92f56c7 100644 --- a/models.go +++ b/models.go @@ -5,6 +5,10 @@ import ( "github.com/charmbracelet/bubbles/spinner" ) +type TerminateWithError struct { + Error error +} + type TaskState int const ( @@ -39,3 +43,9 @@ type Runner struct { } type Runners []Runner + +type Model struct { + Runners Runners + Shutdown bool + ShutdownError error +} diff --git a/mvc.go b/mvc.go index 82848dd..bc75a36 100644 --- a/mvc.go +++ b/mvc.go @@ -6,70 +6,83 @@ import ( "github.com/charmbracelet/lipgloss" ) -func (r *Runners) Init() tea.Cmd { +func (m *Model) Init() tea.Cmd { var cmds []tea.Cmd - for i := range *r { - if (*r)[i].Spinner != nil { - cmds = append(cmds, (*r)[i].Spinner.Tick) + for i := range m.Runners { + if (m.Runners)[i].Spinner != nil { + cmds = append(cmds, (m.Runners)[i].Spinner.Tick) } - for j := range (*r)[i].Children { - if (*r)[i].Children[j].Spinner != nil { - cmds = append(cmds, (*r)[i].Children[j].Spinner.Tick) + for j := range (m.Runners)[i].Children { + if (m.Runners)[i].Children[j].Spinner != nil { + cmds = append(cmds, (m.Runners)[i].Children[j].Spinner.Tick) } } } return tea.Batch(cmds...) } -func (r *Runners) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd + if m.Shutdown && m.ShutdownError != nil { + return m, tea.Quit + } + switch msg := msg.(type) { + case TerminateWithError: + m.SetShutdown(msg.Error) + return m, tea.Quit + case spinner.TickMsg: allDone := true - for i := range *r { + for i := range m.Runners { - if (*r)[i].State == Running || (*r)[i].State == NotStarted { + if (m.Runners)[i].State == Running || (m.Runners)[i].State == NotStarted { if !IsCI() { - newSpinner, cmd := (*r)[i].Spinner.Update(msg) - (*r)[i].Spinner = &newSpinner + newSpinner, cmd := (m.Runners)[i].Spinner.Update(msg) + (m.Runners)[i].Spinner = &newSpinner cmds = append(cmds, cmd) } } - for j := range (*r)[i].Children { - if (*r)[i].Children[j].State == Running || (*r)[i].Children[j].State == NotStarted { + for j := range (m.Runners)[i].Children { + if (m.Runners)[i].Children[j].State == Running || (m.Runners)[i].Children[j].State == NotStarted { if !IsCI() { - newSpinner, cmd := (*r)[i].Children[j].Spinner.Update(msg) - (*r)[i].Children[j].Spinner = &newSpinner + newSpinner, cmd := (m.Runners)[i].Children[j].Spinner.Update(msg) + (m.Runners)[i].Children[j].Spinner = &newSpinner cmds = append(cmds, cmd) } } } - if (*r)[i].State == Failed { - return r, tea.Quit + if (m.Runners)[i].State == Failed { + return m, tea.Quit } - if (*r)[i].State != Completed && (*r)[i].State != Failed { + if (m.Runners)[i].State != Completed && (m.Runners)[i].State != Failed { allDone = false } } if allDone { - return r, tea.Quit + return m, tea.Quit } - return r, tea.Batch(cmds...) + return m, tea.Batch(cmds...) } - return r, nil + return m, nil } -func (r *Runners) checkTasksState() (allDone, anyFailed bool) { +func (m *Model) SetShutdown(err error) { + m.Shutdown = true + m.ShutdownError = err +} + +func (m *Model) checkTasksState() (allDone, anyFailed bool) { allDone = true - for _, runner := range *r { + for _, runner := range m.Runners { if runner.State != Completed && runner.State != Failed { allDone = false } @@ -80,17 +93,18 @@ func (r *Runners) checkTasksState() (allDone, anyFailed bool) { return } -func (r *Runners) View() string { +func (m *Model) View() string { var view string // check if CI is set, if it is then don't return the view until all tasks are completed or one has failed if IsCI() { - allDone, _ := r.checkTasksState() - if !allDone { + allDone, anyFailed := m.checkTasksState() + if !allDone && !anyFailed { return "" } } - for _, runner := range *r { + + for _, runner := range m.Runners { status := "" switch runner.State { case NotStarted: diff --git a/taskin.go b/taskin.go index 3ea6748..e9611a1 100644 --- a/taskin.go +++ b/taskin.go @@ -50,12 +50,21 @@ func (task *Task) Progress(current, total int) { } func (r *Runners) Run() error { - program = tea.NewProgram(r, tea.WithInput(nil)) + m := &Model{Runners: *r, Shutdown: false, ShutdownError: nil} + program = tea.NewProgram(m, tea.WithInput(nil)) _, err := program.Run() + if err != nil { + program.Send(TerminateWithError{Error: err}) + os.Exit(1) + } + if m.Shutdown && m.ShutdownError != nil { + os.Exit(1) + } return err } func New(tasks Tasks, cfg Config) Runners { + _ = mergo.Merge(&cfg, Defaults) var runners Runners for _, task := range tasks { @@ -67,7 +76,7 @@ func New(tasks Tasks, cfg Config) Runners { for i := range runners { for _, runner := range runners[:i] { - if runner.State == Failed { + if runner.State == Failed && runner.Config.Options.ExitOnFailure { return } } @@ -75,10 +84,10 @@ func New(tasks Tasks, cfg Config) Runners { runners[i].State = Running err := runners[i].Task.Task(&runners[i].Task) if err != nil { - runners[i].Task.Title = fmt.Sprintf("%s - Error: %s", runners[i].Task.Title, err.Error()) + runners[i].Task.Title = fmt.Sprintf("%s - %s", runners[i].Task.Title, err.Error()) runners[i].State = Failed if program != nil { - program.Send(spinner.TickMsg{}) + program.Send(TerminateWithError{Error: err}) } continue } @@ -92,7 +101,7 @@ func New(tasks Tasks, cfg Config) Runners { runners[i].Children[j].State = Failed runners[i].State = Failed // Mark parent task as Failed if program != nil { - program.Send(spinner.TickMsg{}) + program.Send(TerminateWithError{Error: err}) } break } From 40663ef201456e65fe260980ed6a1d21903dcee9 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 16:51:09 -0500 Subject: [PATCH 03/17] =?UTF-8?q?=F0=9F=A5=85=20this=20is=20a=20package=20?= =?UTF-8?q?which=20means=20its=20not=20the=20highest=20level=20of=20an=20a?= =?UTF-8?q?pp,=20dont=20do=20an=20os.Exit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taskin.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/taskin.go b/taskin.go index e9611a1..bf926c4 100644 --- a/taskin.go +++ b/taskin.go @@ -55,10 +55,9 @@ func (r *Runners) Run() error { _, err := program.Run() if err != nil { program.Send(TerminateWithError{Error: err}) - os.Exit(1) } if m.Shutdown && m.ShutdownError != nil { - os.Exit(1) + return m.ShutdownError } return err } From 7e524ffaa8a6b286e0157c147a9a481ab999f03e Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 16:53:30 -0500 Subject: [PATCH 04/17] =?UTF-8?q?=F0=9F=92=9A=20allow=20our=20error=20exam?= =?UTF-8?q?ple=20to=20pass=20through?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/examples.yml | 1 + examples/simple/main.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index fa16b0a..238f0d0 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -23,6 +23,7 @@ jobs: run: go run examples/simple/main.go - name: Run Error run: go run examples/error/main.go + continue-on-error: true - name: Run Progress run: go run examples/progress/main.go - name: Run Multi diff --git a/examples/simple/main.go b/examples/simple/main.go index 4f9d3a5..3ffaab3 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -26,7 +26,7 @@ func main() { t.Title = fmt.Sprintf("Task 2: [%d/3] seconds have passed", i+1) time.Sleep(500 * time.Millisecond) } - return fmt.Errorf("task 2 failed") + return nil }, }, }, taskin.Defaults) From a0fa69494c5519c99e983051a89db771b80c5af8 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:01:58 -0500 Subject: [PATCH 05/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mvc.go | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/mvc.go b/mvc.go index bc75a36..d844abf 100644 --- a/mvc.go +++ b/mvc.go @@ -7,6 +7,10 @@ import ( ) func (m *Model) Init() tea.Cmd { + + if IsCI() { + } + var cmds []tea.Cmd for i := range m.Runners { if (m.Runners)[i].Spinner != nil { @@ -129,9 +133,18 @@ func (m *Model) View() string { } } case Completed: - status = lipgloss.NewStyle().Foreground(runner.Config.Colors.Success).Render(runner.Config.Chars.Success) + " " + runner.Task.Title // Green checkmark + if IsCI() { + status = runner.Config.Chars.Success + " " + runner.Task.Title + } else { + status = lipgloss.NewStyle().Foreground(runner.Config.Colors.Success).Render(runner.Config.Chars.Success) + " " + runner.Task.Title // Green checkmark + } case Failed: - status = lipgloss.NewStyle().Foreground(runner.Config.Colors.Failure).Render(runner.Config.Chars.Failure) + " " + runner.Task.Title // Red 'x' + if IsCI() { + status = runner.Config.Chars.Failure + " " + runner.Task.Title + } else { + status = lipgloss.NewStyle().Foreground(runner.Config.Colors.Failure).Render(runner.Config.Chars.Failure) + " " + runner.Task.Title // Red 'x' + + } } view += lipgloss.NewStyle().Render(status) + "\n" @@ -139,7 +152,11 @@ func (m *Model) View() string { status = "" switch child.State { case NotStarted: - status = lipgloss.NewStyle().Foreground(child.Config.Colors.Pending).Render(runner.Config.Chars.NotStarted) + " " + child.Task.Title // Gray bullet + if IsCI() { + status = runner.Config.Chars.NotStarted + " " + child.Task.Title + } else { + status = lipgloss.NewStyle().Foreground(child.Config.Colors.Pending).Render(runner.Config.Chars.NotStarted) + " " + child.Task.Title // Gray bullet + } case Running: if child.Task.ShowProgress.Total != 0 { percent := float64(child.Task.ShowProgress.Current) / float64(child.Task.ShowProgress.Total) @@ -156,11 +173,23 @@ func (m *Model) View() string { } } case Completed: - status = lipgloss.NewStyle().Foreground(child.Config.Colors.Success).Render("✔") + " " + child.Task.Title // Green checkmark + if IsCI() { + status = runner.Config.Chars.Success + " " + child.Task.Title + } else { + status = lipgloss.NewStyle().Foreground(child.Config.Colors.Success).Render(runner.Config.Chars.Success) + " " + child.Task.Title // Green checkmark + } case Failed: - status = lipgloss.NewStyle().Foreground(child.Config.Colors.Failure).Render("✘") + " " + child.Task.Title // Red 'x' + if IsCI() { + status = runner.Config.Chars.Failure + " " + child.Task.Title + } else { + status = lipgloss.NewStyle().Foreground(child.Config.Colors.Failure).Render(runner.Config.Chars.Failure) + " " + child.Task.Title // Red 'x' + } + } + if IsCI() { + view += " " + status + "\n" + } else { + view += " " + lipgloss.NewStyle().Render(status) + "\n" } - view += " " + lipgloss.NewStyle().Render(status) + "\n" } } return view From 5b9bea47b7dcff60941b739826cf4a8cf559a5c8 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:04:02 -0500 Subject: [PATCH 06/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI=20round=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mvc.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/mvc.go b/mvc.go index d844abf..56a049d 100644 --- a/mvc.go +++ b/mvc.go @@ -112,10 +112,18 @@ func (m *Model) View() string { status := "" switch runner.State { case NotStarted: - status = lipgloss.NewStyle().Foreground(runner.Config.Colors.Pending).Render(runner.Config.Chars.NotStarted) + " " + runner.Task.Title // Gray bullet + if IsCI() { + status = runner.Config.Chars.NotStarted + " " + runner.Task.Title + } else { + status = lipgloss.NewStyle().Foreground(runner.Config.Colors.Pending).Render(runner.Config.Chars.NotStarted) + " " + runner.Task.Title // Gray bullet + } case Running: if len(runner.Children) > 0 { - status = lipgloss.NewStyle().Foreground(runner.Config.Colors.ParentStarted).Render(runner.Config.Chars.ParentStarted) + " " + runner.Task.Title + if IsCI() { + status = runner.Config.Chars.ParentStarted + " " + runner.Task.Title + } else { + status = lipgloss.NewStyle().Foreground(runner.Config.Colors.ParentStarted).Render(runner.Config.Chars.ParentStarted) + " " + runner.Task.Title + } } else { if runner.Task.ShowProgress.Total != 0 { percent := float64(runner.Task.ShowProgress.Current) / float64(runner.Task.ShowProgress.Total) From 1c17c6cd3df4893aaaef7412170359c391dc753c Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:07:32 -0500 Subject: [PATCH 07/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI=20round=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taskin.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/taskin.go b/taskin.go index bf926c4..6184735 100644 --- a/taskin.go +++ b/taskin.go @@ -51,7 +51,11 @@ func (task *Task) Progress(current, total int) { func (r *Runners) Run() error { m := &Model{Runners: *r, Shutdown: false, ShutdownError: nil} - program = tea.NewProgram(m, tea.WithInput(nil)) + if IsCI() { + program = tea.NewProgram(m, tea.WithInput(nil), tea.WithOutput(os.Stdout)) + } else { + program = tea.NewProgram(m, tea.WithInput(nil)) + } _, err := program.Run() if err != nil { program.Send(TerminateWithError{Error: err}) From c7d0eccd41c51eeb8809f034b45365310d141a11 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:13:05 -0500 Subject: [PATCH 08/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI=20round=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taskin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskin.go b/taskin.go index 6184735..2f2fa7c 100644 --- a/taskin.go +++ b/taskin.go @@ -52,7 +52,7 @@ func (task *Task) Progress(current, total int) { func (r *Runners) Run() error { m := &Model{Runners: *r, Shutdown: false, ShutdownError: nil} if IsCI() { - program = tea.NewProgram(m, tea.WithInput(nil), tea.WithOutput(os.Stdout)) + program = tea.NewProgram(m, tea.WithoutSignals(), tea.WithoutSignalHandler()) } else { program = tea.NewProgram(m, tea.WithInput(nil)) } From 38e7e54aa16695be658552441f649dd8ed826a28 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:14:11 -0500 Subject: [PATCH 09/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI=20round=205?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taskin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskin.go b/taskin.go index 2f2fa7c..2ed2cad 100644 --- a/taskin.go +++ b/taskin.go @@ -52,7 +52,7 @@ func (task *Task) Progress(current, total int) { func (r *Runners) Run() error { m := &Model{Runners: *r, Shutdown: false, ShutdownError: nil} if IsCI() { - program = tea.NewProgram(m, tea.WithoutSignals(), tea.WithoutSignalHandler()) + program = tea.NewProgram(m, tea.WithInput(nil), tea.WithoutSignals(), tea.WithoutSignalHandler()) } else { program = tea.NewProgram(m, tea.WithInput(nil)) } From 8b7753a1322733fbd7c2fb759c9592eac49d955e Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:27:32 -0500 Subject: [PATCH 10/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI=20round=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mvc.go | 43 +++++++------------------------------------ taskin.go | 6 +----- ui.go | 10 ++++++++++ 3 files changed, 18 insertions(+), 41 deletions(-) create mode 100644 ui.go diff --git a/mvc.go b/mvc.go index 56a049d..a105a95 100644 --- a/mvc.go +++ b/mvc.go @@ -112,18 +112,10 @@ func (m *Model) View() string { status := "" switch runner.State { case NotStarted: - if IsCI() { - status = runner.Config.Chars.NotStarted + " " + runner.Task.Title - } else { - status = lipgloss.NewStyle().Foreground(runner.Config.Colors.Pending).Render(runner.Config.Chars.NotStarted) + " " + runner.Task.Title // Gray bullet - } + status = Color(runner.Config.Colors.Pending, runner.Config.Chars.NotStarted) + " " + runner.Task.Title // Gray bullet case Running: if len(runner.Children) > 0 { - if IsCI() { - status = runner.Config.Chars.ParentStarted + " " + runner.Task.Title - } else { - status = lipgloss.NewStyle().Foreground(runner.Config.Colors.ParentStarted).Render(runner.Config.Chars.ParentStarted) + " " + runner.Task.Title - } + status = Color(runner.Config.Colors.ParentStarted, runner.Config.Chars.ParentStarted) + " " + runner.Task.Title } else { if runner.Task.ShowProgress.Total != 0 { percent := float64(runner.Task.ShowProgress.Current) / float64(runner.Task.ShowProgress.Total) @@ -141,18 +133,9 @@ func (m *Model) View() string { } } case Completed: - if IsCI() { - status = runner.Config.Chars.Success + " " + runner.Task.Title - } else { - status = lipgloss.NewStyle().Foreground(runner.Config.Colors.Success).Render(runner.Config.Chars.Success) + " " + runner.Task.Title // Green checkmark - } + status = Color(runner.Config.Colors.Success, runner.Config.Chars.Success) + " " + runner.Task.Title // Green checkmark case Failed: - if IsCI() { - status = runner.Config.Chars.Failure + " " + runner.Task.Title - } else { - status = lipgloss.NewStyle().Foreground(runner.Config.Colors.Failure).Render(runner.Config.Chars.Failure) + " " + runner.Task.Title // Red 'x' - - } + status = Color(runner.Config.Colors.Failure, runner.Config.Chars.Failure) + " " + runner.Task.Title // Red 'x' } view += lipgloss.NewStyle().Render(status) + "\n" @@ -160,11 +143,7 @@ func (m *Model) View() string { status = "" switch child.State { case NotStarted: - if IsCI() { - status = runner.Config.Chars.NotStarted + " " + child.Task.Title - } else { - status = lipgloss.NewStyle().Foreground(child.Config.Colors.Pending).Render(runner.Config.Chars.NotStarted) + " " + child.Task.Title // Gray bullet - } + status = Color(child.Config.Colors.Pending, runner.Config.Chars.NotStarted) + " " + child.Task.Title // Gray bullet case Running: if child.Task.ShowProgress.Total != 0 { percent := float64(child.Task.ShowProgress.Current) / float64(child.Task.ShowProgress.Total) @@ -181,17 +160,9 @@ func (m *Model) View() string { } } case Completed: - if IsCI() { - status = runner.Config.Chars.Success + " " + child.Task.Title - } else { - status = lipgloss.NewStyle().Foreground(child.Config.Colors.Success).Render(runner.Config.Chars.Success) + " " + child.Task.Title // Green checkmark - } + status = Color(child.Config.Colors.Success, runner.Config.Chars.Success) + " " + child.Task.Title // Green checkmark case Failed: - if IsCI() { - status = runner.Config.Chars.Failure + " " + child.Task.Title - } else { - status = lipgloss.NewStyle().Foreground(child.Config.Colors.Failure).Render(runner.Config.Chars.Failure) + " " + child.Task.Title // Red 'x' - } + status = Color(child.Config.Colors.Failure, runner.Config.Chars.Failure) + " " + child.Task.Title // Red 'x' } if IsCI() { view += " " + status + "\n" diff --git a/taskin.go b/taskin.go index 2ed2cad..bf926c4 100644 --- a/taskin.go +++ b/taskin.go @@ -51,11 +51,7 @@ func (task *Task) Progress(current, total int) { func (r *Runners) Run() error { m := &Model{Runners: *r, Shutdown: false, ShutdownError: nil} - if IsCI() { - program = tea.NewProgram(m, tea.WithInput(nil), tea.WithoutSignals(), tea.WithoutSignalHandler()) - } else { - program = tea.NewProgram(m, tea.WithInput(nil)) - } + program = tea.NewProgram(m, tea.WithInput(nil)) _, err := program.Run() if err != nil { program.Send(TerminateWithError{Error: err}) diff --git a/ui.go b/ui.go new file mode 100644 index 0000000..6d15008 --- /dev/null +++ b/ui.go @@ -0,0 +1,10 @@ +package taskin + +import "github.com/charmbracelet/lipgloss" + +func Color(c lipgloss.TerminalColor, r string) string { + if IsCI() { + return r + } + return lipgloss.NewStyle().Foreground(c).Render(r) +} From 5edec4bb119f6421a6eb9dc70274cbf407080800 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:32:17 -0500 Subject: [PATCH 11/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI=20round=207?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taskin.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/taskin.go b/taskin.go index bf926c4..f466e8c 100644 --- a/taskin.go +++ b/taskin.go @@ -7,7 +7,9 @@ import ( "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "io" "os" + "regexp" ) var program *tea.Program @@ -49,9 +51,26 @@ func (task *Task) Progress(current, total int) { } } +type ansiEscapeCodeFilter struct { + writer io.Writer +} + +func (f *ansiEscapeCodeFilter) Write(p []byte) (n int, err error) { + // Regular expression to match ANSI escape codes + re := regexp.MustCompile(`\x1b[^m]*m`) + // Remove the escape codes from the input + p = re.ReplaceAll(p, []byte{}) + // Write the filtered input to the original writer + return f.writer.Write(p) +} + func (r *Runners) Run() error { m := &Model{Runners: *r, Shutdown: false, ShutdownError: nil} - program = tea.NewProgram(m, tea.WithInput(nil)) + if IsCI() { + program = tea.NewProgram(m, tea.WithInput(nil), tea.WithOutput(&ansiEscapeCodeFilter{writer: os.Stdout})) + } else { + program = tea.NewProgram(m, tea.WithInput(nil)) + } _, err := program.Run() if err != nil { program.Send(TerminateWithError{Error: err}) From 1cb4d6ef94f64e82dffbd5fad9644288c6a5b7da Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:34:51 -0500 Subject: [PATCH 12/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI=20round=208?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taskin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/taskin.go b/taskin.go index f466e8c..5dae8c0 100644 --- a/taskin.go +++ b/taskin.go @@ -56,8 +56,8 @@ type ansiEscapeCodeFilter struct { } func (f *ansiEscapeCodeFilter) Write(p []byte) (n int, err error) { - // Regular expression to match ANSI escape codes - re := regexp.MustCompile(`\x1b[^m]*m`) + // Corrected regular expression to match ANSI escape codes + re := regexp.MustCompile(`\x1b\[[0-9;]*m`) // Remove the escape codes from the input p = re.ReplaceAll(p, []byte{}) // Write the filtered input to the original writer From fa951732fbd1cfe6470d856ad28b50948c933c3b Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:36:01 -0500 Subject: [PATCH 13/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI=20round=209?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taskin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskin.go b/taskin.go index 5dae8c0..c59af43 100644 --- a/taskin.go +++ b/taskin.go @@ -57,7 +57,7 @@ type ansiEscapeCodeFilter struct { func (f *ansiEscapeCodeFilter) Write(p []byte) (n int, err error) { // Corrected regular expression to match ANSI escape codes - re := regexp.MustCompile(`\x1b\[[0-9;]*m`) + re := regexp.MustCompile(`\x1b\[[0-?]*[ -/]*[@-~]`) // Remove the escape codes from the input p = re.ReplaceAll(p, []byte{}) // Write the filtered input to the original writer From 261241f3ef84be5605b64ad75d723153b093b485 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:37:36 -0500 Subject: [PATCH 14/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI=20round=2010?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taskin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskin.go b/taskin.go index c59af43..1e32f50 100644 --- a/taskin.go +++ b/taskin.go @@ -57,7 +57,7 @@ type ansiEscapeCodeFilter struct { func (f *ansiEscapeCodeFilter) Write(p []byte) (n int, err error) { // Corrected regular expression to match ANSI escape codes - re := regexp.MustCompile(`\x1b\[[0-?]*[ -/]*[@-~]`) + re := regexp.MustCompile(`\x1b\[[0-?]*[ -/]*[@-~] *`) // Remove the escape codes from the input p = re.ReplaceAll(p, []byte{}) // Write the filtered input to the original writer From 1a348c1f0ba34e8bd681428d41053a9de6d1d9b3 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:39:48 -0500 Subject: [PATCH 15/17] =?UTF-8?q?=F0=9F=8E=A8=20pull=20all=20lipgloss=20as?= =?UTF-8?q?signments=20for=20CI=20round=2011?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taskin.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/taskin.go b/taskin.go index 1e32f50..41221a7 100644 --- a/taskin.go +++ b/taskin.go @@ -57,7 +57,8 @@ type ansiEscapeCodeFilter struct { func (f *ansiEscapeCodeFilter) Write(p []byte) (n int, err error) { // Corrected regular expression to match ANSI escape codes - re := regexp.MustCompile(`\x1b\[[0-?]*[ -/]*[@-~] *`) + // re := regexp.MustCompile(`\x1b\[[0-?]*[ -/]*[@-~] *`) + re := regexp.MustCompile(` *\x1b\[[0-?]*[ -/]*[@-~]`) // Remove the escape codes from the input p = re.ReplaceAll(p, []byte{}) // Write the filtered input to the original writer From 69f6407a91f38c1a80cf08fca644eaef447ee7bc Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:41:22 -0500 Subject: [PATCH 16/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20pull=20older=20regex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taskin.go | 1 - 1 file changed, 1 deletion(-) diff --git a/taskin.go b/taskin.go index 41221a7..5e9303a 100644 --- a/taskin.go +++ b/taskin.go @@ -57,7 +57,6 @@ type ansiEscapeCodeFilter struct { func (f *ansiEscapeCodeFilter) Write(p []byte) (n int, err error) { // Corrected regular expression to match ANSI escape codes - // re := regexp.MustCompile(`\x1b\[[0-?]*[ -/]*[@-~] *`) re := regexp.MustCompile(` *\x1b\[[0-?]*[ -/]*[@-~]`) // Remove the escape codes from the input p = re.ReplaceAll(p, []byte{}) From 10604d71550cd03f9f5affcf4627fbef3bb815d1 Mon Sep 17 00:00:00 2001 From: kevin olson Date: Mon, 20 May 2024 17:46:25 -0500 Subject: [PATCH 17/17] =?UTF-8?q?=F0=9F=92=9A=20tests=20fix=20and=20lint?= =?UTF-8?q?=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mvc.go | 3 --- mvc_test.go | 39 +++++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/mvc.go b/mvc.go index a105a95..7bdc8d9 100644 --- a/mvc.go +++ b/mvc.go @@ -8,9 +8,6 @@ import ( func (m *Model) Init() tea.Cmd { - if IsCI() { - } - var cmds []tea.Cmd for i := range m.Runners { if (m.Runners)[i].Spinner != nil { diff --git a/mvc_test.go b/mvc_test.go index e81d6ec..5d6692a 100644 --- a/mvc_test.go +++ b/mvc_test.go @@ -1,35 +1,38 @@ package taskin import ( - "os" "testing" ) -func TestRunners_Init(t *testing.T) { - r := &Runners{ - // Initialize your Runners struct here - } +func TestModelInit(t *testing.T) { + // Initialize a new Model + m := &Model{} - cmd := r.Init() + // Call the Init method + cmd := m.Init() - // If Init is not implemented, it should return nil + // Check if the returned command is not nil if cmd != nil { - t.Errorf("Expected Init to return nil") + t.Errorf("Expected command to be not nil, got not nil") } } -func TestRunners_View(t *testing.T) { - r := &Runners{ - // Initialize your Runners struct here - } +func TestModelUpdate(t *testing.T) { + // Initialize a new Model + m := &Model{} - // Set the "CI" environment variable - os.Setenv("CI", "true") + // Call the Update method with a dummy message + newModel, cmd := m.Update("dummy message") - view := r.View() + // Check if the returned model is not nil + if newModel == nil { + t.Errorf("Expected model to be not nil, got nil") + } - // If "CI" is set and not all tasks are completed, View should return an empty string - if view != "" { - t.Errorf("Expected View to return an empty string") + // Check if the returned command is nil + if cmd != nil { + t.Errorf("Expected command to be nil, got non-nil") } } + +// Add more tests for other methods in the Model struct