refactor to make cli better and simplify the code

This commit is contained in:
Robin Olsen
2026-03-01 20:42:56 +01:00
parent a266ce5464
commit 9612cb9338
32 changed files with 182 additions and 255 deletions

View File

@@ -16,7 +16,7 @@ before:
- go generate ./...
builds:
- main: ./cmd/survey
- main: ./cmd/pm
env:
- CGO_ENABLED=0
goos:

View File

@@ -15,7 +15,7 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
-a -installsuffix cgo \
-ldflags="-w -s" \
-trimpath \
-o pm ./cmd/survey/
-o pm ./cmd/pm/
FROM gcr.io/distroless/static-debian13
WORKDIR /data

View File

@@ -13,15 +13,12 @@ clean:
@rm -f package-lock.json
@rm -f package.json
@rm -f ./build/pm
@rm -f ./build/survey
@rm -f ./build/survey_bash.sh
@rm -f ./build/pm_bash.sh
build: tidy
@go build -o ./bin/pm ./cmd/pm
@go build -o ./bin/tui ./cmd/tui
@go build -o ./bin/web ./cmd/web
@go build -o ./bin/survey ./cmd/survey
@echo "Build completed successfully. Binaries are located in the ./bin directory."
enable-multiplatform-build:
@@ -41,8 +38,6 @@ docker-push:
os-build: build completions
@cp ./bin/pm ./build/pm
@cp ./bin/survey ./build/survey
@cp ./bin/survey_bash.sh ./build/survey_bash.sh
@cp ./bin/pm_bash.sh ./build/pm_bash.sh
@docker build -t telikz/lazyos ./build
@echo "Built lazyos image successfully. You can run it using 'make os-run'."
@@ -60,7 +55,7 @@ os-push:
@docker push telikz/lazyos
start:
@go run ./cmd/survey start
@go run ./cmd/pm survey start
cli: tidy
go run ./cmd/pm
@@ -85,12 +80,10 @@ tw: tw-install
completions:
@mkdir -p ./bin
@go build -o ./bin/pm ./cmd/pm
@go build -o ./bin/survey ./cmd/survey
@./bin/survey completion bash > ./bin/survey_bash.sh
@./bin/pm completion bash > ./bin/pm_bash.sh
@./bin/pm completion zsh > ./bin/pm_zsh.sh
@./bin/pm completion fish > ./bin/pm_fish.sh
@./bin/pm completion powershell > ./bin/pm_powershell.ps1
@DEV=True ./bin/pm completion bash > ./bin/pm_bash.sh
@DEV=True ./bin/pm completion zsh > ./bin/pm_zsh.sh
@DEV=True ./bin/pm completion fish > ./bin/pm_fish.sh
@DEV=True ./bin/pm completion powershell > ./bin/pm_powershell.ps1
install-bash-temp: completions
@go install ./cmd/pm
@@ -111,9 +104,7 @@ install-powershell-temp: completions
install-cli: completions
@go install ./cmd/pm
@go install ./cmd/survey
@sudo cp ./bin/pm_bash.sh /etc/bash_completion.d/pm
@sudo cp ./bin/survey_bash.sh /etc/bash_completion.d/survey
.PHONY: tidy clean build docker-build docker-run docker-push os-build os-run os-stop os-push start cli tui web tw-install dev tw completions install-bash-temp install-zsh-temp install-fish-temp install-powershell-temp install-cli

147
cmd/pm/init.go Normal file
View File

@@ -0,0 +1,147 @@
package main
import (
"context"
"os"
"github.com/LazyBachelor/LazyPM/cmd/pm/tasks"
"github.com/LazyBachelor/LazyPM/internal/app"
issues "github.com/LazyBachelor/LazyPM/internal/commands/issues"
survey "github.com/LazyBachelor/LazyPM/internal/commands/survey"
"github.com/LazyBachelor/LazyPM/pkg/repl"
"github.com/LazyBachelor/LazyPM/pkg/task"
"github.com/LazyBachelor/LazyPM/pkg/tui"
"github.com/LazyBachelor/LazyPM/pkg/web"
"github.com/spf13/cobra"
_ "github.com/LazyBachelor/LazyPM/cmd/pm/tasks"
)
func init() {
task.RegisterInterface("tui", tui.New())
task.RegisterInterface("web", web.New())
task.RegisterInterface("repl", repl.New())
task.RegisterTask("create_issue", func(app *app.App) task.Tasker {
return tasks.NewCreateIssueTask(app)
})
task.RegisterTask("coding_task", func(app *app.App) task.Tasker {
return tasks.NewCodingTask(app)
})
task.RegisterTask("git_task", func(app *app.App) task.Tasker {
return tasks.NewGitTask(app)
})
task.RegisterTask("sprint_planning", func(app *app.App) task.Tasker {
return tasks.NewSprintPlanningTask(app)
})
task.RegisterTask("issue_triage", func(app *app.App) task.Tasker {
return tasks.NewIssueTriageTask(app)
})
task.RegisterTask("milestone_tracking", func(app *app.App) task.Tasker {
return tasks.NewMilestoneTrackingTask(app)
})
task.RegisterTask("dependency_management", func(app *app.App) task.Tasker {
return tasks.NewDependencyManagementTask(app)
})
task.RegisterTask("team_capacity", func(app *app.App) task.Tasker {
return tasks.NewTeamCapacityTask(app)
})
task.RegisterTask("report_generation", func(app *app.App) task.Tasker {
return tasks.NewReportGenerationTask(app)
})
task.RegisterTask("stakeholder_update", func(app *app.App) task.Tasker {
return tasks.NewStakeholderUpdateTask(app)
})
task.RegisterTask("priority_management", func(app *app.App) task.Tasker {
return tasks.NewPriorityManagementTask(app)
})
task.RegisterTask("backlog_refinement", func(app *app.App) task.Tasker {
return tasks.NewBacklogRefinementTask(app)
})
cobra.EnableCommandSorting = false
if os.Getenv("DEV") == "True" {
issues.RootCmd.SetCompletionCommandGroupID("other")
} else {
issues.RootCmd.CompletionOptions.DisableDefaultCmd = true
}
RootCmd.AddGroup(&cobra.Group{ID: "issues", Title: "Issue Management"})
issues.CreateCmd.GroupID = "issues"
issues.GetCmd.GroupID = "issues"
issues.ListCmd.GroupID = "issues"
issues.UpdateCmd.GroupID = "issues"
issues.CloseCmd.GroupID = "issues"
issues.DeleteCmd.GroupID = "issues"
RootCmd.AddCommand(issues.CreateCmd)
RootCmd.AddCommand(issues.GetCmd)
RootCmd.AddCommand(issues.ListCmd)
RootCmd.AddCommand(issues.UpdateCmd)
RootCmd.AddCommand(issues.CloseCmd)
RootCmd.AddCommand(issues.DeleteCmd)
RootCmd.AddGroup(&cobra.Group{ID: "comment", Title: "Comment Management"})
issues.CommentCmd.GroupID = "comment"
issues.CommentsCmd.GroupID = "comment"
RootCmd.AddCommand(issues.CommentCmd)
RootCmd.AddCommand(issues.CommentsCmd)
var SurveyRootCmd = survey.RootCmd
SurveyRootCmd.GroupID = "survey"
RootCmd.AddGroup(&cobra.Group{ID: "survey", Title: "Survey Commands"})
survey.StartCmd.RunE = runStartCmd
SurveyRootCmd.AddCommand(survey.StartCmd)
SurveyRootCmd.AddCommand(survey.StatusCmd)
SurveyRootCmd.AddCommand(survey.SubmitCmd)
SurveyRootCmd.AddCommand(survey.ListTasksCmd)
SurveyRootCmd.AddCommand(survey.ListInterfacesCmd)
RootCmd.AddCommand(SurveyRootCmd)
RootCmd.AddGroup(&cobra.Group{ID: "other", Title: "Additional Commands"})
RootCmd.SetHelpCommandGroupID("other")
RootCmd.AddCommand(replCmd)
}
var replCmd = &cobra.Command{
Use: "repl",
GroupID: "other",
Short: "Start the interactive REPL interface",
Long: `Start the interactive Read-Eval-Print Loop (REPL) for managing your projects and issues in an interactive terminal environment.`,
RunE: func(cmd *cobra.Command, args []string) error {
return repl.New().Run(cmd.Context(), App.Config)
},
}
func initializeServices(ctx context.Context) (*app.App, func(), error) {
return app.New(ctx, tasks.BaseConfig().WithAutoInit(true))
}
func initInterfaces() map[string]task.Interface {
interfaces := make(map[string]task.Interface)
for _, name := range task.ListInterfaces() {
i, err := task.GetInterface(name)
if err != nil {
continue
}
interfaces[name] = i
}
return interfaces
}
func initTasks(app *app.App) map[string]task.Tasker {
taskMap := make(map[string]task.Tasker)
for _, name := range task.ListTasks() {
t, err := task.GetTask(name, app)
if err != nil {
continue
}
taskMap[name] = t
}
return taskMap
}

View File

@@ -3,26 +3,29 @@ package main
import (
"context"
"github.com/LazyBachelor/LazyPM/internal/app"
issues "github.com/LazyBachelor/LazyPM/internal/commands/issues"
survey "github.com/LazyBachelor/LazyPM/internal/commands/survey"
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/LazyBachelor/LazyPM/pkg/cli"
"github.com/charmbracelet/fang"
)
var App *app.App
var RootCmd = issues.RootCmd
func main() {
if err := cli.New(issues.RootCmd).Run(context.Background(), models.BaseConfig); err != nil {
ctx := context.Background()
app, cleanup, err := initializeServices(ctx)
if err != nil {
return
}
defer cleanup()
App = app
survey.SetApp(App)
issues.SetApp(App)
if err := fang.Execute(ctx, RootCmd,
fang.WithColorSchemeFunc(fang.AnsiColorScheme)); err != nil {
return
}
}
func init() {
issues.RootCmd.AddCommand(issues.GetCmd)
issues.RootCmd.AddCommand(issues.ListCmd)
issues.RootCmd.AddCommand(issues.CloseCmd)
issues.RootCmd.AddCommand(issues.CreateCmd)
issues.RootCmd.AddCommand(issues.DeleteCmd)
issues.RootCmd.AddCommand(issues.UpdateCmd)
issues.RootCmd.AddCommand(issues.CommentCmd)
issues.RootCmd.AddCommand(issues.CommentsCmd)
issues.RootCmd.AddCommand(survey.StatusCmd)
}

View File

@@ -6,7 +6,7 @@ import (
"fmt"
"math/rand"
"github.com/LazyBachelor/LazyPM/cmd/survey/tasks"
"github.com/LazyBachelor/LazyPM/cmd/pm/tasks"
survey "github.com/LazyBachelor/LazyPM/internal/commands/survey"
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/LazyBachelor/LazyPM/pkg/task"

View File

@@ -1,40 +0,0 @@
package main
import (
"context"
"github.com/LazyBachelor/LazyPM/cmd/survey/tasks"
"github.com/LazyBachelor/LazyPM/internal/app"
"github.com/LazyBachelor/LazyPM/pkg/task"
_ "github.com/LazyBachelor/LazyPM/cmd/survey/tasks"
)
func initializeServices(ctx context.Context) (*app.App, func(), error) {
return app.New(ctx, tasks.BaseConfig().WithAutoInit(true))
}
func initInterfaces() map[string]task.Interface {
interfaces := make(map[string]task.Interface)
for _, name := range task.ListInterfaces() {
i, err := task.GetInterface(name)
if err != nil {
continue
}
interfaces[name] = i
}
return interfaces
}
func initTasks(app *app.App) map[string]task.Tasker {
taskMap := make(map[string]task.Tasker)
for _, name := range task.ListTasks() {
t, err := task.GetTask(name, app)
if err != nil {
continue
}
taskMap[name] = t
}
return taskMap
}

View File

@@ -1,104 +0,0 @@
package main
import (
"context"
"github.com/LazyBachelor/LazyPM/cmd/survey/tasks"
"github.com/LazyBachelor/LazyPM/internal/app"
issues "github.com/LazyBachelor/LazyPM/internal/commands/issues"
survey "github.com/LazyBachelor/LazyPM/internal/commands/survey"
"github.com/LazyBachelor/LazyPM/pkg/repl"
"github.com/LazyBachelor/LazyPM/pkg/task"
"github.com/LazyBachelor/LazyPM/pkg/tui"
"github.com/LazyBachelor/LazyPM/pkg/web"
"github.com/charmbracelet/fang"
)
func main() {
ctx := context.Background()
app, cleanup, err := initializeServices(ctx)
if err != nil {
return
}
defer cleanup()
survey.SetApp(app)
issues.SetApp(app)
if err := fang.Execute(ctx, survey.RootCmd,
fang.WithColorSchemeFunc(fang.AnsiColorScheme)); err != nil {
return
}
}
func init() {
task.RegisterInterface("tui", tui.New())
task.RegisterInterface("web", web.New())
task.RegisterInterface("repl", repl.New())
task.RegisterTask("create_issue", func(app *app.App) task.Tasker {
return tasks.NewCreateIssueTask(app)
})
task.RegisterTask("coding_task", func(app *app.App) task.Tasker {
return tasks.NewCodingTask(app)
})
task.RegisterTask("git_task", func(app *app.App) task.Tasker {
return tasks.NewGitTask(app)
})
task.RegisterTask("sprint_planning", func(app *app.App) task.Tasker {
return tasks.NewSprintPlanningTask(app)
})
task.RegisterTask("issue_triage", func(app *app.App) task.Tasker {
return tasks.NewIssueTriageTask(app)
})
task.RegisterTask("milestone_tracking", func(app *app.App) task.Tasker {
return tasks.NewMilestoneTrackingTask(app)
})
task.RegisterTask("dependency_management", func(app *app.App) task.Tasker {
return tasks.NewDependencyManagementTask(app)
})
task.RegisterTask("team_capacity", func(app *app.App) task.Tasker {
return tasks.NewTeamCapacityTask(app)
})
task.RegisterTask("report_generation", func(app *app.App) task.Tasker {
return tasks.NewReportGenerationTask(app)
})
task.RegisterTask("stakeholder_update", func(app *app.App) task.Tasker {
return tasks.NewStakeholderUpdateTask(app)
})
task.RegisterTask("priority_management", func(app *app.App) task.Tasker {
return tasks.NewPriorityManagementTask(app)
})
task.RegisterTask("backlog_refinement", func(app *app.App) task.Tasker {
return tasks.NewBacklogRefinementTask(app)
})
// Basic survey commands
survey.StartCmd.RunE = runStartCmd
survey.RootCmd.AddCommand(survey.StartCmd)
survey.RootCmd.AddCommand(survey.SubmitCmd)
survey.RootCmd.AddCommand(survey.StatusCmd)
survey.RootCmd.AddCommand(survey.ListTasksCmd)
survey.RootCmd.AddCommand(survey.ListInterfacesCmd)
survey.RootCmd.AddCommand(survey.IssuesCmd)
// Issue related commands
survey.IssuesCmd.AddCommand(issues.ListCmd)
survey.IssuesCmd.AddCommand(issues.CreateCmd)
survey.IssuesCmd.AddCommand(issues.UpdateCmd)
survey.IssuesCmd.AddCommand(issues.DeleteCmd)
survey.IssuesCmd.AddCommand(issues.CloseCmd)
survey.IssuesCmd.AddCommand(issues.CommentCmd)
survey.IssuesCmd.AddCommand(issues.CommentsCmd)
// Issue commands for the REPL interface
issues.RootCmd.AddCommand(issues.GetCmd)
issues.RootCmd.AddCommand(issues.ListCmd)
issues.RootCmd.AddCommand(issues.CloseCmd)
issues.RootCmd.AddCommand(issues.CreateCmd)
issues.RootCmd.AddCommand(issues.DeleteCmd)
issues.RootCmd.AddCommand(issues.UpdateCmd)
issues.RootCmd.AddCommand(issues.CommentCmd)
issues.RootCmd.AddCommand(issues.CommentsCmd)
issues.RootCmd.AddCommand(survey.StatusCmd)
}

View File

@@ -15,6 +15,7 @@ var CloseCmd = &cobra.Command{
Long: `Close an existing issue by its ID.`,
Example: `pm close pm-abc`,
Args: cobra.ExactArgs(1),
RunE: runCloseCmd,

View File

@@ -20,7 +20,7 @@ pm comment ISSUE-1 LGTM --author alice
pm comment ISSUE-1 -m "Needs review"`
var CommentCmd = &cobra.Command{
Use: "comment [issue ID] [message...]",
Use: "comment [id] [message...]",
Short: "Add a comment on an issue",
Long: `Add a comment on an issue by ID. All arguments after the issue ID form the message (no quotes needed), or use --message.`,
Example: commentCmdExample,

View File

@@ -8,7 +8,7 @@ import (
// CommentsCmd represents the command to list comments on an issue.
var CommentsCmd = &cobra.Command{
Use: "comments [issue ID]",
Use: "comments [id]",
Short: "List comments on an issue",
Long: `List all comments on an issue by ID.`,

View File

@@ -21,7 +21,7 @@ pm create Fix bug --desc "Bug description" --status in_progress --type bug --pri
// CreateCmd represents the create command, which allows users to create a new issue with specified details.
var CreateCmd = &cobra.Command{
Use: "create [title]",
Use: "create [title...]",
Short: "Create a new issue",
Long: `Create a new issue with the specified details.`,
Example: createCmdExample,

View File

@@ -20,7 +20,7 @@ pm list -p 1 -l 10`
// ListCmd represents the get issues command.
var ListCmd = &cobra.Command{
Use: "list [search query]",
Use: "list [query]",
Short: "List all issues",
Long: `List all issues in the project management system.`,
Example: lsExamples,

View File

@@ -7,13 +7,13 @@ import (
// GetCmd represents the get issue command.
var GetCmd = &cobra.Command{
Use: "describe [issue ID]",
Use: "read [id]",
Short: "Get issue details",
Long: `Get issue details by ID`,
ValidArgsFunction: completeIssues,
Aliases: []string{"get", "read"},
Aliases: []string{"get", "describe", "details"},
Args: cobra.ExactArgs(1),
RunE: runGetCmd,
}

View File

@@ -85,11 +85,3 @@ func ExecuteArgsStringWithContext(ctx context.Context, args []string) (string, e
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")
}

View File

@@ -11,7 +11,7 @@ import (
var updateFlags Flags
var UpdateCmd = &cobra.Command{
Use: "update [issue ID]",
Use: "update [id]",
Short: "Update an existing issue",
Long: `Update an existing issue by its ID with the specified details.`,
Example: `pm update pm-001 --title "New title" -d "Description" -s in_progress --type task -p 3`,

View File

@@ -1,62 +0,0 @@
// Package cli provides the command-line interface for the PM System.
package cli
import (
"context"
"github.com/LazyBachelor/LazyPM/internal/app"
issues "github.com/LazyBachelor/LazyPM/internal/commands/issues"
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/charmbracelet/fang"
"github.com/spf13/cobra"
)
// Config is an alias for app.Config, used to configure the CLI.
type Config = models.Config
type CLI struct {
RootCmd *cobra.Command
}
func New(rootCmd *cobra.Command) *CLI {
return &CLI{
RootCmd: rootCmd,
}
}
// Run initializes the services and executes the CLI commands.
func (c *CLI) Run(ctx context.Context, config Config) error {
app, cleanup, err := app.New(ctx, config)
if err != nil {
return err
}
defer cleanup()
issues.SetApp(app)
if err := fang.Execute(ctx, c.RootCmd,
fang.WithColorSchemeFunc(fang.AnsiColorScheme)); err != nil {
return err
}
return nil
}
// RunWithArgs initializes the services and executes the CLI commands with the provided arguments.
func (c *CLI) RunWithArgs(ctx context.Context, config Config, args []string) error {
app, cleanup, err := app.New(ctx, config)
if err != nil {
return err
}
defer cleanup()
issues.SetApp(app)
if err := issues.ExecuteArgs(args); err != nil {
return err
}
return nil
}

View File

@@ -11,7 +11,6 @@ import (
issues "github.com/LazyBachelor/LazyPM/internal/commands/issues"
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/LazyBachelor/LazyPM/internal/style"
"github.com/LazyBachelor/LazyPM/pkg/cli"
"github.com/LazyBachelor/LazyPM/pkg/task"
"github.com/LazyBachelor/LazyPM/pkg/tui/styles"
"github.com/c-bata/go-prompt"
@@ -43,7 +42,7 @@ func New() *REPL {
}
// Run starts the interactive Read-Eval-Print Loop for the PM CLI.
func (r *REPL) Run(ctx context.Context, config cli.Config) error {
func (r *REPL) Run(ctx context.Context, config app.Config) error {
// Set terminal to raw mode to capture input properly in the REPL.
// This allows us to handle input character by character and provide a better user experience.
// We also ensure that the terminal state is restored when the REPL exits, even if an error occurs.