178 lines
5.7 KiB
Go
178 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Config holds all externally configurable settings. It is read entirely from
|
|
// ~/.mgshrc and then overlaid with MGSH_* environment variables — there are no
|
|
// built-in defaults. Missing required settings are a fatal error (see
|
|
// missingRequired).
|
|
type Config struct {
|
|
Base string // project base directory
|
|
GitHost string // git server host
|
|
GitPort string // ssh port
|
|
GitUser string // ssh user
|
|
GitPath string // remote path holding the bare repos
|
|
GitKey string // ssh key name (informational)
|
|
GitName string // git user.name to set globally ("" = leave alone)
|
|
GitEmail string // git user.email to set globally ("" = leave alone)
|
|
PushDefault string // git push.default to set globally ("" = leave alone)
|
|
Editor string // editor/opener used as fallback by `open` ("" = coda)
|
|
RemoteURL string // public mirror server base URL (Gitea/GitHub/GitLab)
|
|
RemoteKey string // API token for the mirror server (used by `pushremote`)
|
|
RemoteType string // "gitea"|"github"|"gitlab" (auto-detected when empty)
|
|
RemoteVis string // visibility of created repos: "private" (default)|"public"
|
|
Mirror string // truthy -> `push` also mirrors via `pushremote`
|
|
}
|
|
|
|
// requiredKeys lists the settings mgsh cannot run without.
|
|
var requiredKeys = []string{"base", "githost", "gitport", "gituser", "gitpath"}
|
|
|
|
// missingRequired reports which required settings are still unset. mgsh refuses
|
|
// to start while this is non-empty.
|
|
func (c Config) missingRequired() []string {
|
|
have := map[string]string{
|
|
"base": c.Base, "githost": c.GitHost, "gitport": c.GitPort,
|
|
"gituser": c.GitUser, "gitpath": c.GitPath,
|
|
}
|
|
var missing []string
|
|
for _, k := range requiredKeys {
|
|
if have[k] == "" {
|
|
missing = append(missing, k)
|
|
}
|
|
}
|
|
return missing
|
|
}
|
|
|
|
// configFile returns the path of the user config file (~/.mgshrc).
|
|
func configFile() string {
|
|
home, _ := os.UserHomeDir()
|
|
return filepath.Join(home, ".mgshrc")
|
|
}
|
|
|
|
// loadConfig writes an empty ~/.mgshrc template on first run, then returns the
|
|
// configuration parsed from it, overlaid with MGSH_* environment variables.
|
|
// It applies no defaults; validation of required settings is the caller's job.
|
|
func loadConfig() Config {
|
|
path := configFile()
|
|
if !fileExists(path) {
|
|
writeConfigTemplate(path)
|
|
}
|
|
var c Config
|
|
if data, err := os.ReadFile(path); err == nil {
|
|
applyConfig(&c, parseConfig(string(data)))
|
|
}
|
|
applyEnv(&c)
|
|
return c
|
|
}
|
|
|
|
// writeConfigTemplate creates a blank, annotated ~/.mgshrc for the user to fill
|
|
// in, migrating any aliases from the legacy ~/.mgsh_aliases file. It writes no
|
|
// real values — mgsh has no built-in configuration.
|
|
func writeConfigTemplate(path string) {
|
|
var b strings.Builder
|
|
b.WriteString("# mgsh configuration — fill in the required settings, then run mgsh again.\n")
|
|
b.WriteString("# Format: 'key = value' (or 'key: value'); '#' starts a comment.\n")
|
|
b.WriteString("# MGSH_* environment variables override these settings.\n\n")
|
|
b.WriteString("# --- required ---\n")
|
|
b.WriteString("base =\n")
|
|
b.WriteString("githost =\n")
|
|
b.WriteString("gitport =\n")
|
|
b.WriteString("gituser =\n")
|
|
b.WriteString("gitpath =\n\n")
|
|
b.WriteString("# --- optional ---\n")
|
|
b.WriteString("# gitkey =\n")
|
|
b.WriteString("# gitname = Your Name\n")
|
|
b.WriteString("# gitemail = you@example.com\n")
|
|
b.WriteString("# pushdefault = matching\n")
|
|
b.WriteString("# editor = code\n")
|
|
|
|
legacy := readLegacyAliases()
|
|
if blk := aliasBlock(legacy); blk != "" {
|
|
b.WriteString("\n" + blk)
|
|
}
|
|
|
|
if err := os.WriteFile(path, []byte(b.String()), 0644); err != nil {
|
|
errorln("could not create " + path + ": " + err.Error())
|
|
return
|
|
}
|
|
if len(legacy) > 0 {
|
|
os.Remove(legacyAliasFile()) // aliases now live in ~/.mgshrc
|
|
}
|
|
}
|
|
|
|
// parseConfig reads simple `key = value` (or `key: value`) lines, ignoring
|
|
// blank lines, `#` comments and `alias` definitions. Keys are lower-cased;
|
|
// values are unquoted.
|
|
func parseConfig(s string) map[string]string {
|
|
m := map[string]string{}
|
|
sc := bufio.NewScanner(strings.NewReader(s))
|
|
for sc.Scan() {
|
|
line := strings.TrimSpace(sc.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
if f := strings.Fields(line); len(f) > 0 && f[0] == "alias" {
|
|
continue // alias definitions are handled by the alias loader
|
|
}
|
|
i := strings.IndexAny(line, "=:")
|
|
if i < 0 {
|
|
continue
|
|
}
|
|
key := strings.ToLower(strings.TrimSpace(line[:i]))
|
|
val := strings.Trim(strings.TrimSpace(line[i+1:]), "\"'")
|
|
m[key] = val
|
|
}
|
|
return m
|
|
}
|
|
|
|
func applyConfig(c *Config, m map[string]string) {
|
|
set := func(key string, dst *string) {
|
|
if v, ok := m[key]; ok && v != "" {
|
|
*dst = v
|
|
}
|
|
}
|
|
set("base", &c.Base)
|
|
set("githost", &c.GitHost)
|
|
set("gitport", &c.GitPort)
|
|
set("gituser", &c.GitUser)
|
|
set("gitpath", &c.GitPath)
|
|
set("gitkey", &c.GitKey)
|
|
set("gitname", &c.GitName)
|
|
set("gitemail", &c.GitEmail)
|
|
set("pushdefault", &c.PushDefault)
|
|
set("editor", &c.Editor)
|
|
set("remoteurl", &c.RemoteURL)
|
|
set("remotekey", &c.RemoteKey)
|
|
set("remotetype", &c.RemoteType)
|
|
set("remotevisibility", &c.RemoteVis)
|
|
set("mirror", &c.Mirror)
|
|
}
|
|
|
|
func applyEnv(c *Config) {
|
|
env := func(key string, dst *string) {
|
|
if v := os.Getenv(key); v != "" {
|
|
*dst = v
|
|
}
|
|
}
|
|
env("MGSH_BASE", &c.Base)
|
|
env("MGSH_GITHOST", &c.GitHost)
|
|
env("MGSH_GITPORT", &c.GitPort)
|
|
env("MGSH_GITUSER", &c.GitUser)
|
|
env("MGSH_GITPATH", &c.GitPath)
|
|
env("MGSH_GITKEY", &c.GitKey)
|
|
env("MGSH_GITNAME", &c.GitName)
|
|
env("MGSH_GITEMAIL", &c.GitEmail)
|
|
env("MGSH_PUSHDEFAULT", &c.PushDefault)
|
|
env("MGSH_EDITOR", &c.Editor)
|
|
env("MGSH_REMOTEURL", &c.RemoteURL)
|
|
env("MGSH_REMOTEKEY", &c.RemoteKey)
|
|
env("MGSH_REMOTETYPE", &c.RemoteType)
|
|
env("MGSH_REMOTEVISIBILITY", &c.RemoteVis)
|
|
env("MGSH_MIRROR", &c.Mirror)
|
|
}
|