vikunja/veans/internal/status/status.go

155 lines
4.7 KiB
Go

// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package status maps the five canonical veans statuses to Vikunja bucket
// IDs and the `done` flag. The mapping is canonical and reflected verbatim
// in the agent prompt (see internal/commands/prompt.tmpl).
package status
import (
"fmt"
"strings"
"code.vikunja.io/veans/internal/config"
"code.vikunja.io/veans/internal/output"
)
// Status is the agent-facing state name.
type Status string
const (
Todo Status = "todo"
InProgress Status = "in-progress"
InReview Status = "in-review"
Completed Status = "completed"
Scrapped Status = "scrapped"
)
// All returns the canonical statuses in display order.
func All() []Status {
return []Status{Todo, InProgress, InReview, Completed, Scrapped}
}
// BucketTitleAliases lists titles that count as the canonical bucket for
// each status. Vikunja's default Kanban view ships with "To-Do", "Doing"
// and "Done" buckets — we accept those so a vanilla project doesn't grow
// parallel buckets when veans init runs against it. The first entry is
// the canonical title returned by BucketTitle().
var BucketTitleAliases = map[Status][]string{
Todo: {"Todo", "To-Do", "ToDo", "To Do", "To do", "Backlog"},
InProgress: {"In Progress", "In-Progress", "Doing", "WIP", "In progress"},
InReview: {"In Review", "In-Review", "Review", "In review"},
Completed: {"Done", "Completed", "Complete"},
Scrapped: {"Scrapped", "Cancelled", "Canceled", "Won't Do", "Wontfix"},
}
// MatchBucketTitle reports whether `title` matches `s` either as the
// canonical title or one of its aliases. Comparison is case-insensitive
// and tolerant of stray whitespace.
func MatchBucketTitle(s Status, title string) bool {
want := normalizeBucketTitle(title)
for _, alias := range BucketTitleAliases[s] {
if normalizeBucketTitle(alias) == want {
return true
}
}
return false
}
func normalizeBucketTitle(s string) string {
return strings.ToLower(strings.TrimSpace(s))
}
// BucketTitle returns the bucket name that backs each status. The canonical
// title is the first entry in BucketTitleAliases — single source of truth.
func (s Status) BucketTitle() string {
if aliases, ok := BucketTitleAliases[s]; ok && len(aliases) > 0 {
return aliases[0]
}
return ""
}
// Done reports whether tasks in this status should have done=true.
func (s Status) Done() bool {
return s == Completed || s == Scrapped
}
// Parse normalizes user input. Accepts the canonical hyphenated form, plus
// underscored/snake variants and a couple of natural-language synonyms.
func Parse(raw string) (Status, error) {
n := strings.TrimSpace(strings.ToLower(raw))
n = strings.ReplaceAll(n, "_", "-")
n = strings.ReplaceAll(n, " ", "-")
switch n {
case "todo":
return Todo, nil
case "in-progress", "wip", "doing":
return InProgress, nil
case "in-review", "review":
return InReview, nil
case "completed", "done":
return Completed, nil
case "scrapped", "cancelled", "canceled":
return Scrapped, nil
}
return "", output.New(output.CodeValidation,
"unknown status %q (expected one of: %s)",
raw, strings.Join(allStrings(), ", "))
}
// BucketID resolves a status to the bucket ID stored in .veans.yml.
func BucketID(s Status, b config.Buckets) (int64, error) {
switch s {
case Todo:
return b.Todo, nil
case InProgress:
return b.InProgress, nil
case InReview:
return b.InReview, nil
case Completed:
return b.Done, nil
case Scrapped:
return b.Scrapped, nil
}
return 0, fmt.Errorf("unknown status %q", s)
}
// FromBucketID is the inverse of BucketID — used by `list` to render the
// status of a task fetched from the API.
func FromBucketID(id int64, b config.Buckets) Status {
switch id {
case b.Todo:
return Todo
case b.InProgress:
return InProgress
case b.InReview:
return InReview
case b.Done:
return Completed
case b.Scrapped:
return Scrapped
}
return ""
}
func allStrings() []string {
out := make([]string, 0, 5)
for _, s := range All() {
out = append(out, string(s))
}
return out
}