commit 31d0031

Jan Fooken  ·  2025-01-29 16:37:42 +0000 UTC
parent 743bbea
Prefer $XDG_CONFIG_HOME over platform's directory on MacOS

This commit implements the preference of $XDG_CONFIG_HOME over the
platform's native equivalent on MacOS.  This allows users to
use $HOME/.config as preferred path for storing configurations. This is
preferred by a lot of people, who are using Git repositories to backup
their dotfiles.  Furthermore, there is precedence of macOS applications
storing their configuration files in $HOME/.config by default.

Also added: a "migration" from the preivous configuration file
for users that had already this envrionment variable set.

Signed-off-by: Jan Fooken <jan@faulty.computer>
Co-authored-by: delthas <delthas@dille.cc>
Fixes: https://todo.sr.ht/~delthas/senpai/174
2 files changed,  +74, -17
+73, -16
  1@@ -4,11 +4,13 @@ import (
  2 	"errors"
  3 	"flag"
  4 	"fmt"
  5+	"io"
  6 	"math/rand"
  7 	"os"
  8 	"os/signal"
  9 	"os/user"
 10 	"path"
 11+	"runtime"
 12 	"strings"
 13 	"syscall"
 14 	"time"
 15@@ -38,15 +40,49 @@ func main() {
 16 		return
 17 	}
 18 
 19+	var previousConfigPath string
 20 	if configPath == "" {
 21 		configDir, err := os.UserConfigDir()
 22 		if err != nil {
 23 			panic(err)
 24 		}
 25+		if runtime.GOOS == "darwin" {
 26+			if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" && dir != configDir {
 27+				previousConfigPath = configDir
 28+				configDir = dir
 29+			}
 30+		}
 31 		configPath = path.Join(configDir, "senpai", "senpai.scfg")
 32+		if previousConfigPath != "" {
 33+			previousConfigPath = path.Join(previousConfigPath, "senpai", "senpai.scfg")
 34+		}
 35 	}
 36 
 37 	cfg, err := senpai.LoadConfigFile(configPath)
 38+	if err != nil && errors.Is(err, os.ErrNotExist) && previousConfigPath != "" {
 39+		var ee error
 40+		cfg, ee = senpai.LoadConfigFile(previousConfigPath)
 41+		if ee == nil {
 42+			err = nil
 43+			fmt.Fprintf(os.Stderr, "Configuration assistant: Previous configuration file found at %q; the new default configuration location is now %q\n", previousConfigPath, configPath)
 44+			fmt.Fprintf(os.Stderr, "Configuration assistant: You can run senpai with an explicit -config path argument, or move the file to its new location.\n")
 45+			fmt.Fprintf(os.Stderr, "Configuration assistant: Would you like senpai to move the configuration file to the new location? (examples: yes, no) [optional, default: yes]: ")
 46+			move := true
 47+			scanBool(&move)
 48+			if move {
 49+				if err := copyContents(configPath, previousConfigPath); err != nil {
 50+					fmt.Fprintf(os.Stderr, "failed to move the configuration file: %v\n", err)
 51+					os.Exit(1)
 52+					return
 53+				}
 54+				if err := os.Remove(previousConfigPath); err != nil {
 55+					fmt.Fprintf(os.Stderr, "failed to delete the previous configuration file at %q: %v\n", previousConfigPath, err)
 56+					os.Exit(1)
 57+					return
 58+				}
 59+			}
 60+		}
 61+	}
 62 	if err != nil {
 63 		if !errors.Is(err, os.ErrNotExist) {
 64 			fmt.Fprintf(os.Stderr, "failed to load the required configuration file at %q: %s\n", configPath, err)
 65@@ -72,22 +108,7 @@ func main() {
 66 		fmt.Fprintf(os.Stderr, "Configuration assistant: Enter your server port (examples: 6667, 6697) [optional]: ")
 67 		fmt.Scanln(&port)
 68 		fmt.Fprintf(os.Stderr, "Configuration assistant: Enter whether your server uses TLS (examples: yes, no) [optional, default: yes]: ")
 69-		for {
 70-			var tlsStr string
 71-			fmt.Scanln(&tlsStr)
 72-			if tlsStr == "" {
 73-				break
 74-			}
 75-			switch strings.ToLower(tlsStr) {
 76-			case "y", "yes":
 77-				tls = true
 78-			case "n", "no":
 79-				tls = false
 80-			default:
 81-				continue
 82-			}
 83-			break
 84-		}
 85+		scanBool(&tls)
 86 		var defaultNick string
 87 		if u, err := user.Current(); err == nil {
 88 			defaultNick = u.Username
 89@@ -147,6 +168,7 @@ func main() {
 90 			time.Sleep(500 * time.Millisecond)
 91 			fmt.Fprintf(os.Stderr, ".")
 92 		}
 93+		fmt.Fprintf(os.Stderr, "\n")
 94 	}
 95 
 96 	cfg.Debug = cfg.Debug || debug
 97@@ -183,6 +205,41 @@ func main() {
 98 	}
 99 }
100 
101+func scanBool(v *bool) {
102+	for {
103+		var s string
104+		fmt.Scanln(&s)
105+		if s == "" {
106+			return
107+		}
108+		switch strings.ToLower(s) {
109+		case "y", "yes":
110+			*v = true
111+			return
112+		case "n", "no":
113+			*v = false
114+			return
115+		}
116+	}
117+}
118+
119+func copyContents(dstPath string, srcPath string) error {
120+	src, err := os.Open(srcPath)
121+	if err != nil {
122+		return fmt.Errorf("open %q: %v", srcPath, err)
123+	}
124+	defer src.Close()
125+	dst, err := os.Create(dstPath)
126+	if err != nil {
127+		return fmt.Errorf("create %q: %v", dstPath, err)
128+	}
129+	defer dst.Close()
130+	if _, err := io.Copy(dst, src); err != nil {
131+		return fmt.Errorf("copy %q to %q: %v", srcPath, dstPath, err)
132+	}
133+	return nil
134+}
135+
136 func cachePath() string {
137 	cacheDir, err := os.UserCacheDir()
138 	if err != nil {
+1, -1
1@@ -39,7 +39,7 @@ senpai searches for it in the following location:
2 
3 	$XDG_CONFIG_HOME/senpai/senpai.scfg
4 
5-If unset, $XDG_CONFIG_HOME defaults to *~/.config*.
6+If unset, $XDG_CONFIG_HOME defaults to *~/.config* or the platform's equivalent
7 
8 For information about the configuration format, see *senpai*(5).
9