package main import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/utils" "github.com/spf13/cobra" ) var configCmd = &cobra.Command{ Use: "config", Short: "Configuration utilities", } var resolveIncludeCmd = &cobra.Command{ Use: "resolve-include ", Short: "Check if a file is included in compositor config", Long: "Recursively check if a file is included/sourced in compositor configuration. Returns JSON with exists and included status.", Args: cobra.ExactArgs(2), ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { switch len(args) { case 0: return []string{"hyprland", "niri", "mangowc"}, cobra.ShellCompDirectiveNoFileComp case 1: return []string{"cursor.kdl", "cursor.conf", "outputs.kdl", "outputs.conf", "binds.kdl", "binds.conf"}, cobra.ShellCompDirectiveNoFileComp } return nil, cobra.ShellCompDirectiveNoFileComp }, Run: runResolveInclude, } func init() { configCmd.AddCommand(resolveIncludeCmd) } type IncludeResult struct { Exists bool `json:"exists"` Included bool `json:"included"` } func runResolveInclude(cmd *cobra.Command, args []string) { compositor := strings.ToLower(args[0]) filename := args[1] var result IncludeResult var err error switch compositor { case "hyprland": result, err = checkHyprlandInclude(filename) case "niri": result, err = checkNiriInclude(filename) case "mangowc", "dwl", "mango": result, err = checkMangoWCInclude(filename) default: log.Fatalf("Unknown compositor: %s", compositor) } if err != nil { log.Fatalf("Error checking include: %v", err) } output, _ := json.Marshal(result) fmt.Fprintln(os.Stdout, string(output)) } func checkHyprlandInclude(filename string) (IncludeResult, error) { configDir, err := utils.ExpandPath("$HOME/.config/hypr") if err != nil { return IncludeResult{}, err } targetPath := filepath.Join(configDir, "dms", filename) result := IncludeResult{} if _, err := os.Stat(targetPath); err == nil { result.Exists = true } mainConfig := filepath.Join(configDir, "hyprland.conf") if _, err := os.Stat(mainConfig); os.IsNotExist(err) { return result, nil } processed := make(map[string]bool) result.Included = hyprlandFindInclude(mainConfig, "dms/"+filename, processed) return result, nil } func hyprlandFindInclude(filePath, target string, processed map[string]bool) bool { absPath, err := filepath.Abs(filePath) if err != nil { return false } if processed[absPath] { return false } processed[absPath] = true data, err := os.ReadFile(absPath) if err != nil { return false } baseDir := filepath.Dir(absPath) lines := strings.Split(string(data), "\n") for _, line := range lines { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "#") || trimmed == "" { continue } if !strings.HasPrefix(trimmed, "source") { continue } parts := strings.SplitN(trimmed, "=", 2) if len(parts) < 2 { continue } sourcePath := strings.TrimSpace(parts[1]) if matchesTarget(sourcePath, target) { return true } fullPath := sourcePath if !filepath.IsAbs(sourcePath) { fullPath = filepath.Join(baseDir, sourcePath) } expanded, err := utils.ExpandPath(fullPath) if err != nil { continue } if hyprlandFindInclude(expanded, target, processed) { return true } } return false } func checkNiriInclude(filename string) (IncludeResult, error) { configDir, err := utils.ExpandPath("$HOME/.config/niri") if err != nil { return IncludeResult{}, err } targetPath := filepath.Join(configDir, "dms", filename) result := IncludeResult{} if _, err := os.Stat(targetPath); err == nil { result.Exists = true } mainConfig := filepath.Join(configDir, "config.kdl") if _, err := os.Stat(mainConfig); os.IsNotExist(err) { return result, nil } processed := make(map[string]bool) result.Included = niriFindInclude(mainConfig, "dms/"+filename, processed) return result, nil } func niriFindInclude(filePath, target string, processed map[string]bool) bool { absPath, err := filepath.Abs(filePath) if err != nil { return false } if processed[absPath] { return false } processed[absPath] = true data, err := os.ReadFile(absPath) if err != nil { return false } baseDir := filepath.Dir(absPath) content := string(data) for _, line := range strings.Split(content, "\n") { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "//") || trimmed == "" { continue } if !strings.HasPrefix(trimmed, "include") { continue } startQuote := strings.Index(trimmed, "\"") if startQuote == -1 { continue } endQuote := strings.LastIndex(trimmed, "\"") if endQuote <= startQuote { continue } includePath := trimmed[startQuote+1 : endQuote] if matchesTarget(includePath, target) { return true } fullPath := includePath if !filepath.IsAbs(includePath) { fullPath = filepath.Join(baseDir, includePath) } if niriFindInclude(fullPath, target, processed) { return true } } return false } func checkMangoWCInclude(filename string) (IncludeResult, error) { configDir, err := utils.ExpandPath("$HOME/.config/mango") if err != nil { return IncludeResult{}, err } targetPath := filepath.Join(configDir, "dms", filename) result := IncludeResult{} if _, err := os.Stat(targetPath); err == nil { result.Exists = true } mainConfig := filepath.Join(configDir, "config.conf") if _, err := os.Stat(mainConfig); os.IsNotExist(err) { mainConfig = filepath.Join(configDir, "mango.conf") } if _, err := os.Stat(mainConfig); os.IsNotExist(err) { return result, nil } processed := make(map[string]bool) result.Included = mangowcFindInclude(mainConfig, "dms/"+filename, processed) return result, nil } func mangowcFindInclude(filePath, target string, processed map[string]bool) bool { absPath, err := filepath.Abs(filePath) if err != nil { return false } if processed[absPath] { return false } processed[absPath] = true data, err := os.ReadFile(absPath) if err != nil { return false } baseDir := filepath.Dir(absPath) lines := strings.Split(string(data), "\n") for _, line := range lines { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "#") || trimmed == "" { continue } if !strings.HasPrefix(trimmed, "source") { continue } parts := strings.SplitN(trimmed, "=", 2) if len(parts) < 2 { continue } sourcePath := strings.TrimSpace(parts[1]) if matchesTarget(sourcePath, target) { return true } fullPath := sourcePath if !filepath.IsAbs(sourcePath) { fullPath = filepath.Join(baseDir, sourcePath) } expanded, err := utils.ExpandPath(fullPath) if err != nil { continue } if mangowcFindInclude(expanded, target, processed) { return true } } return false } func matchesTarget(path, target string) bool { path = strings.TrimPrefix(path, "./") target = strings.TrimPrefix(target, "./") return path == target || strings.HasSuffix(path, "/"+target) }