mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
218 lines
5.7 KiB
Go
218 lines
5.7 KiB
Go
package task
|
|
|
|
import (
|
|
"log/slog"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Priority scale: lower = higher priority
|
|
// 1 = highest, 5 = lowest
|
|
const (
|
|
PriorityHigh = 1
|
|
PriorityMediumHigh = 2
|
|
PriorityMedium = 3
|
|
PriorityMediumLow = 4
|
|
PriorityLow = 5
|
|
)
|
|
|
|
type priorityInfo struct {
|
|
label string
|
|
value int
|
|
}
|
|
|
|
var priorities = map[string]priorityInfo{
|
|
"high": {label: "High", value: PriorityHigh},
|
|
"medium-high": {label: "Medium High", value: PriorityMediumHigh},
|
|
"high-medium": {label: "Medium High", value: PriorityMediumHigh},
|
|
"medium": {label: "Medium", value: PriorityMedium},
|
|
"medium-low": {label: "Medium Low", value: PriorityMediumLow},
|
|
"low-medium": {label: "Medium Low", value: PriorityMediumLow},
|
|
"low": {label: "Low", value: PriorityLow},
|
|
}
|
|
|
|
// PriorityValue unmarshals priority from int or string (e.g., "high", "medium-high").
|
|
// high=1, low=5. Values outside 1-5 range are clamped to boundaries.
|
|
type PriorityValue int
|
|
|
|
func (p *PriorityValue) UnmarshalYAML(value *yaml.Node) error {
|
|
// first try integer
|
|
var intVal int
|
|
if err := value.Decode(&intVal); err == nil {
|
|
// Clamp to valid range 1-5
|
|
*p = PriorityValue(clampPriority(intVal))
|
|
return nil
|
|
}
|
|
|
|
// try string
|
|
var strVal string
|
|
if err := value.Decode(&strVal); err != nil {
|
|
// If it's not an int or string (e.g., boolean, object), default to medium
|
|
slog.Warn("invalid priority field, defaulting to medium",
|
|
"received_type", value.Kind,
|
|
"line", value.Line,
|
|
"column", value.Column)
|
|
*p = PriorityValue(PriorityMedium)
|
|
return nil
|
|
}
|
|
|
|
normalized := normalizePriority(strVal)
|
|
if normalized == "" {
|
|
// Empty or whitespace-only string, default to medium
|
|
slog.Warn("invalid priority field, defaulting to medium",
|
|
"received_value", strVal,
|
|
"line", value.Line,
|
|
"column", value.Column)
|
|
*p = PriorityValue(PriorityMedium)
|
|
return nil
|
|
}
|
|
|
|
mapped, ok := priorityWordToInt(normalized)
|
|
if !ok {
|
|
// attempt parsing numeric string
|
|
if num, err := strconv.Atoi(strVal); err == nil {
|
|
// Clamp to valid range 1-5
|
|
*p = PriorityValue(clampPriority(num))
|
|
return nil
|
|
}
|
|
// Unrecognized priority word or invalid numeric string, default to medium
|
|
slog.Warn("invalid priority field, defaulting to medium",
|
|
"received_value", strVal,
|
|
"line", value.Line,
|
|
"column", value.Column)
|
|
*p = PriorityValue(PriorityMedium)
|
|
return nil
|
|
}
|
|
|
|
*p = PriorityValue(mapped)
|
|
return nil
|
|
}
|
|
|
|
// clampPriority ensures priority is within valid range 1-5
|
|
func clampPriority(val int) int {
|
|
if val < PriorityHigh {
|
|
return PriorityHigh
|
|
}
|
|
if val > PriorityLow {
|
|
return PriorityLow
|
|
}
|
|
return val
|
|
}
|
|
|
|
func (p PriorityValue) MarshalYAML() (interface{}, error) {
|
|
return int(p), nil
|
|
}
|
|
|
|
// normalizePriority standardizes a raw priority string.
|
|
func normalizePriority(s string) string {
|
|
s = strings.ToLower(strings.TrimSpace(s))
|
|
s = strings.ReplaceAll(s, "_", "-")
|
|
s = strings.ReplaceAll(s, " ", "-")
|
|
return s
|
|
}
|
|
|
|
// priorityWordToInt converts priority words to numeric values.
|
|
func priorityWordToInt(s string) (int, bool) {
|
|
if info, ok := priorities[s]; ok {
|
|
return info.value, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// canonicalPriorities defines the display labels for each priority level
|
|
// in a deterministic order (highest to lowest).
|
|
var canonicalPriorities = []struct {
|
|
value int
|
|
label string
|
|
}{
|
|
{PriorityHigh, "High"},
|
|
{PriorityMediumHigh, "Medium High"},
|
|
{PriorityMedium, "Medium"},
|
|
{PriorityMediumLow, "Medium Low"},
|
|
{PriorityLow, "Low"},
|
|
}
|
|
|
|
// priorityDisplayValues maps int priority values to their display labels.
|
|
var priorityDisplayValues = buildPriorityDisplayValues()
|
|
|
|
func buildPriorityDisplayValues() map[int]string {
|
|
result := make(map[int]string, len(canonicalPriorities))
|
|
for _, entry := range canonicalPriorities {
|
|
result[entry.value] = entry.label
|
|
}
|
|
return result
|
|
}
|
|
|
|
// PriorityDisplay returns the human-readable label for a priority integer.
|
|
// e.g., 1 → "High", 3 → "Medium", 5 → "Low"
|
|
func PriorityDisplay(priority int) string {
|
|
if label, ok := priorityDisplayValues[priority]; ok {
|
|
return label
|
|
}
|
|
return priorityDisplayValues[clampPriority(priority)]
|
|
}
|
|
|
|
// PriorityFromDisplay converts a display label back to a priority integer.
|
|
// e.g., "High" → 1, "Medium Low" → 4. Returns PriorityMedium if not found.
|
|
func PriorityFromDisplay(display string) int {
|
|
normalized := normalizePriority(display)
|
|
if val, ok := priorityWordToInt(normalized); ok {
|
|
return val
|
|
}
|
|
return PriorityMedium
|
|
}
|
|
|
|
// AllPriorityDisplayValues returns priority display labels ordered high→low (1→5).
|
|
func AllPriorityDisplayValues() []string {
|
|
return []string{
|
|
priorityDisplayValues[PriorityHigh],
|
|
priorityDisplayValues[PriorityMediumHigh],
|
|
priorityDisplayValues[PriorityMedium],
|
|
priorityDisplayValues[PriorityMediumLow],
|
|
priorityDisplayValues[PriorityLow],
|
|
}
|
|
}
|
|
|
|
// PriorityLabel returns an emoji-based label for a priority value.
|
|
func PriorityLabel(priority int) string {
|
|
switch priority {
|
|
case PriorityHigh: // 1
|
|
return "🔴"
|
|
case PriorityMediumHigh: // 2
|
|
return "🟠"
|
|
case PriorityMedium: // 3
|
|
return "🟡"
|
|
case PriorityMediumLow: // 4
|
|
return "🔵"
|
|
case PriorityLow: // 5
|
|
return "🟢"
|
|
default:
|
|
// For out-of-range values, map to boundaries
|
|
if priority < PriorityHigh {
|
|
return "🔴"
|
|
}
|
|
return "🟢"
|
|
}
|
|
}
|
|
|
|
// NormalizePriority standardizes a raw priority string or number into an integer.
|
|
// Returns the normalized priority value (clamped to 1-5) or -1 if invalid.
|
|
func NormalizePriority(priority string) int {
|
|
normalized := normalizePriority(priority)
|
|
if normalized == "" {
|
|
return -1
|
|
}
|
|
|
|
if mapped, ok := priorityWordToInt(normalized); ok {
|
|
return mapped
|
|
}
|
|
|
|
// Try parsing as number
|
|
if num, err := strconv.Atoi(priority); err == nil {
|
|
return clampPriority(num)
|
|
}
|
|
|
|
return -1
|
|
}
|