diff --git a/dist/plan b/dist/plan index 84756b0..9411c6c 100755 Binary files a/dist/plan and b/dist/plan differ diff --git a/plan/command/command.go b/plan/command/command.go index 97fe5c4..0400225 100644 --- a/plan/command/command.go +++ b/plan/command/command.go @@ -48,7 +48,7 @@ func NewCLI(deps Dependencies) *CLI { return &CLI{ deps: deps, cmdArgs: []CommandArgs{ - NewShowArgs(), + NewShowArgs(), NewProjectsArgs(), NewAddArgs(), NewDeleteArgs(), NewListArgs(), NewSyncArgs(), NewUpdateArgs(), }, diff --git a/plan/command/projects.go b/plan/command/projects.go new file mode 100644 index 0000000..a472601 --- /dev/null +++ b/plan/command/projects.go @@ -0,0 +1,53 @@ +package command + +import ( + "fmt" + "sort" + + "go-mod.ewintr.nl/planner/plan/format" +) + +type ProjectsArgs struct{} + +func NewProjectsArgs() ProjectsArgs { + return ProjectsArgs{} +} + +func (pa ProjectsArgs) Parse(main []string, fields map[string]string) (Command, error) { + if len(main) != 1 || main[0] != "projects" { + return nil, ErrWrongCommand + } + + return Projects{}, nil +} + +type Projects struct{} + +func (ps Projects) Do(deps Dependencies) (CommandResult, error) { + projects, err := deps.TaskRepo.Projects() + if err != nil { + return nil, fmt.Errorf("could not find projects: %v", err) + } + + return ProjectsResult{ + Projects: projects, + }, nil +} + +type ProjectsResult struct { + Projects map[string]int +} + +func (psr ProjectsResult) Render() string { + projects := make([]string, 0, len(psr.Projects)) + for pr := range psr.Projects { + projects = append(projects, pr) + } + sort.Strings(projects) + data := [][]string{{"projects", "count"}} + for _, p := range projects { + data = append(data, []string{p, fmt.Sprintf("%d", psr.Projects[p])}) + } + + return fmt.Sprintf("\n%s\n", format.Table(data)) +} diff --git a/plan/storage/memory/task.go b/plan/storage/memory/task.go index 10290a1..c91ef8d 100644 --- a/plan/storage/memory/task.go +++ b/plan/storage/memory/task.go @@ -67,3 +67,15 @@ func (t *Task) Delete(id string) error { return nil } + +func (t *Task) Projects() (map[string]int, error) { + projects := make(map[string]int) + for _, tsk := range t.tasks { + if _, ok := projects[tsk.Project]; !ok { + projects[tsk.Project] = 0 + } + projects[tsk.Project]++ + } + + return projects, nil +} diff --git a/plan/storage/sqlite/task.go b/plan/storage/sqlite/task.go index 5df8aea..4fe7dac 100644 --- a/plan/storage/sqlite/task.go +++ b/plan/storage/sqlite/task.go @@ -125,8 +125,8 @@ func (t *SqliteTask) FindMany(params storage.TaskListParams) ([]item.Task, error return tasks, nil } -func (s *SqliteTask) Delete(id string) error { - result, err := s.db.Exec(` +func (t *SqliteTask) Delete(id string) error { + result, err := t.db.Exec(` DELETE FROM tasks WHERE id = ?`, id) if err != nil { @@ -144,3 +144,23 @@ WHERE id = ?`, id) return nil } + +func (t *SqliteTask) Projects() (map[string]int, error) { + rows, err := t.db.Query(`SELECT project, count(*) FROM tasks GROUP BY project`) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err) + } + + result := make(map[string]int) + defer rows.Close() + for rows.Next() { + var pr string + var count int + if err := rows.Scan(&pr, &count); err != nil { + return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err) + } + result[pr] = count + } + + return result, nil +} diff --git a/plan/storage/storage.go b/plan/storage/storage.go index ff8201a..914e98c 100644 --- a/plan/storage/storage.go +++ b/plan/storage/storage.go @@ -41,6 +41,7 @@ type Task interface { FindOne(id string) (item.Task, error) FindMany(params TaskListParams) ([]item.Task, error) Delete(id string) error + Projects() (map[string]int, error) } func Match(tsk item.Task, params TaskListParams) bool {