package service import ( "encoding/json" "errors" "os" "os/user" "path/filepath" "slices" ) type fileConfig struct { path string User string `json:"user"` UserAliases []*Alias `json:"aliases"` Serve ServerConfig `json:"serve"` Remote ServerConfig `json:"remote"` OIDC OIDCConfig `json:"oidc"` } var defaultAliases = []*Alias{ {Name: "mine", Command: "list --assignee $me --type issue --status open", Description: "Show open issues assigned to you"}, {Name: "due", Command: "list --type issue --status open", Description: "Show open issues"}, {Name: "inbox", Command: "list --mention $me", Description: "Show your inbox"}, } func LoadConfigFile() (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) GetOIDCConfig() (OIDCConfig, bool) { if c.OIDC.Issuer == "" { return OIDCConfig{}, false } cfg := c.OIDC if cfg.UserClaim == "" { cfg.UserClaim = "preferred_username" } return cfg, true } func (c *fileConfig) GetRemoteConfig() (ServerConfig, bool) { if c.Remote.Host == "" { return ServerConfig{}, false } port := c.Remote.Port if port == 0 { port = 7000 } return ServerConfig{Host: c.Remote.Host, Port: port}, true } func (c *fileConfig) GetServerConfig() ServerConfig { host := c.Serve.Host if host == "" { host = "localhost" } port := c.Serve.Port if port == 0 { port = 7000 } return ServerConfig{Host: host, Port: port} } 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) }