Files
ax/src/output/output.go

210 lines
5.6 KiB
Go
Raw Normal View History

2026-03-26 12:48:47 +00:00
package output
import (
"axolotl/models"
2026-03-29 18:58:34 +02:00
"axolotl/service"
2026-03-26 12:48:47 +00:00
"encoding/json"
"fmt"
"io"
"sort"
"strings"
"github.com/fatih/color"
)
type RenderMap map[string]struct {
s string
l string
c *color.Color
2026-03-26 12:48:47 +00:00
}
var (
cPrimary = color.New(color.FgCyan)
cSecond = color.New(color.FgMagenta)
cDim = color.New(color.FgHiBlack)
cText = color.New(color.FgWhite)
cTitle = color.New(color.FgWhite, color.Bold)
cGood = color.New(color.FgGreen)
cWarn = color.New(color.FgYellow)
cBad = color.New(color.FgRed)
typeRM = RenderMap{
"issue": {" ", "\uf188 issue", cSecond},
"note": {"\uf15c", "\uf15c note", cPrimary},
"user": {"\uf007", "\uf007 user", cGood},
"namespace": {"\uf07b", "\uf07b namespace", cWarn},
"": {" ", "n/a", cDim},
}
statusRM = RenderMap{
"open": {"●", "● open", cPrimary},
"done": {"○", "○ done", cDim},
"": {"—", "n/a", cDim},
}
prioRM = RenderMap{
"high": {"\uf0e7", "high", cBad},
"medium": {"\uf0e7", "medium", cWarn},
"low": {" ", "low", cDim},
"": {" ", "n/a", cDim},
}
relIcons = map[string]string{"blocks": "\uf068", "subtask": "\uf0da", "related": "\uf0c1", "assignee": "\uf007", "in_namespace": "\uf07b"}
prioRanks = map[string]int{"high": 3, "medium": 2, "low": 1}
statusRanks = map[string]int{"open": 2, "": 1, "done": 0}
)
2026-03-26 12:48:47 +00:00
const (
iconCalendar = "\uf133"
iconCheck = "\uf00c"
iconCross = "\uf00d"
iconNamespace = "\uf07b"
)
2026-03-26 12:48:47 +00:00
func PrintNodes(w io.Writer, svc service.NodeService, nodes []*models.Node, jsonOut bool) error {
2026-03-26 12:48:47 +00:00
if jsonOut {
return json.NewEncoder(w).Encode(nodes)
}
if len(nodes) == 0 {
fmt.Fprintln(w, cDim.Sprint("No results."))
2026-03-26 12:48:47 +00:00
return nil
}
fmt.Fprintln(w)
2026-03-26 12:48:47 +00:00
sort.Slice(nodes, func(i, j int) bool {
si, sj := nodes[i].GetProperty("status"), nodes[j].GetProperty("status")
if si != sj {
return statusRanks[si] > statusRanks[sj]
2026-03-26 12:48:47 +00:00
}
return prioRanks[nodes[i].GetProperty("prio")] > prioRanks[nodes[j].GetProperty("prio")]
2026-03-26 12:48:47 +00:00
})
for _, n := range nodes {
n_rels := n.Relations
ns_rel_node_ids := n_rels[string(models.RelInNamespace)]
ns_rel_node_titles := make([]string, 0, len(ns_rel_node_ids))
for _, id := range ns_rel_node_ids {
ns_rel_node, err := svc.GetByID(id)
if err != nil {
ns_rel_node_titles = append(ns_rel_node_titles, id)
continue
}
ns_rel_node_titles = append(ns_rel_node_titles, ns_rel_node.Title)
}
fmt.Fprintf(w, " %s %s %s %s %s %s",
cDim.Sprint(n.ID),
render(prioRM, n.GetProperty("prio"), true),
render(statusRM, n.GetProperty("status"), true),
render(typeRM, n.GetProperty("type"), true),
cTitle.Sprint(truncate(n.Title, 80)),
cDim.Sprint("["+strings.Join(ns_rel_node_titles, ",")+"]"),
2026-03-26 12:48:47 +00:00
)
tags := n.GetDisplayTags()
2026-03-26 12:48:47 +00:00
if len(tags) > 0 {
fmt.Fprintf(w, " %s", cPrimary.Sprint("#"+strings.Join(tags, " #")))
2026-03-26 12:48:47 +00:00
}
fmt.Fprintln(w)
}
fmt.Fprintln(w)
return nil
}
func PrintNode(w io.Writer, svc service.NodeService, n *models.Node, jsonOut bool) error {
2026-03-26 12:48:47 +00:00
if jsonOut {
return json.NewEncoder(w).Encode(n)
}
fmt.Fprintln(w)
fmt.Fprintf(w, " %s %s %s\n", render(typeRM, n.GetProperty("type"), false), cDim.Sprint(n.ID), cTitle.Sprint(n.Title))
fmt.Fprintln(w, cDim.Sprint(" ───────────────────────────────"))
fmt.Fprintf(w, " Status: %s\n", render(statusRM, n.GetProperty("status"), false))
fmt.Fprintf(w, " Priority: %s\n", render(prioRM, n.GetProperty("prio"), false))
2026-03-26 12:48:47 +00:00
if n.DueDate != "" {
fmt.Fprintf(w, " Due: %s %s\n", iconCalendar, n.DueDate)
2026-03-26 12:48:47 +00:00
}
fmt.Fprintf(w, " Created: %s\n", cDim.Sprint(n.CreatedAt))
fmt.Fprintf(w, " Updated: %s\n", cDim.Sprint(n.UpdatedAt))
2026-03-26 12:48:47 +00:00
if tags := n.GetDisplayTags(); len(tags) > 0 {
fmt.Fprintf(w, "\n tags: %s\n", cPrimary.Sprint(strings.Join(tags, " • ")))
}
n_rels := n.Relations
for relType := range n_rels {
rel_node_ids := n_rels[string(relType)]
if len(rel_node_ids) > 0 {
fmt.Fprintf(w, "\n %s\n", string(relType))
}
for _, id := range rel_node_ids {
rel_node, err := svc.GetByID(id)
if err != nil {
fmt.Fprintf(w, " %s %s\n", relIcons[relType], cDim.Sprint(id))
continue
}
fmt.Fprintf(w, " %s %s\n", relIcons[relType], rel_node.Title)
}
}
2026-03-26 12:48:47 +00:00
if n.Content != "" {
fmt.Fprintln(w, "\n"+cPrimary.Sprint(" Content:"))
for i, line := range strings.Split(n.Content, "\n") {
if i > 5 {
fmt.Fprintf(w, "%s ...\n", cDim.Sprint(" │ "))
break
}
fmt.Fprintf(w, "%s%s\n", cDim.Sprint(" │ "), cText.Sprint(line))
2026-03-26 12:48:47 +00:00
}
}
fmt.Fprintln(w)
return nil
}
2026-03-29 18:58:34 +02:00
func PrintAliases(w io.Writer, aliases []*service.Alias, jsonOut bool) error {
2026-03-26 12:48:47 +00:00
if jsonOut {
return json.NewEncoder(w).Encode(aliases)
}
if len(aliases) == 0 {
fmt.Fprintln(w, cDim.Sprint("No aliases defined."))
2026-03-26 12:48:47 +00:00
return nil
}
fmt.Fprintln(w)
for _, a := range aliases {
fmt.Fprintf(w, " %s %s\n", cPrimary.Sprint(a.Name), cDim.Sprint(a.Command))
2026-03-29 18:58:34 +02:00
if a.Description != "" {
fmt.Fprintf(w, " %s\n", cDim.Sprint(a.Description))
}
2026-03-26 12:48:47 +00:00
}
fmt.Fprintln(w)
return nil
}
func PrintAction(w io.Writer, action, detail string, isError bool) {
if isError {
fmt.Fprintln(w, cBad.Sprint(iconCross+" "+action+" ")+cDim.Sprint(detail))
return
}
icon := iconCheck
if action == "Created" {
icon = iconNamespace
}
fmt.Fprintln(w, cGood.Sprint(icon+" "+action+" ")+cDim.Sprint(detail))
2026-03-26 12:48:47 +00:00
}
func render(rm RenderMap, key string, short bool) string {
v, ok := rm[key]
if !ok {
v, ok = rm[""]
if !ok {
return ""
}
}
if short {
return v.c.Sprint(v.s)
}
return v.c.Sprint(v.l)
}
2026-03-26 12:48:47 +00:00
func truncate(s string, max int) string {
if len(s) <= max {
return s
}
return s[:max-1] + "…"
}