package output import ( "axolotl/db" "axolotl/models" "encoding/json" "fmt" "io" "sort" "strings" "github.com/fatih/color" ) type RenderMap map[string]struct { s string l string c *color.Color } 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"} prioRanks = map[string]int{"high": 3, "medium": 2, "low": 1} statusRanks = map[string]int{"open": 2, "": 1, "done": 0} ) const ( iconCalendar = "\uf133" iconCheck = "\uf00c" iconCross = "\uf00d" iconNamespace = "\uf07b" ) 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) } func getDisplayTags(n *models.Node) []string { var tags []string for _, t := range n.Tags { if !strings.HasPrefix(t, "_") { tags = append(tags, t) } } return tags } func PrintNodes(w io.Writer, nodes []*models.Node, jsonOut bool) error { if jsonOut { return json.NewEncoder(w).Encode(nodes) } if len(nodes) == 0 { fmt.Fprintln(w, cDim.Sprint("No results.")) return nil } fmt.Fprintln(w) 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] } return prioRanks[nodes[i].GetProperty("prio")] > prioRanks[nodes[j].GetProperty("prio")] }) for _, n := range nodes { tags := getDisplayTags(n) 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, 35)), cDim.Sprint("["+n.GetProperty("namespace")+"]"), ) if len(tags) > 0 { fmt.Fprintf(w, " %s", cPrimary.Sprint("#"+strings.Join(tags, " #"))) } fmt.Fprintln(w) } fmt.Fprintln(w) return nil } func PrintNode(w io.Writer, n *models.Node, jsonOut bool) error { 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)) fmt.Fprintf(w, " Namespace: %s\n", cWarn.Sprint(n.GetProperty("namespace"))) if n.DueDate != "" { fmt.Fprintf(w, " Due: %s %s\n", iconCalendar, n.DueDate) } fmt.Fprintf(w, " Created: %s\n", cDim.Sprint(n.CreatedAt)) fmt.Fprintf(w, " Updated: %s\n", cDim.Sprint(n.UpdatedAt)) 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)) } } if tags := getDisplayTags(n); len(tags) > 0 { fmt.Fprintf(w, "\n tags: %s\n\n", cPrimary.Sprint(strings.Join(tags, " • "))) } if db, err := db.GetDB(); err == nil { if len(n.Relations) > 0 { for relType, ids := range n.Relations { fmt.Fprintf(w, " %s\n", string(relType)) if relIcon, ok := relIcons[string(relType)]; ok && relType != "created" { for _, id := range ids { node, err := db.NodeByID(id) if err == nil { fmt.Fprintf(w, " %s %s\n", relIcon, node.Title) } } } } } } else { fmt.Fprintf(w, "failed to attach to db: %v", err) } fmt.Fprintln(w) return nil } func PrintAliases(w io.Writer, aliases []*db.Alias, jsonOut bool) error { if jsonOut { return json.NewEncoder(w).Encode(aliases) } if len(aliases) == 0 { fmt.Fprintln(w, cDim.Sprint("No aliases defined.")) return nil } fmt.Fprintln(w) for _, a := range aliases { fmt.Fprintf(w, " %s %s\n", cPrimary.Sprint(a.Name), cDim.Sprint(a.Command)) } 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)) } func truncate(s string, max int) string { if len(s) <= max { return s } return s[:max-1] + "…" }