Fixes an issue where alias arguments containing spaces or flags were being split incorrectly by using strings.Fields on the substituted command string. Instead, the command string is tokenized first and then substitution happens on each token. Also disables Cobra flag parsing for aliases dynamically so that arguments and flags correctly cascade down to the target command instead of throwing unknown flag errors.
186 lines
3.8 KiB
Go
186 lines
3.8 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
type fileConfig struct {
|
|
path string
|
|
User string `json:"user"`
|
|
UserAliases []*Alias `json:"aliases"`
|
|
}
|
|
|
|
var defaultAliases = []*Alias{
|
|
{Name: "mine", Command: "list --assignee $me --tag _status::open", Description: "Show open tasks assigned to you"},
|
|
{Name: "due", Command: "list --tag _status::open --tag _due", Description: "Show open tasks with due dates"},
|
|
{Name: "new", Command: "create $@", Description: "Create a new task"},
|
|
}
|
|
|
|
func LoadConfig() (Config, error) {
|
|
path, err := findConfigPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return loadConfig(path)
|
|
}
|
|
|
|
func loadConfig(path string) (*fileConfig, error) {
|
|
fc := &fileConfig{path: path, UserAliases: []*Alias{}}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
if err := json.Unmarshal(data, fc); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return fc, nil
|
|
}
|
|
|
|
func findConfigPath() (string, error) {
|
|
dir, err := filepath.Abs(".")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for {
|
|
p := filepath.Join(dir, ".axconfig")
|
|
if _, err := os.Stat(p); err == nil {
|
|
return p, nil
|
|
}
|
|
if parent := filepath.Dir(dir); parent == dir {
|
|
break
|
|
} else {
|
|
dir = parent
|
|
}
|
|
}
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Join(home, ".config", "ax", "config.json"), nil
|
|
}
|
|
|
|
func (c *fileConfig) GetUser() string {
|
|
if c.User != "" {
|
|
return c.User
|
|
}
|
|
if u := os.Getenv("AX_USER"); u != "" {
|
|
return u
|
|
}
|
|
if u, err := user.Current(); err == nil {
|
|
return u.Username
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
func (c *fileConfig) SetUser(username string) error {
|
|
c.User = username
|
|
return c.Save()
|
|
}
|
|
|
|
func (c *fileConfig) GetAlias(name string) (*Alias, error) {
|
|
for _, a := range c.UserAliases {
|
|
if a.Name == name {
|
|
return a, nil
|
|
}
|
|
}
|
|
for _, a := range defaultAliases {
|
|
if a.Name == name {
|
|
return a, nil
|
|
}
|
|
}
|
|
return nil, errors.New("alias not found")
|
|
}
|
|
|
|
func (c *fileConfig) SetAlias(alias *Alias) error {
|
|
for i, a := range c.UserAliases {
|
|
if a.Name == alias.Name {
|
|
c.UserAliases[i] = alias
|
|
return c.Save()
|
|
}
|
|
}
|
|
c.UserAliases = append(c.UserAliases, alias)
|
|
return c.Save()
|
|
}
|
|
|
|
func (c *fileConfig) DeleteAlias(name string) error {
|
|
for i, a := range c.UserAliases {
|
|
if a.Name == name {
|
|
c.UserAliases = slices.Delete(c.UserAliases, i, i+1)
|
|
return c.Save()
|
|
}
|
|
}
|
|
for _, a := range defaultAliases {
|
|
if a.Name == name {
|
|
return errors.New("cannot delete default alias")
|
|
}
|
|
}
|
|
return errors.New("alias not found")
|
|
}
|
|
|
|
func (c *fileConfig) ListAliases() ([]*Alias, error) {
|
|
seen := make(map[string]bool)
|
|
var result []*Alias
|
|
for _, a := range c.UserAliases {
|
|
result = append(result, a)
|
|
seen[a.Name] = true
|
|
}
|
|
for _, a := range defaultAliases {
|
|
if !seen[a.Name] {
|
|
result = append(result, a)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (c *fileConfig) Save() error {
|
|
if err := os.MkdirAll(filepath.Dir(c.path), 0755); err != nil {
|
|
return err
|
|
}
|
|
data, err := json.MarshalIndent(c, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(c.path, data, 0644)
|
|
}
|
|
|
|
func ExpandAlias(alias *Alias, args []string, currentUser string) []string {
|
|
cmd := alias.Command
|
|
cmd = strings.ReplaceAll(cmd, "$me", currentUser)
|
|
|
|
parts := strings.Fields(cmd)
|
|
var result []string
|
|
|
|
for _, part := range parts {
|
|
if part == "$@" {
|
|
result = append(result, args...)
|
|
continue
|
|
}
|
|
|
|
hasCatchAll := strings.Contains(part, "$@")
|
|
replaced := part
|
|
|
|
if hasCatchAll {
|
|
replaced = strings.ReplaceAll(replaced, "$@", strings.Join(args, " "))
|
|
}
|
|
|
|
for i := len(args) - 1; i >= 0; i-- {
|
|
placeholder := fmt.Sprintf("$%d", i+1)
|
|
replaced = strings.ReplaceAll(replaced, placeholder, args[i])
|
|
}
|
|
|
|
result = append(result, replaced)
|
|
}
|
|
|
|
return result
|
|
}
|