refactor: consolidate packages - move output to cmd, config/session to store, rename Store to GraphStore
This commit is contained in:
+1
-2
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"axolotl/models"
|
||||
"axolotl/output"
|
||||
"axolotl/service"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -65,7 +64,7 @@ var addCmd = &cobra.Command{
|
||||
return
|
||||
}
|
||||
|
||||
output.PrintNode(cmd.OutOrStdout(), svc, n, jsonFlag)
|
||||
PrintNode(cmd.OutOrStdout(), svc, n, jsonFlag)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -1,8 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"axolotl/output"
|
||||
"axolotl/service"
|
||||
"axolotl/store"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
@@ -17,7 +16,7 @@ var aliasCmd = &cobra.Command{
|
||||
w := cmd.OutOrStdout()
|
||||
if len(args) == 0 {
|
||||
if aliases, err := cfg.ListAliases(); err == nil {
|
||||
output.PrintAliases(w, aliases, jsonFlag)
|
||||
PrintAliases(w, aliases, jsonFlag)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -30,10 +29,10 @@ var aliasCmd = &cobra.Command{
|
||||
fmt.Println(a.Command)
|
||||
return
|
||||
}
|
||||
if err := cfg.SetAlias(&service.Alias{Name: args[0], Command: args[1], Description: aliasDesc}); err != nil {
|
||||
if err := cfg.SetAlias(&store.Alias{Name: args[0], Command: args[1], Description: aliasDesc}); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to set alias:", err)
|
||||
} else {
|
||||
output.PrintAction(w, "Alias set", args[0], false)
|
||||
PrintAction(w, "Alias set", args[0], false)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -45,7 +44,7 @@ var aliasDelCmd = &cobra.Command{
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
output.PrintAction(cmd.OutOrStdout(), "Alias deleted", args[0], false)
|
||||
PrintAction(cmd.OutOrStdout(), "Alias deleted", args[0], false)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"axolotl/output"
|
||||
"axolotl/service"
|
||||
"bufio"
|
||||
"fmt"
|
||||
@@ -39,7 +38,7 @@ var delCmd = &cobra.Command{
|
||||
if err := svc.Delete(args[0]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to delete: %v", err)
|
||||
} else {
|
||||
output.PrintAction(cmd.OutOrStdout(), "Deleted", args[0], true)
|
||||
PrintAction(cmd.OutOrStdout(), "Deleted", args[0], true)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
+2
-7
@@ -1,7 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"axolotl/output"
|
||||
"axolotl/service"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -34,11 +33,7 @@ var editCmd = &cobra.Command{
|
||||
tmp.Close()
|
||||
defer os.Remove(tmp.Name())
|
||||
|
||||
editor := os.Getenv("EDITOR")
|
||||
if editor == "" {
|
||||
editor = "vi"
|
||||
}
|
||||
c := exec.Command(editor, tmp.Name())
|
||||
c := exec.Command(cfg.GetEditor(), tmp.Name())
|
||||
c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
if err := c.Run(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "editor failed:", err)
|
||||
@@ -56,7 +51,7 @@ var editCmd = &cobra.Command{
|
||||
fmt.Fprintln(os.Stderr, "failed to update:", err)
|
||||
return
|
||||
}
|
||||
output.PrintNode(cmd.OutOrStdout(), svc, n, jsonFlag)
|
||||
PrintNode(cmd.OutOrStdout(), svc, n, jsonFlag)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -1,8 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"axolotl/output"
|
||||
"axolotl/service"
|
||||
"axolotl/store"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -13,11 +13,14 @@ import (
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init [path]", Short: "Initialize a new database", Args: cobra.MaximumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
p := "."
|
||||
dataRoot, err := store.FindDataRoot(".local", "share")
|
||||
if len(args) > 0 {
|
||||
p = args[0]
|
||||
dataRoot = args[0]
|
||||
} else if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to find data dir:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
dbPath := filepath.Join(p, ".ax.db")
|
||||
dbPath := filepath.Join(dataRoot, "ax.db")
|
||||
if _, err := os.Stat(dbPath); err == nil {
|
||||
fmt.Fprintln(os.Stderr, "database already exists:", dbPath)
|
||||
os.Exit(1)
|
||||
@@ -26,7 +29,7 @@ var initCmd = &cobra.Command{
|
||||
fmt.Fprintln(os.Stderr, "failed to initialize:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
output.PrintAction(cmd.OutOrStdout(), "Created", dbPath, false)
|
||||
PrintAction(cmd.OutOrStdout(), "Created", dbPath, false)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"axolotl/models"
|
||||
"axolotl/output"
|
||||
"axolotl/service"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -59,7 +58,7 @@ var listCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if nodes, err := svc.List(filter); err == nil {
|
||||
output.PrintNodes(cmd.OutOrStdout(), svc, nodes, jsonFlag)
|
||||
PrintNodes(cmd.OutOrStdout(), svc, nodes, jsonFlag)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "err: %v\n", err)
|
||||
}
|
||||
|
||||
+8
-2
@@ -1,7 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"axolotl/service"
|
||||
"axolotl/store"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -66,7 +66,13 @@ var loginCmd = &cobra.Command{
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
resp.Body.Close()
|
||||
|
||||
if err := service.SaveSession(&service.Session{Token: result.Token}); err != nil {
|
||||
session, err := store.LoadSession()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to save session: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
session.Token = result.Token
|
||||
if err := session.Save(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to save session: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"axolotl/models"
|
||||
"axolotl/service"
|
||||
"axolotl/store"
|
||||
"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", "in_namespace": "\uf07b"}
|
||||
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 PrintNodes(w io.Writer, svc service.NodeService, 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 {
|
||||
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, ",")+"]"),
|
||||
)
|
||||
tags := n.GetDisplayTags()
|
||||
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, svc service.NodeService, 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))
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(w)
|
||||
return nil
|
||||
}
|
||||
|
||||
func PrintAliases(w io.Writer, aliases []*store.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))
|
||||
if a.Description != "" {
|
||||
fmt.Fprintf(w, " %s\n", cDim.Sprint(a.Description))
|
||||
}
|
||||
}
|
||||
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 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 truncate(s string, max int) string {
|
||||
if len(s) <= max {
|
||||
return s
|
||||
}
|
||||
return s[:max-1] + "…"
|
||||
}
|
||||
+5
-5
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"axolotl/models"
|
||||
"axolotl/service"
|
||||
"axolotl/store"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -11,17 +12,17 @@ import (
|
||||
)
|
||||
|
||||
var jsonFlag bool
|
||||
var cfg service.Config
|
||||
var cfg *store.Config
|
||||
var rootCmd = &cobra.Command{Use: "ax", Short: "The axolotl issue tracker"}
|
||||
|
||||
func Execute() {
|
||||
var err error
|
||||
cfg, err = service.LoadConfigFile()
|
||||
cfg, err = store.LoadConfigFile()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to load config:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
registerAliasCommands()
|
||||
RegisterAliasCommands()
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -31,7 +32,7 @@ func init() {
|
||||
rootCmd.PersistentFlags().BoolVar(&jsonFlag, "json", false, "")
|
||||
}
|
||||
|
||||
func registerAliasCommands() {
|
||||
func RegisterAliasCommands() {
|
||||
rootCmd.AddGroup(&cobra.Group{ID: "aliases", Title: "Aliases:"})
|
||||
aliases, _ := cfg.ListAliases()
|
||||
for _, a := range aliases {
|
||||
@@ -87,7 +88,6 @@ func registerAliasCommands() {
|
||||
}
|
||||
|
||||
// parseRelInput parses a rel string into a RelInput.
|
||||
//
|
||||
// Formats:
|
||||
// - "prefix::value" → property rel with no target (tag)
|
||||
// - "relname:target" → edge rel with a target node
|
||||
|
||||
+3
-2
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"axolotl/serve"
|
||||
"axolotl/service"
|
||||
"axolotl/store"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -17,9 +18,9 @@ var serveCmd = &cobra.Command{
|
||||
sc := cfg.GetServerConfig()
|
||||
addr := fmt.Sprintf("%s:%d", sc.Host, sc.Port)
|
||||
|
||||
var oidcCfg *service.OIDCConfig
|
||||
var oidcCfg *store.OIDCConfig
|
||||
if oc, ok := cfg.GetOIDCConfig(); ok {
|
||||
oidcCfg = &oc
|
||||
oidcCfg = oc
|
||||
}
|
||||
|
||||
handler, err := serve.New(service.GetNodeServiceForUser, oidcCfg)
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"axolotl/output"
|
||||
"axolotl/service"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -23,7 +22,7 @@ var showCmd = &cobra.Command{
|
||||
fmt.Fprintln(os.Stderr, "node not found:", args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
output.PrintNode(cmd.OutOrStdout(), svc, n, jsonFlag)
|
||||
PrintNode(cmd.OutOrStdout(), svc, n, jsonFlag)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"axolotl/models"
|
||||
"axolotl/output"
|
||||
"axolotl/service"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -11,10 +10,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
uTitle, uContent, uDue string
|
||||
uClearDue bool
|
||||
uStatus, uPrio, uType string
|
||||
uNamespace, uAssignee string
|
||||
uTitle, uContent, uDue string
|
||||
uClearDue bool
|
||||
uStatus, uPrio, uType string
|
||||
uNamespace, uAssignee string
|
||||
uAddTags, uRmTags, uAddRels, uRmRels []string
|
||||
)
|
||||
|
||||
@@ -90,7 +89,7 @@ var updateCmd = &cobra.Command{
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
output.PrintNode(cmd.OutOrStdout(), svc, n, jsonFlag)
|
||||
PrintNode(cmd.OutOrStdout(), svc, n, jsonFlag)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user