refactor to use Tasker interface instead of Task

add tasker register for registering tasks for extiensibility
This commit is contained in:
Robin Olsen
2026-02-18 13:07:23 +01:00
parent 89f8596884
commit 23747fb799
9 changed files with 276 additions and 300 deletions

View File

@@ -46,7 +46,7 @@ func runStartCmd(cmd *cobra.Command, args []string) error {
}
defer cleanup()
tasks := initTasks(svc)
surveyTasks := initTasks(svc)
if cmd.Flags().Changed("interface") {
if _, ok := interfaces[interfaceType]; !ok {
@@ -58,10 +58,10 @@ func runStartCmd(cmd *cobra.Command, args []string) error {
}
if cmd.Flags().Changed("stage") {
if stage < 1 || stage > len(tasks) {
if stage < 1 || stage > len(surveyTasks) {
return fmt.Errorf("invalid stage")
}
if err := runTask(cmd.Context(), tasks[stage-1], interfaces[interfaceType]); err != nil {
if err := runTask(cmd.Context(), surveyTasks[stage-1], interfaces[interfaceType]); err != nil {
return err
}
return nil
@@ -71,8 +71,6 @@ func runStartCmd(cmd *cobra.Command, args []string) error {
return returnIfUserQuit(err, "failed to run intro")
}
surveyTasks := initTasks(svc)
if err := taskLoop(cmd.Context(), surveyTasks, interfaces); err != nil {
return returnIfUserQuit(err, "task loop failed")
}

View File

@@ -3,12 +3,13 @@ package main
import (
"context"
"github.com/LazyBachelor/LazyPM/cmd/survey/tasks"
"github.com/LazyBachelor/LazyPM/internal/service"
"github.com/LazyBachelor/LazyPM/pkg/cli/repl"
"github.com/LazyBachelor/LazyPM/pkg/task"
"github.com/LazyBachelor/LazyPM/pkg/tui"
"github.com/LazyBachelor/LazyPM/pkg/web"
_ "github.com/LazyBachelor/LazyPM/cmd/survey/tasks"
)
func initializeServices(ctx context.Context) (*service.Services, func(), error) {
@@ -21,12 +22,6 @@ func initializeServices(ctx context.Context) (*service.Services, func(), error)
return service.NewServices(ctx, config)
}
func initTasks(svc *service.Services) []*task.Task {
return []*task.Task{
tasks.NewCreateIssueTask(svc).Init(),
}
}
func initInterfaces() map[string]task.Interface {
return map[string]task.Interface{
"repl": repl.NewRepl(),
@@ -34,3 +29,17 @@ func initInterfaces() map[string]task.Interface {
"web": web.NewWeb(),
}
}
func initTasks(svc *service.Services) []task.Tasker {
var taskers []task.Tasker
for _, name := range task.List() {
t, err := task.Get(name, svc)
if err != nil {
continue
}
taskers = append(taskers, t)
}
return taskers
}

View File

@@ -10,56 +10,11 @@ import (
"github.com/LazyBachelor/LazyPM/pkg/task"
)
func runTask(ctx context.Context, t *task.Task, i task.Interface) error {
t.SetInterface(i)
t.SetInterfaceType(tasks.InterfaceToType(i))
doneChan := make(chan bool, 1)
quitChan := make(chan bool, 1)
feedbackChan := make(chan task.ValidationFeedback, 10)
if validated, ok := i.(task.ValidatedInterface); ok {
validated.SetChannels(feedbackChan, quitChan)
}
if err := t.Initialize(ctx); err != nil {
return fmt.Errorf("failed to initialize task: %w", err)
}
if err := t.IntroduceTask(); err != nil {
return returnIfUserQuit(err, "failed to display task introduction screen")
}
t.SetChannels(feedbackChan, doneChan, quitChan)
go t.StartValidationLoop(ctx)
interfaceDone := make(chan error, 1)
go func() {
interfaceDone <- t.StartInterface(ctx, t.Config)
}()
select {
case <-doneChan:
close(quitChan)
<-interfaceDone
fmt.Println("Task completed successfully!")
case err := <-interfaceDone:
close(quitChan)
if err != nil {
return returnIfUserQuit(err, "failed to start task interface")
}
fmt.Println("Task incomplete - you exited early")
}
if err := t.StartQuestionnaire(); err != nil {
return returnIfUserQuit(err, "failed to start questionnaire")
}
return nil
func runTask(ctx context.Context, t task.Tasker, i task.Interface) error {
return task.RunTask(ctx, t, i, tasks.InterfaceToType(i))
}
func taskLoop(ctx context.Context, surveyTasks []*task.Task, interfaces map[string]task.Interface) error {
func taskLoop(ctx context.Context, surveyTasks []task.Tasker, interfaces map[string]task.Interface) error {
var ifaceNames []string
for name := range interfaces {
ifaceNames = append(ifaceNames, name)
@@ -69,11 +24,11 @@ func taskLoop(ctx context.Context, surveyTasks []*task.Task, interfaces map[stri
ifaceNames[i], ifaceNames[j] = ifaceNames[j], ifaceNames[i]
})
for i, task := range surveyTasks {
for i, t := range surveyTasks {
idx := i % len(ifaceNames)
selected := interfaces[ifaceNames[idx]]
if err := runTask(ctx, task, selected); err != nil {
if err := runTask(ctx, t, selected); err != nil {
return err
}
}
@@ -81,7 +36,7 @@ func taskLoop(ctx context.Context, surveyTasks []*task.Task, interfaces map[stri
}
func returnIfUserQuit(err error, msg string) error {
if errors.Is(err, ErrUserQuit) {
if errors.Is(err, task.ErrUserQuit) {
return nil
}
return fmt.Errorf("%s: %w", msg, err)

View File

@@ -0,0 +1,71 @@
package tasks
import (
"context"
"github.com/LazyBachelor/LazyPM/internal/service"
"github.com/LazyBachelor/LazyPM/pkg/task"
taskui "github.com/LazyBachelor/LazyPM/pkg/task/ui"
"github.com/charmbracelet/huh"
)
const codingDescription = `You are tasked with writing a simple function.
Write a function that takes two integers and returns their sum.
The function should be named "Add" and be part of the "coding" package.`
func init() {
task.Register("coding_task", func(svc *service.Services) task.Tasker {
return NewCodingTask(svc)
})
}
type CodingTask struct {
svc *service.Services
}
func NewCodingTask(svc *service.Services) *CodingTask {
return &CodingTask{svc: svc}
}
func (t *CodingTask) Config() task.TaskConfig {
return task.TaskConfig{
IssuePrefix: "pm",
BeadsDBPath: "./.pm/db.db",
StatisticsStoragePath: "./.pm/coding-stats.json",
WebAddress: "localhost:8080",
}
}
func (t *CodingTask) Details() taskui.TaskDetails {
return taskui.TaskDetails{
Title: "Coding Task",
Description: codingDescription,
TimeToComplete: "10m",
Difficulty: "Easy",
}
}
func (t *CodingTask) Questions(interfaceType task.InterfaceType) taskui.Questions {
return taskui.Questions{
huh.NewGroup(huh.NewConfirm().Title("Did you complete the coding task?")),
huh.NewGroup(
huh.NewSelect[int]().
Options(
huh.NewOption("Very easy", 1),
huh.NewOption("Easy", 2),
huh.NewOption("Moderate", 3),
huh.NewOption("Hard", 4),
).
Title("How difficult was the task?"),
),
}
}
func (t *CodingTask) Setup(ctx context.Context) error {
return nil
}
func (t *CodingTask) Validate(ctx context.Context) (bool, error) {
return true, nil
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/LazyBachelor/LazyPM/internal/service"
"github.com/LazyBachelor/LazyPM/pkg/task"
ui "github.com/LazyBachelor/LazyPM/pkg/task/ui"
taskui "github.com/LazyBachelor/LazyPM/pkg/task/ui"
"github.com/charmbracelet/huh"
)
@@ -17,23 +17,18 @@ This task will test your ability to use the issue creation workflow effectively.
Assign this task to yourself and start creating the issue.
Make sure to fill out all the necessary details, including the title, description, and assignee.`
func init() {
task.Register("create_issue", func(svc *service.Services) task.Tasker {
return NewCreateIssueTask(svc)
})
}
type CreateIssueTask struct {
*task.Task
svc *service.Services
}
func NewCreateIssueTask(svc *service.Services) *CreateIssueTask {
return &CreateIssueTask{
svc: svc,
}
}
func (t *CreateIssueTask) Init() *task.Task {
task := task.NewTask(t.svc, t.Details(), t.QuestionsFunc())
task.SetConfigFunc(t.Config)
task.SetDbStateFunc(t.DbStateFunc)
task.SetValidateFunc(t.ValidateFunc)
return task
return &CreateIssueTask{svc: svc}
}
func (t *CreateIssueTask) Config() task.TaskConfig {
@@ -45,65 +40,47 @@ func (t *CreateIssueTask) Config() task.TaskConfig {
}
}
func (t *CreateIssueTask) Details() ui.TaskDetails {
return ui.TaskDetails{
func (t *CreateIssueTask) Details() taskui.TaskDetails {
return taskui.TaskDetails{
Title: "Create Issue Task",
Description: "Create a new issue in the project management system to test the issue creation workflow.",
Description: "Create a new issue in the project management system...",
TimeToComplete: "15m",
Difficulty: "Hard",
}
}
func (t *CreateIssueTask) QuestionsFunc() task.QuestionsFunc {
return func(interfaceType task.InterfaceType) ui.Questions {
questions := ui.Questions{}
// Add an extra question for TUI
if interfaceType == task.InterfaceTUI {
questions = append(questions,
huh.NewGroup(
huh.NewConfirm().Title("Did you complete?"),
),
)
}
questions = append(questions,
huh.NewGroup(
huh.NewConfirm().Title("Was this good"),
),
huh.NewGroup(
huh.NewSelect[int]().Options(
func (t *CreateIssueTask) Questions(interfaceType task.InterfaceType) taskui.Questions {
return taskui.Questions{
huh.NewGroup(huh.NewConfirm().Title("Was this good")),
huh.NewGroup(
huh.NewSelect[int]().
Options(
huh.NewOption("Very good", 1),
huh.NewOption("Very Bad", 2),
).Title("How good was it?"),
),
)
return questions
).
Title("How good was it?"),
),
}
}
func (t *CreateIssueTask) DbStateFunc(ctx context.Context) error {
func (t *CreateIssueTask) Setup(ctx context.Context) error {
// Clear existing issues to ensure a clean state for the task
if err := t.svc.DeleteIssues(); err != nil {
return err
}
issue := models.Issue{
ID: "pm-abc",
Title: "Create A New Issue", Description: description,
IssueType: models.TypeTask, Status: models.StatusOpen,
ID: "pm-abc",
Title: "Create A New Issue",
Description: description,
IssueType: models.TypeTask,
Status: models.StatusOpen,
}
if err := t.svc.Beads.CreateIssue(ctx, &issue, ""); err != nil {
return err
}
return nil
return t.svc.Beads.CreateIssue(ctx, &issue, "")
}
func (t *CreateIssueTask) ValidateFunc(ctx context.Context) (ok bool, errorMsg error) {
// Fetches issues, indexed with latest first
func (t *CreateIssueTask) Validate(ctx context.Context) (bool, error) {
issues, err := t.svc.Beads.SearchIssues(ctx, "", models.IssueFilter{})
if err != nil {
return false, err