diff --git a/.github/workflows/prek.yml b/.github/workflows/prek.yml index 1b2b95af8..c04b7cb1f 100644 --- a/.github/workflows/prek.yml +++ b/.github/workflows/prek.yml @@ -11,5 +11,14 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Install flatpak + run: sudo apt update && sudo apt install -y flatpak + + - name: Add flathub + run: sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + + - name: Add a flatpak that mutagen could support + run: sudo flatpak install -y org.freedesktop.Platform/x86_64/24.08 app.zen_browser.zen + - name: run pre-commit hooks uses: j178/prek-action@v1 diff --git a/core/internal/matugen/matugen.go b/core/internal/matugen/matugen.go index 7ab24305e..0301ea8e2 100644 --- a/core/internal/matugen/matugen.go +++ b/core/internal/matugen/matugen.go @@ -250,61 +250,61 @@ output_path = '%s' if !opts.ShouldSkipTemplate("gtk") { switch opts.Mode { case "light": - appendConfig(opts, cfgFile, nil, "gtk3-light.toml") + appendConfig(opts, cfgFile, nil, nil, "gtk3-light.toml") default: - appendConfig(opts, cfgFile, nil, "gtk3-dark.toml") + appendConfig(opts, cfgFile, nil, nil, "gtk3-dark.toml") } } if !opts.ShouldSkipTemplate("niri") { - appendConfig(opts, cfgFile, []string{"niri"}, "niri.toml") + appendConfig(opts, cfgFile, []string{"niri"}, nil, "niri.toml") } if !opts.ShouldSkipTemplate("qt5ct") { - appendConfig(opts, cfgFile, []string{"qt5ct"}, "qt5ct.toml") + appendConfig(opts, cfgFile, []string{"qt5ct"}, nil, "qt5ct.toml") } if !opts.ShouldSkipTemplate("qt6ct") { - appendConfig(opts, cfgFile, []string{"qt6ct"}, "qt6ct.toml") + appendConfig(opts, cfgFile, []string{"qt6ct"}, nil, "qt6ct.toml") } if !opts.ShouldSkipTemplate("firefox") { - appendConfig(opts, cfgFile, []string{"firefox"}, "firefox.toml") + appendConfig(opts, cfgFile, []string{"firefox"}, nil, "firefox.toml") } if !opts.ShouldSkipTemplate("pywalfox") { - appendConfig(opts, cfgFile, []string{"pywalfox"}, "pywalfox.toml") + appendConfig(opts, cfgFile, []string{"pywalfox"}, nil, "pywalfox.toml") } if !opts.ShouldSkipTemplate("zenbrowser") { - appendConfig(opts, cfgFile, []string{"zen", "zen-browser"}, "zenbrowser.toml") + appendConfig(opts, cfgFile, []string{"zen", "zen-browser"}, []string{"app.zen_browser.zen"}, "zenbrowser.toml") } if !opts.ShouldSkipTemplate("vesktop") { - appendConfig(opts, cfgFile, []string{"vesktop"}, "vesktop.toml") + appendConfig(opts, cfgFile, []string{"vesktop"}, []string{"dev.vencord.Vesktop"}, "vesktop.toml") } if !opts.ShouldSkipTemplate("equibop") { - appendConfig(opts, cfgFile, []string{"equibop"}, "equibop.toml") + appendConfig(opts, cfgFile, []string{"equibop"}, nil, "equibop.toml") } if !opts.ShouldSkipTemplate("ghostty") { - appendTerminalConfig(opts, cfgFile, tmpDir, []string{"ghostty"}, "ghostty.toml") + appendTerminalConfig(opts, cfgFile, tmpDir, []string{"ghostty"}, nil, "ghostty.toml") } if !opts.ShouldSkipTemplate("kitty") { - appendTerminalConfig(opts, cfgFile, tmpDir, []string{"kitty"}, "kitty.toml") + appendTerminalConfig(opts, cfgFile, tmpDir, []string{"kitty"}, nil, "kitty.toml") } if !opts.ShouldSkipTemplate("foot") { - appendTerminalConfig(opts, cfgFile, tmpDir, []string{"foot"}, "foot.toml") + appendTerminalConfig(opts, cfgFile, tmpDir, []string{"foot"}, nil, "foot.toml") } if !opts.ShouldSkipTemplate("alacritty") { - appendTerminalConfig(opts, cfgFile, tmpDir, []string{"alacritty"}, "alacritty.toml") + appendTerminalConfig(opts, cfgFile, tmpDir, []string{"alacritty"}, nil, "alacritty.toml") } if !opts.ShouldSkipTemplate("wezterm") { - appendTerminalConfig(opts, cfgFile, tmpDir, []string{"wezterm"}, "wezterm.toml") + appendTerminalConfig(opts, cfgFile, tmpDir, []string{"wezterm"}, nil, "wezterm.toml") } if !opts.ShouldSkipTemplate("nvim") { - appendTerminalConfig(opts, cfgFile, tmpDir, []string{"nvim"}, "neovim.toml") + appendTerminalConfig(opts, cfgFile, tmpDir, []string{"nvim"}, nil, "neovim.toml") } if !opts.ShouldSkipTemplate("dgop") { - appendConfig(opts, cfgFile, []string{"dgop"}, "dgop.toml") + appendConfig(opts, cfgFile, []string{"dgop"}, nil, "dgop.toml") } if !opts.ShouldSkipTemplate("kcolorscheme") { - appendConfig(opts, cfgFile, nil, "kcolorscheme.toml") + appendConfig(opts, cfgFile, nil, nil, "kcolorscheme.toml") } if !opts.ShouldSkipTemplate("vscode") { @@ -342,12 +342,21 @@ output_path = '%s' return nil } -func appendConfig(opts *Options, cfgFile *os.File, checkCmd []string, fileName string) { +func appendConfig( + opts *Options, + cfgFile *os.File, + checkCmd []string, + checkFlatpaks []string, + fileName string, +) { configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName) if _, err := os.Stat(configPath); err != nil { return } - if len(checkCmd) > 0 && !utils.AnyCommandExists(checkCmd...) { + cmdExists := checkCmd == nil || utils.AnyCommandExists(checkCmd...) + flatpakExists := checkFlatpaks == nil || utils.AnyFlatpakExists(checkFlatpaks...) + + if !cmdExists && !flatpakExists { return } data, err := os.ReadFile(configPath) @@ -358,12 +367,15 @@ func appendConfig(opts *Options, cfgFile *os.File, checkCmd []string, fileName s cfgFile.WriteString("\n") } -func appendTerminalConfig(opts *Options, cfgFile *os.File, tmpDir string, checkCmd []string, fileName string) { +func appendTerminalConfig(opts *Options, cfgFile *os.File, tmpDir string, checkCmd []string, checkFlatpaks []string, fileName string) { configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName) if _, err := os.Stat(configPath); err != nil { return } - if len(checkCmd) > 0 && !utils.AnyCommandExists(checkCmd...) { + cmdExists := checkCmd == nil || utils.AnyCommandExists(checkCmd...) + flatpakExists := checkFlatpaks == nil || utils.AnyFlatpakExists(checkFlatpaks...) + + if !cmdExists && !flatpakExists { return } data, err := os.ReadFile(configPath) diff --git a/core/internal/matugen/matugen_test.go b/core/internal/matugen/matugen_test.go new file mode 100644 index 000000000..75862d662 --- /dev/null +++ b/core/internal/matugen/matugen_test.go @@ -0,0 +1,300 @@ +package matugen + +import ( + "os" + "path/filepath" + "testing" +) + +func TestAppendConfigBinaryExists(t *testing.T) { + tempDir := t.TempDir() + + shellDir := filepath.Join(tempDir, "shell") + configsDir := filepath.Join(shellDir, "matugen", "configs") + if err := os.MkdirAll(configsDir, 0755); err != nil { + t.Fatalf("failed to create configs dir: %v", err) + } + + testConfig := "test config content" + configPath := filepath.Join(configsDir, "test.toml") + if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil { + t.Fatalf("failed to write config: %v", err) + } + + outFile := filepath.Join(tempDir, "output.toml") + cfgFile, err := os.Create(outFile) + if err != nil { + t.Fatalf("failed to create output file: %v", err) + } + defer cfgFile.Close() + + opts := &Options{ShellDir: shellDir} + + appendConfig(opts, cfgFile, []string{"sh"}, nil, "test.toml") + + cfgFile.Close() + output, err := os.ReadFile(outFile) + if err != nil { + t.Fatalf("failed to read output: %v", err) + } + + if len(output) == 0 { + t.Errorf("expected config to be written when binary exists") + } + if string(output) != testConfig+"\n" { + t.Errorf("expected %q, got %q", testConfig+"\n", string(output)) + } +} + +func TestAppendConfigBinaryDoesNotExist(t *testing.T) { + tempDir := t.TempDir() + + shellDir := filepath.Join(tempDir, "shell") + configsDir := filepath.Join(shellDir, "matugen", "configs") + if err := os.MkdirAll(configsDir, 0755); err != nil { + t.Fatalf("failed to create configs dir: %v", err) + } + + testConfig := "test config content" + configPath := filepath.Join(configsDir, "test.toml") + if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil { + t.Fatalf("failed to write config: %v", err) + } + + outFile := filepath.Join(tempDir, "output.toml") + cfgFile, err := os.Create(outFile) + if err != nil { + t.Fatalf("failed to create output file: %v", err) + } + defer cfgFile.Close() + + opts := &Options{ShellDir: shellDir} + + appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{}, "test.toml") + + cfgFile.Close() + output, err := os.ReadFile(outFile) + if err != nil { + t.Fatalf("failed to read output: %v", err) + } + + if len(output) != 0 { + t.Errorf("expected no config when binary doesn't exist, got: %q", string(output)) + } +} + +func TestAppendConfigFlatpakExists(t *testing.T) { + tempDir := t.TempDir() + + shellDir := filepath.Join(tempDir, "shell") + configsDir := filepath.Join(shellDir, "matugen", "configs") + if err := os.MkdirAll(configsDir, 0755); err != nil { + t.Fatalf("failed to create configs dir: %v", err) + } + + testConfig := "zen config content" + configPath := filepath.Join(configsDir, "test.toml") + if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil { + t.Fatalf("failed to write config: %v", err) + } + + outFile := filepath.Join(tempDir, "output.toml") + cfgFile, err := os.Create(outFile) + if err != nil { + t.Fatalf("failed to create output file: %v", err) + } + defer cfgFile.Close() + + opts := &Options{ShellDir: shellDir} + + appendConfig(opts, cfgFile, nil, []string{"app.zen_browser.zen"}, "test.toml") + + cfgFile.Close() + output, err := os.ReadFile(outFile) + if err != nil { + t.Fatalf("failed to read output: %v", err) + } + + if len(output) == 0 { + t.Errorf("expected config to be written when flatpak exists") + } +} + +func TestAppendConfigFlatpakDoesNotExist(t *testing.T) { + tempDir := t.TempDir() + + shellDir := filepath.Join(tempDir, "shell") + configsDir := filepath.Join(shellDir, "matugen", "configs") + if err := os.MkdirAll(configsDir, 0755); err != nil { + t.Fatalf("failed to create configs dir: %v", err) + } + + testConfig := "test config content" + configPath := filepath.Join(configsDir, "test.toml") + if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil { + t.Fatalf("failed to write config: %v", err) + } + + outFile := filepath.Join(tempDir, "output.toml") + cfgFile, err := os.Create(outFile) + if err != nil { + t.Fatalf("failed to create output file: %v", err) + } + defer cfgFile.Close() + + opts := &Options{ShellDir: shellDir} + + appendConfig(opts, cfgFile, []string{}, []string{"com.nonexistent.flatpak"}, "test.toml") + + cfgFile.Close() + output, err := os.ReadFile(outFile) + if err != nil { + t.Fatalf("failed to read output: %v", err) + } + + if len(output) != 0 { + t.Errorf("expected no config when flatpak doesn't exist, got: %q", string(output)) + } +} + +func TestAppendConfigBothExist(t *testing.T) { + tempDir := t.TempDir() + + shellDir := filepath.Join(tempDir, "shell") + configsDir := filepath.Join(shellDir, "matugen", "configs") + if err := os.MkdirAll(configsDir, 0755); err != nil { + t.Fatalf("failed to create configs dir: %v", err) + } + + testConfig := "zen config content" + configPath := filepath.Join(configsDir, "test.toml") + if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil { + t.Fatalf("failed to write config: %v", err) + } + + outFile := filepath.Join(tempDir, "output.toml") + cfgFile, err := os.Create(outFile) + if err != nil { + t.Fatalf("failed to create output file: %v", err) + } + defer cfgFile.Close() + + opts := &Options{ShellDir: shellDir} + + appendConfig(opts, cfgFile, []string{"sh"}, []string{"app.zen_browser.zen"}, "test.toml") + + cfgFile.Close() + output, err := os.ReadFile(outFile) + if err != nil { + t.Fatalf("failed to read output: %v", err) + } + + if len(output) == 0 { + t.Errorf("expected config to be written when both binary and flatpak exist") + } +} + +func TestAppendConfigNeitherExists(t *testing.T) { + tempDir := t.TempDir() + + shellDir := filepath.Join(tempDir, "shell") + configsDir := filepath.Join(shellDir, "matugen", "configs") + if err := os.MkdirAll(configsDir, 0755); err != nil { + t.Fatalf("failed to create configs dir: %v", err) + } + + testConfig := "test config content" + configPath := filepath.Join(configsDir, "test.toml") + if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil { + t.Fatalf("failed to write config: %v", err) + } + + outFile := filepath.Join(tempDir, "output.toml") + cfgFile, err := os.Create(outFile) + if err != nil { + t.Fatalf("failed to create output file: %v", err) + } + defer cfgFile.Close() + + opts := &Options{ShellDir: shellDir} + + appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{"com.nonexistent.flatpak"}, "test.toml") + + cfgFile.Close() + output, err := os.ReadFile(outFile) + if err != nil { + t.Fatalf("failed to read output: %v", err) + } + + if len(output) != 0 { + t.Errorf("expected no config when neither exists, got: %q", string(output)) + } +} + +func TestAppendConfigNoChecks(t *testing.T) { + tempDir := t.TempDir() + + shellDir := filepath.Join(tempDir, "shell") + configsDir := filepath.Join(shellDir, "matugen", "configs") + if err := os.MkdirAll(configsDir, 0755); err != nil { + t.Fatalf("failed to create configs dir: %v", err) + } + + testConfig := "always include" + configPath := filepath.Join(configsDir, "test.toml") + if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil { + t.Fatalf("failed to write config: %v", err) + } + + outFile := filepath.Join(tempDir, "output.toml") + cfgFile, err := os.Create(outFile) + if err != nil { + t.Fatalf("failed to create output file: %v", err) + } + defer cfgFile.Close() + + opts := &Options{ShellDir: shellDir} + + appendConfig(opts, cfgFile, nil, nil, "test.toml") + + cfgFile.Close() + output, err := os.ReadFile(outFile) + if err != nil { + t.Fatalf("failed to read output: %v", err) + } + + if len(output) == 0 { + t.Errorf("expected config to be written when no checks specified") + } +} + +func TestAppendConfigFileDoesNotExist(t *testing.T) { + tempDir := t.TempDir() + + shellDir := filepath.Join(tempDir, "shell") + configsDir := filepath.Join(shellDir, "matugen", "configs") + if err := os.MkdirAll(configsDir, 0755); err != nil { + t.Fatalf("failed to create configs dir: %v", err) + } + + outFile := filepath.Join(tempDir, "output.toml") + cfgFile, err := os.Create(outFile) + if err != nil { + t.Fatalf("failed to create output file: %v", err) + } + defer cfgFile.Close() + + opts := &Options{ShellDir: shellDir} + + appendConfig(opts, cfgFile, nil, nil, "nonexistent.toml") + + cfgFile.Close() + output, err := os.ReadFile(outFile) + if err != nil { + t.Fatalf("failed to read output: %v", err) + } + + if len(output) != 0 { + t.Errorf("expected no config when file doesn't exist, got: %q", string(output)) + } +} diff --git a/core/internal/utils/flatpak.go b/core/internal/utils/flatpak.go index ba721b5ea..f1c9009d7 100644 --- a/core/internal/utils/flatpak.go +++ b/core/internal/utils/flatpak.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "os/exec" + "slices" "strings" ) @@ -56,6 +57,10 @@ func FlatpakSearchBySubstring(substring string) bool { return false } +func AnyFlatpakExists(flatpaks ...string) bool { + return slices.ContainsFunc(flatpaks, FlatpakExists) +} + func FlatpakInstallationDir(name string) (string, error) { if !FlatpakInPath() { return "", errors.New("flatpak not found in PATH") diff --git a/core/internal/utils/flatpak_test.go b/core/internal/utils/flatpak_test.go index 9d4ee99a2..7f6f8555c 100644 --- a/core/internal/utils/flatpak_test.go +++ b/core/internal/utils/flatpak_test.go @@ -208,3 +208,42 @@ func TestFlatpakInstallationDirCommandFailure(t *testing.T) { t.Errorf("expected 'not installed' error, got: %v", err) } } + +func TestAnyFlatpakExistsSomeExist(t *testing.T) { + if !FlatpakInPath() { + t.Skip("flatpak not in PATH") + } + + result := AnyFlatpakExists("com.nonexistent.flatpak", "app.zen_browser.zen", "com.another.nonexistent") + if !result { + t.Errorf("expected true when at least one flatpak exists") + } +} + +func TestAnyFlatpakExistsNoneExist(t *testing.T) { + if !FlatpakInPath() { + t.Skip("flatpak not in PATH") + } + + result := AnyFlatpakExists("com.nonexistent.flatpak1", "com.nonexistent.flatpak2") + if result { + t.Errorf("expected false when no flatpaks exist") + } +} + +func TestAnyFlatpakExistsNoFlatpak(t *testing.T) { + tempDir := t.TempDir() + t.Setenv("PATH", tempDir) + + result := AnyFlatpakExists("any.package.name", "another.package") + if result { + t.Errorf("expected false when flatpak not in PATH, got true") + } +} + +func TestAnyFlatpakExistsEmpty(t *testing.T) { + result := AnyFlatpakExists() + if result { + t.Errorf("expected false when no flatpaks specified") + } +}