Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
✨ feat: add frontmatter extraction examples and documentation
Closes #1359
  • Loading branch information
harehare committed Mar 4, 2026
commit a301eaae39a3564e61085380e9e0a1cdd31b26d1
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ mq 'select(.h1 || .h2)' example.html

# Extract specific cell from a Markdown table
mq '.[1][2] | to_text()' data.md

# Extract frontmatter metadata from markdown files:
import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README’s mq query uses if (.yaml): yaml::yaml_parse() | get(:title), which is parsed as (if (.yaml): yaml::yaml_parse()) | get(:title); get(:title) still runs for non-frontmatter nodes and relies on get(None, …) -> None. For a clearer copy/paste example, consider wrapping the piped expressions in do ... end inside the if (in addition to making it a runnable mq '…' file.md command).

Suggested change
import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)
mq 'import "yaml" | if (.yaml): do yaml::yaml_parse() | get(:title) end' file.md

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This README snippet is inside a ```sh code block where all other examples are shell commands, but the new frontmatter line isn’t a runnable command (it’s missing the mq '…' <file> wrapper/quoting). As written, it will fail if copied into a shell; please format it like the other examples (invoke `mq` with the query quoted and include a sample input file).

Suggested change
import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)
mq 'import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)' data.md

Copilot uses AI. Check for mistakes.
```

### Using with markitdown
Expand Down
10 changes: 10 additions & 0 deletions docs/books/src/start/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,3 +461,13 @@ $ mq -A 'let headers = count_by(fn(x): x | select(.h);)
| let anchor = downcase(replace(text, " ", "-"))
| if (!is_empty(text)): s"${indent}- [${text}](#${anchor})"
```

## Frontmatter Operations

Extract frontmatter metadata from markdown files:

```mq
import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In mq syntax, if (cond): expr1 | expr2 parses as (if (cond): expr1) | expr2, so get(:title) will run even when the condition is false and relies on get(None, …) -> None. If you intend both steps to be conditional (and to better match the control-flow docs), wrap the piped expressions in a do ... end block under the if.

Suggested change
import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)
import "yaml" | if (.yaml): do yaml::yaml_parse() | get(:title) end

Copilot uses AI. Check for mistakes.
```


3 changes: 3 additions & 0 deletions editors/vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def snake_to_camel(x):

# CSV parse
include "csv" | csv_parse("a,b,c\n1,2,3\n4,5,6", true) | csv_to_markdown_table()

# Extract Front Matter
import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example uses if (.yaml): yaml::yaml_parse() | get(:title). In mq, the pipe after the if is outside the if body ((if (.yaml): yaml::yaml_parse()) | get(:title)), so get(:title) is evaluated for all nodes and relies on get(None, …) -> None. Consider wrapping the pipeline in do ... end inside the if to make the conditional scope explicit and avoid unnecessary work.

Suggested change
import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)
import "yaml" | if (.yaml): do yaml::yaml_parse() | get(:title) end

Copilot uses AI. Check for mistakes.
`;

let client: lc.LanguageClient | null = null;
Expand Down
17 changes: 17 additions & 0 deletions packages/mq-playground/src/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,23 @@ end
- [Chapter2](Chapter2.md)
- [Chapter3](Chapter3.md)
- [Chapter4](Chapter4.md)
`,
isUpdate: false,
format: "markdown",
},
{
name: "Extract frontmatter data",
code: `import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)`,
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if (.yaml): yaml::yaml_parse() | get(:title) example is parsed as (if (.yaml): yaml::yaml_parse()) | get(:title), meaning get(:title) runs for non-frontmatter nodes too and relies on get(None, …) -> None. To make the example clearer (and ensure both steps are conditional), wrap the piped expressions in a do ... end block inside the if.

Suggested change
code: `import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)`,
code: `import "yaml" | if (.yaml): do yaml::yaml_parse() | get(:title) end`,

Copilot uses AI. Check for mistakes.
markdown: `---
title: "Sample Document"
author: "John Doe"
date: "2024-01-01"
---

# Sample Document

This is a sample document with frontmatter.

`,
isUpdate: false,
format: "markdown",
Expand Down