diff --git a/core/internal/matugen/matugen.go b/core/internal/matugen/matugen.go index 23ee1a872..5f9421abf 100644 --- a/core/internal/matugen/matugen.go +++ b/core/internal/matugen/matugen.go @@ -500,23 +500,25 @@ func buildImportData(dank16JSON, image string) string { return fmt.Sprintf(`{"dank16": %s, "image": %s}`, dank16JSON, path) } +func userConfigSection(opts *Options) string { + if !opts.RunUserTemplates || opts.ConfigDir == "" { + return "[config]\n\n" + } + data, err := os.ReadFile(filepath.Join(opts.ConfigDir, "matugen", "config.toml")) + if err != nil { + return "[config]\n\n" + } + section := extractTOMLSection(string(data), "[config]", "[templates]") + if section == "" { + return "[config]\n\n" + } + return section + "\n" +} + func buildMergedConfig(opts *Options, cfgFile *os.File, tmpDir string) error { userConfigPath := filepath.Join(opts.ConfigDir, "matugen", "config.toml") - wroteConfig := false - if opts.RunUserTemplates { - if data, err := os.ReadFile(userConfigPath); err == nil { - configSection := extractTOMLSection(string(data), "[config]", "[templates]") - if configSection != "" { - cfgFile.WriteString(configSection) - cfgFile.WriteString("\n") - wroteConfig = true - } - } - } - if !wroteConfig { - cfgFile.WriteString("[config]\n\n") - } + cfgFile.WriteString(userConfigSection(opts)) baseConfigPath := filepath.Join(opts.ShellDir, "matugen", "configs", "base.toml") if data, err := os.ReadFile(baseConfigPath); err == nil { @@ -983,7 +985,32 @@ func runMatugenDryRun(opts *Options) (string, error) { return execDryRun(opts, newFlags) } +// matugen aborts on a config file it cannot deserialize, and without -c it +// reads the user's own ~/.config/matugen/config.toml, so a dry run gets a +// config of its own instead of inheriting whatever is there. +func writeDryRunConfig(opts *Options) (string, error) { + cfgFile, err := os.CreateTemp("", "matugen-dryrun-*.toml") + if err != nil { + return "", fmt.Errorf("failed to create dry-run config: %w", err) + } + defer cfgFile.Close() + + section := userConfigSection(opts) + if idx := strings.Index(section, "\n[templates"); idx != -1 { + section = section[:idx+1] + } + cfgFile.WriteString(section) + cfgFile.WriteString("[templates]\n") + return cfgFile.Name(), nil +} + func execDryRun(opts *Options, flags matugenFlags) (string, error) { + cfgPath, err := writeDryRunConfig(opts) + if err != nil { + return "", err + } + defer os.Remove(cfgPath) + var baseArgs []string switch opts.Kind { case "hex": @@ -991,7 +1018,7 @@ func execDryRun(opts *Options, flags matugenFlags) (string, error) { default: baseArgs = []string{opts.Kind, opts.Value} } - baseArgs = append(baseArgs, "-m", string(opts.Mode), "-t", opts.MatugenType, "--json", "hex", "--dry-run") + baseArgs = append(baseArgs, "-m", string(opts.Mode), "-t", opts.MatugenType, "-c", cfgPath, "--json", "hex", "--dry-run") baseArgs = appendContrastArg(baseArgs, opts.Contrast) if flags.isV4 { baseArgs = append(baseArgs, sourceSelectionArgs(opts.SourceMode, flags.supportsPrefer)...) diff --git a/core/internal/matugen/matugen_test.go b/core/internal/matugen/matugen_test.go index f0e304816..4acf43f51 100644 --- a/core/internal/matugen/matugen_test.go +++ b/core/internal/matugen/matugen_test.go @@ -1327,3 +1327,76 @@ func TestResolveSmartMode(t *testing.T) { }) } } + +func writeUserMatugenConfig(t *testing.T, configDir, content string) { + t.Helper() + dir := filepath.Join(configDir, "matugen") + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("failed to create matugen config dir: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, "config.toml"), []byte(content), 0o644); err != nil { + t.Fatalf("failed to write user config: %v", err) + } +} + +func TestWriteDryRunConfigAlwaysDeclaresConfigAndTemplates(t *testing.T) { + tests := []struct { + name string + userConfig string + runUserTemplates bool + wantContains []string + wantNotContains []string + }{ + { + name: "comment only user config", + userConfig: "# nothing here\n", + runUserTemplates: true, + wantNotContains: []string{"# nothing here"}, + }, + { + name: "user config section is carried over", + userConfig: "[config]\nfallback_color = \"#ff0000\"\n\n[templates.mine]\ninput_path = 'in'\noutput_path = 'out'\n", + runUserTemplates: true, + wantContains: []string{"fallback_color = \"#ff0000\""}, + wantNotContains: []string{"[templates.mine]"}, + }, + { + name: "user templates disabled", + userConfig: "[config]\nfallback_color = \"#ff0000\"\n", + wantNotContains: []string{"fallback_color"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + configDir := filepath.Join(t.TempDir(), "config") + writeUserMatugenConfig(t, configDir, tc.userConfig) + + opts := &Options{ConfigDir: configDir, RunUserTemplates: tc.runUserTemplates} + cfgPath, err := writeDryRunConfig(opts) + if err != nil { + t.Fatalf("writeDryRunConfig failed: %v", err) + } + defer os.Remove(cfgPath) + + data, err := os.ReadFile(cfgPath) + if err != nil { + t.Fatalf("failed to read dry-run config: %v", err) + } + + content := string(data) + assert.Contains(t, content, "[config]") + assert.Contains(t, content, "[templates]") + for _, want := range tc.wantContains { + assert.Contains(t, content, want) + } + for _, unwanted := range tc.wantNotContains { + assert.NotContains(t, content, unwanted) + } + }) + } +} + +func TestUserConfigSectionWithoutConfigDir(t *testing.T) { + assert.Equal(t, "[config]\n\n", userConfigSection(&Options{RunUserTemplates: true})) +}