From b151522485dc0432cf0f997356bbec7035d089c9 Mon Sep 17 00:00:00 2001 From: cobyfrombrooklyn-bot Date: Wed, 25 Mar 2026 18:06:20 -0400 Subject: [PATCH] fix: preserve original filename when using --front-matter (#2613) When using --front-matter, yq creates a temporary file for the extracted YAML content but replaces the original filename in args with the temp file path. This caused the 'filename' operator to return the temp file path instead of the original filename. Added a filename alias mechanism: when front matter processing replaces the file path, it registers the original filename as an alias. The readDocuments and stream evaluator functions resolve aliases before setting candidateNode.filename. Fixes #2538 Co-authored-by: cobyfrombrooklyn-bot --- cmd/evaluate_all_command.go | 3 ++ cmd/evaluate_sequence_command.go | 3 ++ pkg/yqlib/front_matter_test.go | 49 ++++++++++++++++++++++++++++++++ pkg/yqlib/stream_evaluator.go | 1 + pkg/yqlib/utils.go | 24 ++++++++++++++++ 5 files changed, 80 insertions(+) diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go index 8bc5ac9b..821cf47b 100644 --- a/cmd/evaluate_all_command.go +++ b/cmd/evaluate_all_command.go @@ -101,12 +101,15 @@ func evaluateAll(cmd *cobra.Command, args []string) (cmdError error) { } if frontMatter != "" { + originalFilename := args[0] frontMatterHandler := yqlib.NewFrontMatterHandler(args[0]) err = frontMatterHandler.Split() if err != nil { return err } args[0] = frontMatterHandler.GetYamlFrontMatterFilename() + yqlib.SetFilenameAlias(args[0], originalFilename) + defer yqlib.ClearFilenameAliases() if frontMatter == "process" { reader := frontMatterHandler.GetContentReader() diff --git a/cmd/evaluate_sequence_command.go b/cmd/evaluate_sequence_command.go index efa9dfa5..4d2ecd72 100644 --- a/cmd/evaluate_sequence_command.go +++ b/cmd/evaluate_sequence_command.go @@ -122,12 +122,15 @@ func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) { if frontMatter != "" { yqlib.GetLogger().Debug("using front matter handler") + originalFilename := args[0] frontMatterHandler := yqlib.NewFrontMatterHandler(args[0]) err = frontMatterHandler.Split() if err != nil { return err } args[0] = frontMatterHandler.GetYamlFrontMatterFilename() + yqlib.SetFilenameAlias(args[0], originalFilename) + defer yqlib.ClearFilenameAliases() if frontMatter == "process" { reader := frontMatterHandler.GetContentReader() diff --git a/pkg/yqlib/front_matter_test.go b/pkg/yqlib/front_matter_test.go index 47547c51..65c17c50 100644 --- a/pkg/yqlib/front_matter_test.go +++ b/pkg/yqlib/front_matter_test.go @@ -108,6 +108,55 @@ yaml: doc fmHandler.CleanUp() } +func TestFrontMatterFilenamePreserved(t *testing.T) { + // Regression test for https://github.com/mikefarah/yq/issues/2538 + // When using --front-matter, the filename operator should return + // the original filename, not the path to the temporary file. + file := createTestFile(`--- +name: john +--- +Some content +`) + originalFilename := "/path/to/original/file.md" + + fmHandler := NewFrontMatterHandler(file) + err := fmHandler.Split() + if err != nil { + panic(err) + } + + tempFilename := fmHandler.GetYamlFrontMatterFilename() + + // Register the alias (as the command code does) + SetFilenameAlias(tempFilename, originalFilename) + defer ClearFilenameAliases() + + // Verify resolveFilename returns the original name + resolved := resolveFilename(tempFilename) + test.AssertResult(t, originalFilename, resolved) + + // Read documents using the temp file, verify they get the original filename + reader, err := readStream(tempFilename) + if err != nil { + panic(err) + } + decoder := NewYamlDecoder(ConfiguredYamlPreferences) + docs, err := readDocuments(reader, tempFilename, 0, decoder) + if err != nil { + panic(err) + } + + if docs.Len() == 0 { + t.Fatal("expected at least one document") + } + + firstDoc := docs.Front().Value.(*CandidateNode) + test.AssertResult(t, originalFilename, firstDoc.filename) + + tryRemoveTempFile(file) + fmHandler.CleanUp() +} + func TestFrontMatterSplitWithArray(t *testing.T) { file := createTestFile(`[1,2,3] --- diff --git a/pkg/yqlib/stream_evaluator.go b/pkg/yqlib/stream_evaluator.go index 0c9ad6dd..ad147894 100644 --- a/pkg/yqlib/stream_evaluator.go +++ b/pkg/yqlib/stream_evaluator.go @@ -76,6 +76,7 @@ func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, p } func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *ExpressionNode, printer Printer, decoder Decoder) (uint, error) { + filename = resolveFilename(filename) var currentIndex uint err := decoder.Init(reader) diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index 4424582a..2843e42d 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -9,6 +9,29 @@ import ( "os" ) +// filenameAliases maps real file paths to display names. +// Used by front matter handling to preserve original filenames +// when the actual content is read from temporary files. +var filenameAliases = map[string]string{} + +// SetFilenameAlias registers a display name for a file path so that +// the filename operator returns the original name instead of a temp path. +func SetFilenameAlias(realPath string, displayName string) { + filenameAliases[realPath] = displayName +} + +// ClearFilenameAliases removes all filename aliases. +func ClearFilenameAliases() { + filenameAliases = map[string]string{} +} + +func resolveFilename(filename string) string { + if alias, ok := filenameAliases[filename]; ok { + return alias + } + return filename +} + func readStream(filename string) (io.Reader, error) { var reader *bufio.Reader if filename == "-" { @@ -36,6 +59,7 @@ func ReadDocuments(reader io.Reader, decoder Decoder) (*list.List, error) { } func readDocuments(reader io.Reader, filename string, fileIndex int, decoder Decoder) (*list.List, error) { + filename = resolveFilename(filename) err := decoder.Init(reader) if err != nil { return nil, err