diff --git a/cmd/pm/main.go b/cmd/pm/main.go index 3ac39fd..291d10b 100644 --- a/cmd/pm/main.go +++ b/cmd/pm/main.go @@ -3,12 +3,24 @@ package main import ( "context" + issuesCmd "github.com/LazyBachelor/LazyPM/internal/commands/issues" + surveyCmd "github.com/LazyBachelor/LazyPM/internal/commands/survey" "github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/pkg/cli" ) func main() { - if err := cli.NewCli().Run(context.Background(), service.BaseConfig); err != nil { + if err := cli.NewCli(issuesCmd.RootCmd).Run(context.Background(), service.BaseConfig); err != nil { return } } + +func init() { + issuesCmd.RootCmd.AddCommand(issuesCmd.GetCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.ListCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.CloseCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.CreateCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.DeleteCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.UpdateCmd) + issuesCmd.RootCmd.AddCommand(surveyCmd.StatusCmd) +} diff --git a/cmd/survey/cmd.go b/cmd/survey/cmd.go deleted file mode 100644 index 6243cc1..0000000 --- a/cmd/survey/cmd.go +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/LazyBachelor/LazyPM/pkg/task" - "github.com/spf13/cobra" -) - -var interfaceType string -var stage int - -var rootCmd = &cobra.Command{ - Use: "survey", - Long: `Project Management Interface Survey - -Thank you for participating in our survey! - -We are gathering data on how users interact with different task management interfaces to better understand their preferences and compare their usability. -This survey will present you with a series of tasks to complete using various interfaces, including command-line, web-based, and terminal user interfaces. - -Please answer the questions honestly and to the best of your ability. -Your responses will be kept confidential and used solely for research purposes.`} - -var startCmd = &cobra.Command{ - Use: "start", - Short: "Start the user survey", - RunE: runStartCmd, -} - -var submitCmd = &cobra.Command{ - Use: "submit", - Short: "Submit your survey responses", - RunE: func(cmd *cobra.Command, args []string) error { - cmd.Println("Submitting responses and metrics...") - return nil - }, -} - -func runStartCmd(cmd *cobra.Command, args []string) error { - interfaces := initInterfaces() - - svc, cleanup, err := initializeServices(cmd.Context()) - if err != nil { - return returnIfUserQuit(err, "failed to initialize services") - } - defer cleanup() - - surveyTasks := initTasks(svc) - - if cmd.Flags().Changed("interface") { - if _, ok := interfaces[interfaceType]; !ok { - return fmt.Errorf("invalid interface, valid are (tui, repl, web)") - } - interfaces = map[string]task.Interface{ - interfaceType: interfaces[interfaceType], - } - } - - if cmd.Flags().Changed("stage") { - if stage < 1 || stage > len(surveyTasks) { - return fmt.Errorf("invalid stage") - } - if err := runTask(cmd.Context(), surveyTasks[stage-1], interfaces[interfaceType]); err != nil { - return err - } - return nil - } - - if err := newIntroModel().Run(); err != nil { - return returnIfUserQuit(err, "failed to run intro") - } - - if err := taskLoop(cmd.Context(), surveyTasks, interfaces); err != nil { - return returnIfUserQuit(err, "task loop failed") - } - return nil -} - -var listCmd = &cobra.Command{ - Use: "list", - Short: "List available tasks", - RunE: func(cmd *cobra.Command, args []string) error { - for i, name := range task.List() { - cmd.Printf("%d. %s\n", i+1, name) - } - return nil - }, -} - -func init() { - rootCmd.CompletionOptions.DisableDefaultCmd = true - startCmd.Flags().StringVarP(&interfaceType, "interface", "i", "tui", "Specify interface.") - startCmd.Flags().IntVarP(&stage, "stage", "s", 1, "Run stage directly") - rootCmd.AddCommand(startCmd) - rootCmd.AddCommand(submitCmd) - rootCmd.AddCommand(listCmd) -} diff --git a/cmd/survey/main.go b/cmd/survey/main.go index 5ff186a..085b265 100644 --- a/cmd/survey/main.go +++ b/cmd/survey/main.go @@ -3,14 +3,33 @@ package main import ( "context" + "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" + "github.com/LazyBachelor/LazyPM/internal/commands/survey" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/task" "github.com/charmbracelet/fang" ) func main() { ctx := context.Background() - if err := fang.Execute(ctx, rootCmd, + if err := fang.Execute(ctx, surveyCmd.RootCmd, fang.WithColorSchemeFunc(fang.AnsiColorScheme)); err != nil { return } } + +func init() { + surveyCmd.StartCmd.RunE = runStartCmd + surveyCmd.RootCmd.AddCommand(surveyCmd.StartCmd) + surveyCmd.RootCmd.AddCommand(surveyCmd.SubmitCmd) + surveyCmd.RootCmd.AddCommand(surveyCmd.StatusCmd) + surveyCmd.RootCmd.AddCommand(surveyCmd.ListCmd) + + task.Register("create_issue", func(app *service.App) task.Tasker { + return tasks.NewCreateIssueTask(app) + }) + task.Register("coding_task", func(app *service.App) task.Tasker { + return tasks.NewCodingTask(app) + }) +} diff --git a/cmd/survey/runner.go b/cmd/survey/runner.go index 3e213f1..6ff6ca0 100644 --- a/cmd/survey/runner.go +++ b/cmd/survey/runner.go @@ -7,28 +7,67 @@ import ( "math/rand" "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" + surveyCmd "github.com/LazyBachelor/LazyPM/internal/commands/survey" "github.com/LazyBachelor/LazyPM/pkg/task" + "github.com/spf13/cobra" ) -func runTask(ctx context.Context, t task.Tasker, i task.Interface) error { - return task.RunTask(ctx, t, i, tasks.InterfaceToType(i)) +func runStartCmd(cmd *cobra.Command, args []string) error { + interfaces := initInterfaces() + + svc, cleanup, err := initializeServices(cmd.Context()) + if err != nil { + return returnIfUserQuit(err, "failed to initialize services") + } + defer cleanup() + + surveyTasks := initTasks(svc) + + if cmd.Flags().Changed("interface") { + if _, ok := interfaces[surveyCmd.InterfaceType]; !ok { + return fmt.Errorf("invalid interface, valid are (tui, repl, web)") + } + interfaces = map[string]task.Interface{ + surveyCmd.InterfaceType: interfaces[surveyCmd.InterfaceType], + } + } + + if cmd.Flags().Changed("stage") { + if surveyCmd.Task < 1 || surveyCmd.Task > len(surveyTasks) { + return fmt.Errorf("invalid stage") + } + if err := task.RunTask(cmd.Context(), surveyTasks[surveyCmd.Task-1], + interfaces[surveyCmd.InterfaceType], tasks.InterfaceToType(interfaces[surveyCmd.InterfaceType])); err != nil { + return err + } + return nil + } + + if err := newIntroModel().Run(); err != nil { + return returnIfUserQuit(err, "failed to run intro") + } + + if err := taskLoop(cmd.Context(), surveyTasks, interfaces); err != nil { + return returnIfUserQuit(err, "task loop failed") + } + return nil } func taskLoop(ctx context.Context, surveyTasks []task.Tasker, interfaces map[string]task.Interface) error { - var ifaceNames []string + var iNames []string for name := range interfaces { - ifaceNames = append(ifaceNames, name) + iNames = append(iNames, name) } - rand.Shuffle(len(ifaceNames), func(i, j int) { - ifaceNames[i], ifaceNames[j] = ifaceNames[j], ifaceNames[i] + rand.Shuffle(len(iNames), func(i, j int) { + iNames[i], iNames[j] = iNames[j], iNames[i] }) for i, t := range surveyTasks { - idx := i % len(ifaceNames) - selected := interfaces[ifaceNames[idx]] + idx := i % len(iNames) + selected := interfaces[iNames[idx]] - if err := runTask(ctx, t, selected); err != nil { + if err := task.RunTask(ctx, t, selected, tasks.InterfaceToType(selected)); err != nil { return err } } diff --git a/cmd/survey/tasks/base.go b/cmd/survey/tasks/base.go index ccc8bbe..3432e5d 100644 --- a/cmd/survey/tasks/base.go +++ b/cmd/survey/tasks/base.go @@ -2,7 +2,7 @@ package tasks import ( "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/cli/repl" + "github.com/LazyBachelor/LazyPM/pkg/repl" "github.com/LazyBachelor/LazyPM/pkg/task" taskui "github.com/LazyBachelor/LazyPM/pkg/task/ui" "github.com/LazyBachelor/LazyPM/pkg/tui" diff --git a/pkg/cli/commands/close.go b/internal/commands/issues/close.go similarity index 87% rename from pkg/cli/commands/close.go rename to internal/commands/issues/close.go index 2bafcbb..3c3dc80 100644 --- a/pkg/cli/commands/close.go +++ b/internal/commands/issues/close.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "fmt" @@ -7,9 +7,9 @@ import ( "github.com/spf13/cobra" ) -// closeCmd represents the close command, +// CloseCmd represents the close command, // which allows users to close an existing issue by its ID. -var closeCmd = &cobra.Command{ +var CloseCmd = &cobra.Command{ Use: "close [id]", Short: "Close an existing issue", Long: `Close an existing issue by its ID.`, @@ -58,8 +58,3 @@ func runCloseCmd(cmd *cobra.Command, args []string) error { return nil } - -// init function to set up the close command and its flags. -func init() { - rootCmd.AddCommand(closeCmd) -} diff --git a/pkg/cli/commands/completion.go b/internal/commands/issues/completion.go similarity index 99% rename from pkg/cli/commands/completion.go rename to internal/commands/issues/completion.go index e164585..7330a28 100644 --- a/pkg/cli/commands/completion.go +++ b/internal/commands/issues/completion.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "context" diff --git a/pkg/cli/commands/create.go b/internal/commands/issues/create.go similarity index 82% rename from pkg/cli/commands/create.go rename to internal/commands/issues/create.go index a270f03..17a9864 100644 --- a/pkg/cli/commands/create.go +++ b/internal/commands/issues/create.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "fmt" @@ -18,8 +18,8 @@ const ( pm create Fix bug --desc "Bug description" --status in_progress --type bug --priority 5` ) -// createCmd represents the create command, which allows users to create a new issue with specified details. -var createCmd = &cobra.Command{ +// CreateCmd represents the create command, which allows users to create a new issue with specified details. +var CreateCmd = &cobra.Command{ Use: "create [title]", Short: "Create a new issue", Long: `Create a new issue with the specified details.`, @@ -105,15 +105,13 @@ func runCreateInteractive() error { // init function to set up the create command and its flags. func init() { - createCmd.Flags().BoolVarP(&createFlags.interactive, "interactive", "i", false, "Create issue interactively") - createCmd.Flags().StringVarP(&createFlags.description, "desc", "d", "", "Issue description") - createCmd.Flags().StringVarP(&createFlags.status, "status", "s", "open", "Issue status(open, closed, in_progress)") - createCmd.Flags().StringVarP(&createFlags.issueType, "type", "t", "task", "Issue type(bug, feature, task)") - createCmd.Flags().IntVarP(&createFlags.priority, "priority", "p", 0, "Issue priority(0-4)") + CreateCmd.Flags().BoolVarP(&createFlags.interactive, "interactive", "i", false, "Create issue interactively") + CreateCmd.Flags().StringVarP(&createFlags.description, "desc", "d", "", "Issue description") + CreateCmd.Flags().StringVarP(&createFlags.status, "status", "s", "open", "Issue status(open, closed, in_progress)") + CreateCmd.Flags().StringVarP(&createFlags.issueType, "type", "t", "task", "Issue type(bug, feature, task)") + CreateCmd.Flags().IntVarP(&createFlags.priority, "priority", "p", 0, "Issue priority(0-4)") - createCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) - createCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) - createCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) - - rootCmd.AddCommand(createCmd) + CreateCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) + CreateCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) + CreateCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) } diff --git a/pkg/cli/commands/delete.go b/internal/commands/issues/delete.go similarity index 92% rename from pkg/cli/commands/delete.go rename to internal/commands/issues/delete.go index 0b66590..8a4ae2e 100644 --- a/pkg/cli/commands/delete.go +++ b/internal/commands/issues/delete.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "context" @@ -17,8 +17,8 @@ var ( deleteInteractive bool ) -// deleteCmd represents the delete command. -var deleteCmd = &cobra.Command{ +// DeleteCmd represents the delete command. +var DeleteCmd = &cobra.Command{ Use: "delete [id]", Short: "Delete an existing issue", Long: `Delete an existing issue by its ID.`, @@ -125,8 +125,6 @@ func runDeleteInteractive(ctx context.Context) error { // init function to set up the delete command and its flags. func init() { - deleteCmd.Flags().BoolVarP(&deleteInteractive, "interactive", "i", false, "Delete issues interactively") - deleteCmd.Flags().BoolVarP(&confirmDelete, "yes", "y", true, "Confirm deletion without prompt") - - rootCmd.AddCommand(deleteCmd) + DeleteCmd.Flags().BoolVarP(&deleteInteractive, "interactive", "i", false, "Delete issues interactively") + DeleteCmd.Flags().BoolVarP(&confirmDelete, "yes", "y", true, "Confirm deletion without prompt") } diff --git a/pkg/cli/commands/ls.go b/internal/commands/issues/list.go similarity index 63% rename from pkg/cli/commands/ls.go rename to internal/commands/issues/list.go index 1eb13cc..79f55e0 100644 --- a/pkg/cli/commands/ls.go +++ b/internal/commands/issues/list.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "strings" @@ -17,8 +17,8 @@ pm list --title "New feature" --desc "feature description" pm list -p 1 -l 10` ) -// getIssuesCmd represents the get issues command. -var getIssuesCmd = &cobra.Command{ +// ListCmd represents the get issues command. +var ListCmd = &cobra.Command{ Use: "list [search query]", Short: "List all issues", Long: `List all issues in the project management system.`, @@ -70,17 +70,15 @@ func runGetIssuesCmd(cmd *cobra.Command, args []string) error { // init function to set up the get issues command and its flags. func init() { - getIssuesCmd.Flags().StringVar(&listFlags.title, "title", "", "Filter issues by title") - getIssuesCmd.Flags().StringVarP(&listFlags.description, "desc", "d", "", "Filter issues by description") - getIssuesCmd.Flags().StringVarP(&listFlags.status, "status", "s", "", "Filter issues by status (open, closed, in_progress)") - getIssuesCmd.Flags().StringVarP(&listFlags.issueType, "type", "t", "", "Filter issues by type (bug, feature, task)") - getIssuesCmd.Flags().IntVarP(&listFlags.priority, "priority", "p", 0, "Filter issues by priority (0-4)") + ListCmd.Flags().StringVar(&listFlags.title, "title", "", "Filter issues by title") + ListCmd.Flags().StringVarP(&listFlags.description, "desc", "d", "", "Filter issues by description") + ListCmd.Flags().StringVarP(&listFlags.status, "status", "s", "", "Filter issues by status (open, closed, in_progress)") + ListCmd.Flags().StringVarP(&listFlags.issueType, "type", "t", "", "Filter issues by type (bug, feature, task)") + ListCmd.Flags().IntVarP(&listFlags.priority, "priority", "p", 0, "Filter issues by priority (0-4)") - getIssuesCmd.Flags().IntVarP(&listFlags.limit, "limit", "l", 25, "Limit the number of issues returned") + ListCmd.Flags().IntVarP(&listFlags.limit, "limit", "l", 25, "Limit the number of issues returned") - getIssuesCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) - getIssuesCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) - getIssuesCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) - - rootCmd.AddCommand(getIssuesCmd) + ListCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) + ListCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) + ListCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) } diff --git a/pkg/cli/commands/read.go b/internal/commands/issues/read.go similarity index 88% rename from pkg/cli/commands/read.go rename to internal/commands/issues/read.go index 40c8c8c..c070fcf 100644 --- a/pkg/cli/commands/read.go +++ b/internal/commands/issues/read.go @@ -1,12 +1,12 @@ -package commands +package issuesCmd import ( "github.com/LazyBachelor/LazyPM/internal/models" "github.com/spf13/cobra" ) -// getIssueCmd represents the get issue command. -var getIssueCmd = &cobra.Command{ +// GetCmd represents the get issue command. +var GetCmd = &cobra.Command{ Use: "describe [issue ID]", Short: "Get issue details", Long: `Get issue details by ID`, @@ -44,5 +44,4 @@ func runGetCmd(cmd *cobra.Command, args []string) error { // init function to set up the get issue command. func init() { - rootCmd.AddCommand(getIssueCmd) } diff --git a/pkg/cli/commands/root.go b/internal/commands/issues/root.go similarity index 69% rename from pkg/cli/commands/root.go rename to internal/commands/issues/root.go index 45b1c45..16c24f2 100644 --- a/pkg/cli/commands/root.go +++ b/internal/commands/issues/root.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "bytes" @@ -29,8 +29,8 @@ type Flags struct { priority int } -// rootCmd is the base command for the CLI application. -var rootCmd = &cobra.Command{ +// RootCmd is the base command for the CLI application. +var RootCmd = &cobra.Command{ Short: "Project Management CLI", Long: `Project Management CLI for managing issues and tasks.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { @@ -45,7 +45,7 @@ var rootCmd = &cobra.Command{ // Must be called before executing any commands to ensure services are available. func SetApp(application *service.App) { app = application - rootCmd.Use = app.Config.RootCmd + RootCmd.Use = app.Config.RootCmd } // AppFromContext retrieves the App from the command context @@ -57,37 +57,37 @@ func AppFromContext(ctx context.Context) *service.App { return app } -// Execute executes the root command using the fang library. -func Execute() error { - return fang.Execute(context.Background(), rootCmd, - fang.WithColorSchemeFunc(fang.AnsiColorScheme)) -} - // ExecuteArgs executes the command with the given arguments using the fang library. func ExecuteArgs(args []string) error { - rootCmd.SetArgs(args) - return fang.Execute(context.Background(), rootCmd, + RootCmd.SetArgs(args) + return fang.Execute(context.Background(), RootCmd, fang.WithColorSchemeFunc(fang.AnsiColorScheme)) } // ExecuteArgsString executes the command with the given arguments and returns the output as a string. // This is useful for testing command outputs and used in the REPL func ExecuteArgsString(args []string) (string, error) { + return ExecuteArgsStringWithContext(context.Background(), args) +} + +// ExecuteArgsStringWithContext executes the command with context and returns the output as a string. +func ExecuteArgsStringWithContext(ctx context.Context, args []string) (string, error) { buf := new(bytes.Buffer) - rootCmd.SetOut(buf) - rootCmd.SetErr(buf) - rootCmd.SetArgs(args) + RootCmd.SetOut(buf) + RootCmd.SetErr(buf) + RootCmd.SetArgs(args) + RootCmd.SetContext(ctx) - err := rootCmd.Execute() + err := RootCmd.Execute() return buf.String(), err } // init function to set up the command hierarchy and options. func init() { - rootCmd.CompletionOptions.DisableDefaultCmd = false - rootCmd.AddGroup(&cobra.Group{ID: "help", Title: "Helping Commands"}) - rootCmd.SetCompletionCommandGroupID("help") - rootCmd.SetHelpCommandGroupID("help") + RootCmd.CompletionOptions.DisableDefaultCmd = false + RootCmd.AddGroup(&cobra.Group{ID: "help", Title: "Helping Commands"}) + RootCmd.SetCompletionCommandGroupID("help") + RootCmd.SetHelpCommandGroupID("help") } diff --git a/pkg/cli/commands/update.go b/internal/commands/issues/update.go similarity index 80% rename from pkg/cli/commands/update.go rename to internal/commands/issues/update.go index f26b23e..92b87a8 100644 --- a/pkg/cli/commands/update.go +++ b/internal/commands/issues/update.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "fmt" @@ -9,7 +9,7 @@ import ( var updateFlags Flags -var updateCmd = &cobra.Command{ +var UpdateCmd = &cobra.Command{ Use: "update [issue ID]", Short: "Update an existing issue", Long: `Update an existing issue by its ID with the specified details.`, @@ -55,17 +55,15 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error { } func init() { - updateCmd.Flags().StringVar(&updateFlags.title, "title", "", "New issue title") - updateCmd.Flags().StringVarP(&updateFlags.description, "desc", "d", "", "New issue description") - updateCmd.Flags().StringVarP(&updateFlags.status, "status", "s", "", "New issue status(open, closed, in_progress)") - updateCmd.Flags().StringVarP(&updateFlags.issueType, "type", "t", "", "New issue type(bug, feature, task)") - updateCmd.Flags().IntVarP(&updateFlags.priority, "priority", "p", 0, "New issue priority(0-5)") + UpdateCmd.Flags().StringVar(&updateFlags.title, "title", "", "New issue title") + UpdateCmd.Flags().StringVarP(&updateFlags.description, "desc", "d", "", "New issue description") + UpdateCmd.Flags().StringVarP(&updateFlags.status, "status", "s", "", "New issue status(open, closed, in_progress)") + UpdateCmd.Flags().StringVarP(&updateFlags.issueType, "type", "t", "", "New issue type(bug, feature, task)") + UpdateCmd.Flags().IntVarP(&updateFlags.priority, "priority", "p", 0, "New issue priority(0-5)") - updateCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) - updateCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) - updateCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) - - rootCmd.AddCommand(updateCmd) + UpdateCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) + UpdateCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) + UpdateCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) } func getUpdateValues(cmd *cobra.Command) (map[string]interface{}, error) { diff --git a/internal/commands/survey/list.go b/internal/commands/survey/list.go new file mode 100644 index 0000000..6b4d6fa --- /dev/null +++ b/internal/commands/survey/list.go @@ -0,0 +1,17 @@ +package surveyCmd + +import ( + "github.com/LazyBachelor/LazyPM/pkg/task" + "github.com/spf13/cobra" +) + +var ListCmd = &cobra.Command{ + Use: "list", + Short: "List available tasks", + RunE: func(cmd *cobra.Command, args []string) error { + for i, name := range task.List() { + cmd.Printf("%d. %s\n", i+1, name) + } + return nil + }, +} diff --git a/internal/commands/survey/root.go b/internal/commands/survey/root.go new file mode 100644 index 0000000..b5e00a9 --- /dev/null +++ b/internal/commands/survey/root.go @@ -0,0 +1,29 @@ +package surveyCmd + +import ( + "github.com/spf13/cobra" +) + +var ( + InterfaceType string + Task int +) + +// RootCmd is the base command for the survey CLI. +var RootCmd = &cobra.Command{ + Use: "survey", + Long: `Project Management Interface Survey + +Thank you for participating in our survey! + +We are gathering data on how users interact with different task management interfaces to better understand their preferences and compare their usability. +This survey will present you with a series of tasks to complete using various interfaces, including command-line, web-based, and terminal user interfaces. + +Please answer the questions honestly and to the best of your ability. +Your responses will be kept confidential and used solely for research purposes.`} + +func init() { + RootCmd.CompletionOptions.DisableDefaultCmd = true + StartCmd.Flags().StringVarP(&InterfaceType, "interface", "i", "tui", "Specify interface.") + StartCmd.Flags().IntVarP(&Task, "task", "t", 1, "Run task directly") +} diff --git a/internal/commands/survey/start.go b/internal/commands/survey/start.go new file mode 100644 index 0000000..c987941 --- /dev/null +++ b/internal/commands/survey/start.go @@ -0,0 +1,9 @@ +package surveyCmd + +import "github.com/spf13/cobra" + +// StartCmd is the start command - RunE is set in cmd/survey/ +var StartCmd = &cobra.Command{ + Use: "start", + Short: "Start the user survey", +} diff --git a/internal/commands/survey/status.go b/internal/commands/survey/status.go new file mode 100644 index 0000000..8983bff --- /dev/null +++ b/internal/commands/survey/status.go @@ -0,0 +1,30 @@ +package surveyCmd + +import ( + "github.com/LazyBachelor/LazyPM/internal/commands/issues" + "github.com/spf13/cobra" +) + +// StatusCmd displays the current task validation status +var StatusCmd = &cobra.Command{ + Use: "status", + Short: "Check task validation status", + Long: "Displays the current task validation status and feedback.", + RunE: runStatusCmd, +} + +func runStatusCmd(cmd *cobra.Command, args []string) error { + app := issuesCmd.AppFromContext(cmd.Context()) + if app == nil || app.CurrentFeedback == nil { + cmd.Println("No validation status available.") + return nil + } + + if app.CurrentFeedback.Message == "" { + cmd.Println("No validation status available yet.") + return nil + } + + cmd.Print(app.CurrentFeedback.Message) + return nil +} diff --git a/internal/commands/survey/submit.go b/internal/commands/survey/submit.go new file mode 100644 index 0000000..9766e6c --- /dev/null +++ b/internal/commands/survey/submit.go @@ -0,0 +1,12 @@ +package surveyCmd + +import "github.com/spf13/cobra" + +var SubmitCmd = &cobra.Command{ + Use: "submit", + Short: "Submit your survey responses", + RunE: func(cmd *cobra.Command, args []string) error { + cmd.Println("Submitting responses and metrics...") + return nil + }, +} diff --git a/internal/service/interfaces.go b/internal/service/interfaces.go index 33095ba..6275d27 100644 --- a/internal/service/interfaces.go +++ b/internal/service/interfaces.go @@ -8,11 +8,18 @@ import ( "github.com/steveyegge/beads" ) +// ValidationFeedback holds task validation status +type ValidationFeedback struct { + Success bool + Message string +} + type App struct { - Config Config - Issues IssueService - Stats StatsService - Logger *slog.Logger + Config Config + Issues IssueService + Stats StatsService + Logger *slog.Logger + CurrentFeedback *ValidationFeedback } type IssueService interface { diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 420e37d..1b9d01f 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -4,17 +4,23 @@ package cli import ( "context" + "github.com/LazyBachelor/LazyPM/internal/commands/issues" "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/cli/commands" + "github.com/charmbracelet/fang" + "github.com/spf13/cobra" ) // Config is an alias for service.Config, used to configure the CLI. type Config = service.Config -type CLI struct{} +type CLI struct { + RootCmd *cobra.Command +} -func NewCli() *CLI { - return &CLI{} +func NewCli(rootCmd *cobra.Command) *CLI { + return &CLI{ + RootCmd: rootCmd, + } } // Run initializes the services and executes the CLI commands. @@ -26,9 +32,10 @@ func (c *CLI) Run(ctx context.Context, config Config) error { defer cleanup() - commands.SetApp(app) + issuesCmd.SetApp(app) - if err := commands.Execute(); err != nil { + if err := fang.Execute(ctx, c.RootCmd, + fang.WithColorSchemeFunc(fang.AnsiColorScheme)); err != nil { return err } @@ -44,9 +51,9 @@ func (c *CLI) RunWithArgs(ctx context.Context, config Config, args []string) err defer cleanup() - commands.SetApp(app) + issuesCmd.SetApp(app) - if err := commands.ExecuteArgs(args); err != nil { + if err := issuesCmd.ExecuteArgs(args); err != nil { return err } diff --git a/pkg/cli/commands/status.go b/pkg/cli/commands/status.go deleted file mode 100644 index ad11ec8..0000000 --- a/pkg/cli/commands/status.go +++ /dev/null @@ -1,46 +0,0 @@ -package commands - -import ( - "github.com/LazyBachelor/LazyPM/pkg/task" - "github.com/spf13/cobra" -) - -// replInstance holds a reference to the REPL for accessing validation feedback -var replInstance interface { - GetCurrentFeedback() task.ValidationFeedback -} - -// SetRepl sets the REPL instance for use by commands -func SetRepl(repl interface { - GetCurrentFeedback() task.ValidationFeedback -}) { - replInstance = repl -} - -// StatusCmd displays the current task validation status -var StatusCmd = &cobra.Command{ - Use: "status", - Short: "Check task validation status", - Long: "Displays the current task validation status and feedback.", - RunE: runStatusCmd, -} - -func runStatusCmd(cmd *cobra.Command, args []string) error { - if replInstance == nil { - cmd.Println("No task validation available") - return nil - } - - feedback := replInstance.GetCurrentFeedback() - if feedback.Message == "" { - cmd.Println("No validation status available yet.") - return nil - } - - cmd.Print(feedback.Message) - - return nil -} -func init() { - rootCmd.AddCommand(StatusCmd) -} diff --git a/pkg/cli/styles/styles.go b/pkg/cli/styles/styles.go deleted file mode 100644 index e4bf744..0000000 --- a/pkg/cli/styles/styles.go +++ /dev/null @@ -1,10 +0,0 @@ -// Package styles defines the styling for the CLI output using the lipgloss library. -package styles - -import "github.com/charmbracelet/lipgloss" - -var ( - TitleStyle = lipgloss.NewStyle().Bold(true).Padding(1) - - CommandStyle = lipgloss.NewStyle().Padding(1) -) diff --git a/pkg/cli/repl/completer.go b/pkg/repl/completer.go similarity index 100% rename from pkg/cli/repl/completer.go rename to pkg/repl/completer.go diff --git a/pkg/cli/repl/executor.go b/pkg/repl/executor.go similarity index 63% rename from pkg/cli/repl/executor.go rename to pkg/repl/executor.go index c0c50a7..037528f 100644 --- a/pkg/cli/repl/executor.go +++ b/pkg/repl/executor.go @@ -4,12 +4,10 @@ import ( "os/exec" "strings" - "github.com/LazyBachelor/LazyPM/pkg/cli/commands" + "github.com/LazyBachelor/LazyPM/internal/commands/issues" ) // execute processes the input command and returns the output -// or an error if it occurs. It handles what type of command is being executed, -// whether it's a PM command or a shell command, and routes it accordingly. func execute(input string) (string, error) { if input == "" { return "", nil @@ -29,8 +27,6 @@ func execute(input string) (string, error) { return executeShellCommand(input) } -// executeShellCommand executes a shell command -// and returns its output or an error if it occurs. func executeShellCommand(input string) (string, error) { parts := strings.Fields(input) if len(parts) == 0 { @@ -42,14 +38,12 @@ func executeShellCommand(input string) (string, error) { return string(output), err } -// executePMCommand executes a PM command using the commands package -// and returns its output or an error if it occurs. func executePMCommand(input string) (string, error) { parts := strings.Fields(input) if len(parts) == 0 { return "", nil } - output, err := commands.ExecuteArgsString(parts) + output, err := issuesCmd.ExecuteArgsString(parts) return output, err } diff --git a/pkg/cli/repl/options.go b/pkg/repl/options.go similarity index 100% rename from pkg/cli/repl/options.go rename to pkg/repl/options.go diff --git a/pkg/cli/repl/repl.go b/pkg/repl/repl.go similarity index 74% rename from pkg/cli/repl/repl.go rename to pkg/repl/repl.go index ff49153..bfbf51f 100644 --- a/pkg/cli/repl/repl.go +++ b/pkg/repl/repl.go @@ -7,11 +7,13 @@ import ( "os" "strings" + "github.com/LazyBachelor/LazyPM/internal/commands/issues" + surveyCmd "github.com/LazyBachelor/LazyPM/internal/commands/survey" "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/internal/style" "github.com/LazyBachelor/LazyPM/pkg/cli" - "github.com/LazyBachelor/LazyPM/pkg/cli/commands" - "github.com/LazyBachelor/LazyPM/pkg/cli/styles" "github.com/LazyBachelor/LazyPM/pkg/task" + "github.com/LazyBachelor/LazyPM/pkg/tui/styles" "github.com/c-bata/go-prompt" "golang.org/x/term" ) @@ -27,6 +29,7 @@ You can also run shell commands directly. Type 'exit' or 'quit' to leave.` type REPL struct { feedbackChan chan task.ValidationFeedback quitChan chan bool + app *service.App currentFeedback task.ValidationFeedback exitRequested bool @@ -55,12 +58,12 @@ func (r *REPL) Run(ctx context.Context, config cli.Config) error { defer cleanup() // Make sure to set app, to ensure they are available. - commands.SetApp(app) + issuesCmd.SetApp(app) - // Set the REPL instance so status command can access it - commands.SetRepl(r) + // Store app reference for updating feedback + r.app = app - fmt.Println(styles.TitleStyle.Render(ReplTitle)) // Print REPL title. + fmt.Println(style.TitleStyle.Render(ReplTitle)) // Print REPL title. // Start goroutine to watch for validation feedback and quit signals if r.feedbackChan != nil && r.quitChan != nil { @@ -102,8 +105,8 @@ func (r *REPL) Run(ctx context.Context, config cli.Config) error { // Add the input to the history for future navigation. history = append(history, input) - output, _ := execute(input) // Ignore errors for now, gives better ux - fmt.Println(styles.CommandStyle.Render(output)) // Print the output of the command in a styled format. + output, _ := execute(input) // Ignore errors for now, gives better ux + fmt.Println(style.TextStyle.Render(output)) // Print the output of the command in a styled format. } return nil @@ -114,6 +117,13 @@ func (r *REPL) watchValidation() { select { case feedback := <-r.feedbackChan: r.currentFeedback = feedback + // Update app's CurrentFeedback so status command can access it + if r.app != nil { + r.app.CurrentFeedback = &service.ValidationFeedback{ + Success: feedback.Success, + Message: feedback.Message, + } + } if feedback.Success { fmt.Printf("\n%s\n", styles.TitleStyle.Render("Task completed successfully!")) fmt.Println("Press Enter to exit...") @@ -127,13 +137,18 @@ func (r *REPL) watchValidation() { } } -// GetCurrentFeedback returns the current validation feedback for the status command -func (r *REPL) GetCurrentFeedback() task.ValidationFeedback { - return r.currentFeedback -} - // SetChannels sets the channels for receiving validation feedback and quit signals from the task interface func (r *REPL) SetChannels(feedbackChan chan task.ValidationFeedback, quitChan chan bool) { r.feedbackChan = feedbackChan r.quitChan = quitChan } + +func init() { + issuesCmd.RootCmd.AddCommand(issuesCmd.GetCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.ListCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.CloseCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.CreateCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.DeleteCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.UpdateCmd) + issuesCmd.RootCmd.AddCommand(surveyCmd.StatusCmd) +} diff --git a/pkg/cli/repl/suggestions.go b/pkg/repl/suggestions.go similarity index 97% rename from pkg/cli/repl/suggestions.go rename to pkg/repl/suggestions.go index e99c681..522afa1 100644 --- a/pkg/cli/repl/suggestions.go +++ b/pkg/repl/suggestions.go @@ -4,7 +4,7 @@ import ( "context" "strings" - "github.com/LazyBachelor/LazyPM/pkg/cli/commands" + "github.com/LazyBachelor/LazyPM/internal/commands/issues" "github.com/c-bata/go-prompt" "github.com/muesli/reflow/truncate" ) @@ -21,6 +21,7 @@ var rootSuggestions = []prompt.Suggest{ // commandSuggestions is a list of prompt suggestions for PM commands. var baseSuggestions = []prompt.Suggest{ {Text: "help", Description: "Show help information"}, + {Text: "status", Description: "Show task status"}, {Text: "delete", Description: "Delete an issue by ID"}, {Text: "close", Description: "Close an issue by ID"}, {Text: "create", Description: "Create a new issue with title"}, @@ -148,7 +149,7 @@ func issueIDSuggestions(partial string, hasCommand bool) []prompt.Suggest { return nil } - issues, _ := commands.GetIssueCompletions(context.Background(), partial) + issues, _ := issuesCmd.GetIssueCompletions(context.Background(), partial) var suggestions []prompt.Suggest for _, issue := range issues { diff --git a/pkg/task/runner.go b/pkg/task/runner.go index d060cdd..3af739f 100644 --- a/pkg/task/runner.go +++ b/pkg/task/runner.go @@ -15,7 +15,7 @@ import ( // 3. Run the interface // 4. Start validation loop in background // 5. Show questionnaire when done -func RunTask(ctx context.Context, t Tasker, i Interface, ifaceType InterfaceType) error { +func RunTask(ctx context.Context, t Tasker, i Interface, iType InterfaceType) error { doneChan := make(chan bool, 1) quitChan := make(chan bool, 1) feedbackChan := make(chan ValidationFeedback, 10) @@ -65,7 +65,7 @@ func RunTask(ctx context.Context, t Tasker, i Interface, ifaceType InterfaceType } // Show questionnaire - questions := t.Questions(ifaceType) + questions := t.Questions(iType) questionare := taskui.NewQuestionnaireModel(questions) model, err = tea.NewProgram(questionare, tea.WithAltScreen()).Run() if err != nil {