//go:build mage package main import ( "errors" "fmt" "io/fs" "os" "path/filepath" "slices" "strings" "github.com/magefile/mage/sh" ) // Files ensure that the configuration files in the managed directory is up to date and // ensures that they are symlinked correctly to the files in the user's home configuration // directory. func Files() error { homeConfigDirectory, err := os.UserConfigDir() if err != nil { return fmt.Errorf("unable to get the user's home configuration directory: %w", err) } if err = filepath.WalkDir(rootFilesDir, manageFilesFunc(homeConfigDirectory)); err != nil { return fmt.Errorf("received an error while processing the files: %w", err) } return nil } func manageFilesFunc(homeConfigDirectory string) fs.WalkDirFunc { return func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if path == rootFilesDir { return nil } relativePath := strings.TrimPrefix(path, rootFilesDir+"/") managedPath := filepath.Join(rootManagedDir, relativePath) configPath := filepath.Join(homeConfigDirectory, relativePath) if d.IsDir() { dirs := []string{managedPath, configPath} for _, dir := range slices.All(dirs) { if err := ensureDirectory(dir); err != nil { return fmt.Errorf("unable to ensure the existence of the directory %q: %w", dir, err) } } return nil } if err := sh.Copy(managedPath, path); err != nil { return fmt.Errorf("unable to copy %s to %s: %w", path, managedPath, err) } if err := ensureSymlink(managedPath, configPath); err != nil { return err } return nil } } func ensureSymlink(source, dest string) error { absolutePathErrorMessageFormat := "unable to get the absolute path to %s: %w" absoluteSourcePath, err := filepath.Abs(source) if err != nil { return fmt.Errorf(absolutePathErrorMessageFormat, source, err) } absoluteDestPath, err := filepath.Abs(dest) if err != nil { return fmt.Errorf(absolutePathErrorMessageFormat, dest, err) } destInfo, err := os.Lstat(absoluteDestPath) if err != nil { if errors.Is(err, os.ErrNotExist) { fmt.Printf("Linking %s to %s\n", absoluteDestPath, absoluteSourcePath) if err := os.Symlink(absoluteSourcePath, absoluteDestPath); err != nil { return fmt.Errorf( "unable to symlink %s to %s: %w", absoluteDestPath, absoluteSourcePath, err, ) } return nil } return fmt.Errorf("unable to get the file info for %s: %w", absoluteDestPath, err) } if destInfo.Mode().Type() != fs.ModeSymlink { return fmt.Errorf("the path %s exists but it is not a symlink", absoluteDestPath) } destLinksTo, err := filepath.EvalSymlinks(absoluteDestPath) if err != nil { return fmt.Errorf("unable to evaluate the symlink %s: %w", absoluteDestPath, err) } if destLinksTo == absoluteSourcePath { return nil } fmt.Printf( "%s should link back to %s but instead links back to %s\n", absoluteDestPath, absoluteSourcePath, destLinksTo, ) fmt.Println("Recreating:", absoluteDestPath) if err := os.Remove(absoluteDestPath); err != nil { return fmt.Errorf("unable to remove %s: %w", absoluteDestPath, err) } if err := os.Symlink(absoluteSourcePath, absoluteDestPath); err != nil { return fmt.Errorf( "unable to symlink %s to %s: %w", absoluteDestPath, absoluteSourcePath, err, ) } return nil }