155 lines
4.7 KiB
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
|
|
}
|