//go:build mage package main import ( "fmt" "io/fs" "os" "path/filepath" "slices" "strings" "text/template" ) const templateExtension string = ".gotmpl" // Templates generates the configuration files in the managed directory from the templates and // ensures that they the generated files are symlinked correctly to the files in the user's home // configuration directory. func Templates() error { homeConfigDirectory, err := os.UserConfigDir() if err != nil { return fmt.Errorf("unable to get the user's home configuration directory: %w", err) } config, err := newConfig() if err != nil { return fmt.Errorf("unable to load the configuration: %w", err) } managedConfig := managedConfigSet(config.ManagedConfigurations) if err = filepath.WalkDir(rootTemplateDir, manageTemplatesFunc(homeConfigDirectory, config, managedConfig)); err != nil { return fmt.Errorf("received an error while processing the templates: %w", err) } return nil } func manageTemplatesFunc(homeConfigDirectory string, config config, managedConfig map[string]struct{}) fs.WalkDirFunc { funcMap := template.FuncMap{ "env": env, } return func(templatePath string, d fs.DirEntry, err error) error { if err != nil { return err } if templatePath == rootTemplateDir { return nil } relativePath := strings.TrimPrefix(templatePath, rootTemplateDir+"/") appConfigName := strings.SplitN(relativePath, "/", 2)[0] if _, exists := managedConfig[appConfigName]; !exists { return nil } if d.IsDir() { managedDir := filepath.Join(rootManagedDir, relativePath) configDir := filepath.Join(homeConfigDirectory, relativePath) dirs := []string{managedDir, configDir} 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 !strings.HasSuffix(templatePath, templateExtension) { return fmt.Errorf( "the template %s does not have the %q file extension", templatePath, templateExtension, ) } managedPath := filepath.Join(rootManagedDir, strings.TrimSuffix(relativePath, templateExtension)) configPath := filepath.Join(homeConfigDirectory, strings.TrimSuffix(relativePath, templateExtension)) fmt.Println("Processing template:", relativePath) if err := renderTemplate(config, templatePath, managedPath, funcMap); err != nil { return fmt.Errorf( "unable to generate %s from template %s: %w", managedPath, templatePath, err, ) } if err := ensureSymlink(managedPath, configPath); err != nil { return err } return nil } } func renderTemplate(config config, templatePath, managedPath string, funcMap template.FuncMap) error { name := filepath.Base(templatePath) tmpl, err := template.New(name).Funcs(funcMap).ParseFiles(templatePath) if err != nil { return fmt.Errorf("unable to create a new template value from %s: %w", templatePath, err) } output, err := os.Create(managedPath) if err != nil { return fmt.Errorf("unable to create %s: %w", managedPath, err) } defer output.Close() if err := tmpl.Execute(output, config); err != nil { return fmt.Errorf("unable to render the template to %s: %w", managedPath, err) } return nil } func env(value string) string { return os.Getenv(value) }