Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

This guide will teach you about mq, a command-line tool for querying and transforming Markdown files using a syntax inspired by jq. You’ll learn how to select specific elements, filter content, apply transformations, and compose these operations into powerful one-liners or reusable scripts.

Let’s get started.

Installation

The quickest way to install mq is via the install script:

curl -sSL https://mqlang.org/install.sh | bash

On macOS and Linux, you can also use Homebrew:

brew install mq

For other installation methods including Cargo, pre-built binaries, Docker, and more, see the Install page.

Your First Query

Once installed, let’s try a simple query. Save this file as hello.md:

# Hello

Welcome to **mq**.

## Getting Started

Install it, then run your first query.

## Features

- Select headings
- Filter nodes
- Transform content

Now run mq to extract all headings:

$ mq '.h' hello.md
# Hello
## Getting Started
## Features

Use to_text() to get just the heading text:

$ mq '.h | to_text' hello.md
Hello
Getting Started
Features

You can narrow it down to a specific level, for example only h2:

$ mq '.h2 | to_text' hello.md
Getting Started
Features

Queries are composable with |, just like a Unix pipeline.

What’s Next

With the basics covered, the Getting Started section walks through installation options, syntax, and common patterns. When you’re ready to look up specific behavior, the Reference covers every selector, operator, and built-in function in detail.

Getting Started

Getting Started

This section guides you through the installation of mq.

Install

Quick Install

curl -sSL https://mqlang.org/install.sh | bash
# Install the debugger
curl -sSL https://mqlang.org/install.sh | bash -s -- --with-debug

The installer will:

  • Download the latest mq binary for your platform
  • Install it to ~/.local/bin/
  • Update your shell profile to add mq to your PATH

Cargo

# Install from crates.io
cargo install mq-run
# Install from Github
cargo install --git https://github.com/harehare/mq.git mq-run --tag v0.6.0
# Latest Development Version
cargo install --git https://github.com/harehare/mq.git mq-run --bin mq
# Install the debugger
cargo install --git https://github.com/harehare/mq.git mq-run --bin mq-dbg --features="debugger"
# Install using binstall
cargo binstall [email protected]

Binaries

You can download pre-built binaries from the GitHub releases page:

# macOS (Apple Silicon)
curl -L https://github.com/harehare/mq/releases/download/v0.6.0/mq-aarch64-apple-darwin -o /usr/local/bin/mq && chmod +x /usr/local/bin/mq
# Linux x86_64
curl -L https://github.com/harehare/mq/releases/download/v0.6.0/mq-x86_64-unknown-linux-gnu -o /usr/local/bin/mq && chmod +x /usr/local/bin/mq
# Linux arm64
curl -L https://github.com/harehare/mq/releases/download/v0.6.0/mq-aarch64-unknown-linux-gnu -o /usr/local/bin/mq && chmod +x /usr/local/bin/mq
# Windows (PowerShell)
Invoke-WebRequest -Uri https://github.com/harehare/mq/releases/download/v0.6.0/mq-x86_64-pc-windows-msvc.exe -OutFile "$env:USERPROFILE\bin\mq.exe"

Homebrew

# Using Homebrew (macOS and Linux)
$ brew install mq

Docker

$ docker run --rm ghcr.io/harehare/mq:0.6.0

mq-lsp (Language Server)

The mq Language Server provides IDE features like completion, hover, and diagnostics for mq query files.

Quick Install

curl -sSL https://mqlang.org/install_lsp.sh | bash

Cargo

# Install from crates.io
cargo install mq-lsp
# Install from Github
cargo install --git https://github.com/harehare/mq.git mq-lsp --tag v0.6.0
# Latest Development Version
cargo install --git https://github.com/harehare/mq.git mq-lsp
# Install using binstall
cargo binstall [email protected]

Binaries

You can download pre-built binaries from the GitHub releases page:

# macOS (Apple Silicon)
curl -L https://github.com/harehare/mq/releases/download/v0.6.0/mq-lsp-aarch64-apple-darwin -o /usr/local/bin/mq-lsp && chmod +x /usr/local/bin/mq-lsp
# Linux x86_64
curl -L https://github.com/harehare/mq/releases/download/v0.6.0/mq-lsp-x86_64-unknown-linux-gnu -o /usr/local/bin/mq-lsp && chmod +x /usr/local/bin/mq-lsp
# Linux arm64
curl -L https://github.com/harehare/mq/releases/download/v0.6.0/mq-lsp-aarch64-unknown-linux-gnu -o /usr/local/bin/mq-lsp && chmod +x /usr/local/bin/mq-lsp
# Windows (PowerShell)
Invoke-WebRequest -Uri https://github.com/harehare/mq/releases/download/v0.6.0/mq-lsp-x86_64-pc-windows-msvc.exe -OutFile "$env:USERPROFILE\bin\mq-lsp.exe"

Visual Studio Code Extension

You can install the VSCode extension from the Visual Studio Marketplace.

Neovim

You can install the Neovim plugin by following the instructions in the mq.nvim README.

GitHub Actions

You can use mq in your GitHub Actions workflows with the Setup mq action:

steps:
  - uses: actions/checkout@v4
  - uses: harehare/setup-mq@v1
  - run: mq '.code' README.md

MCP (Model Context Protocol) server

mq supports an MCP server for integration with LLM applications.

See the MCP documentation for more information.

Python

You can use mq in Python through the markdown-query package:

# Install from PyPI
$ pip install markdown-query

npm

You can use mq in npm through the mq-web package:

$ npm i mq-web

Web crawler

Quick Install

curl -sSL https://mqlang.org/install_crawler.sh | bash

The installer will:

  • Download the latest mq-crawl binary for your platform
  • Install it to ~/.local/bin/
  • Verify the checksum of the downloaded binary
  • Update your shell profile to add mq-crawl to your PATH

Homebrew

brew install harehare/tap/mq-crawl

Cargo

cargo install mq-crawler

See the Web Crawler page for usage details.

Development

Prerequisites

Setting up the development environment

Clone the repository:

git clone https://github.com/harehare/mq.git
cd mq

Install development dependencies:

# Using cargo
cargo install just wasm-pack

Or if you prefer using asdf:

# Using asdf
asdf install

Common development tasks

Here are some useful commands to help you during development:

# Run the CLI with the provided arguments
just run '.code'

# Run formatting, linting and all tests
just test-all

# Run formatter and linter
just lint

# Build the project in release mode
just build

# Update documentation
just docs

Check the just --list for more available commands and build options.

Syntax Highlighting

Using bat

bat is a cat clone with syntax highlighting and Git integration. You can use mq’s Sublime syntax file to enable syntax highlighting for mq files in bat.

Setting up mq syntax highlighting

  1. Create the bat syntax directory if it doesn’t exist:
mkdir -p "$(bat --config-dir)/syntaxes"
  1. Copy the mq syntax file:
# Clone the mq repository or download mq.sublime-syntax
curl -o "$(bat --config-dir)/syntaxes/mq.sublime-syntax" \
  https://raw.githubusercontent.com/harehare/mq/main/assets/mq.sublime-syntax
  1. Rebuild bat’s cache:
bat cache --build

Usage

Now you can use bat to display mq files with syntax highlighting:

# View an mq file with syntax highlighting
bat query.mq

Example

Create a sample mq file:

cat > example.mq << 'EOF'
# This is a comment
def greet(name):
  s"Hello, ${name}!"
end

.h | .text | greet("World")
EOF

View it with syntax highlighting:

bat example.mq

Editor Support

In addition to bat, mq syntax highlighting is available for:

Playground

An Online Playground is available, powered by WebAssembly.

Interactive TUI

The Text-based User Interface (TUI) provides an interactive way to explore and query Markdown files directly in your terminal.

Quick Install

curl -fsSL https://raw.githubusercontent.com/harehare/mq-tui/main/bin/install.sh | bash
$ mq tui file.md

TUI Features

  • Interactive Querying: Enter and edit queries in real-time with immediate feedback
  • Detail View: Examine the structure of selected markdown nodes in depth
  • Navigation: Browse through query results with keyboard shortcuts
  • Query History: Access and reuse previous queries

TUI Key Bindings

KeyAction
: (colon)Enter query mode
EnterExecute query
Esc / qExit query mode / Exit app
/k, /jNavigate results
dToggle detail view
? / F1Show help screen
Ctrl+lClear query
PgUp/PgDnPage through results
Home/EndJump to first/last result

Repository

The source code and further documentation for mqt are available on GitHub:

https://github.com/harehare/mqt

Web crawler

mq-crawler is a web crawler that fetches HTML content from websites, converts it to Markdown format, and processes it with mq queries.

Key Features

  • HTML to Markdown conversion: Automatically converts crawled HTML pages to clean Markdown
  • robots.txt compliance: Respects robots.txt rules for ethical web crawling
  • mq-lang integration: Processes content with mq-lang queries for filtering and transformation
  • Configurable crawling: Customizable delays, domain restrictions, and link discovery
  • Flexible output: Save to files or output to stdout
  • Headless Chrome: Built-in headless Chrome for JavaScript-heavy sites (no external server needed)
  • WebDriver support: Browser-based crawling via Selenium WebDriver
  • Domain filtering: Restrict crawling to specific domains

Installation

Quick Install

curl -sSL https://mqlang.org/install_crawler.sh | bash

The installer will:

  • Download the latest mq-crawl binary for your platform
  • Install it to ~/.local/bin/
  • Verify the checksum of the downloaded binary
  • Update your shell profile to add mq-crawl to your PATH

After installation, restart your terminal or source your shell profile, then verify:

mq-crawl --version

Homebrew

brew install harehare/tap/mq-crawl

Cargo

cargo install mq-crawler

Binaries

You can download pre-built binaries from the GitHub releases page.

Usage

mq-crawl [OPTIONS] <URL>

Options

OptionDescriptionDefault
-o, --output <OUTPUT>Directory to save markdown files (stdout if not specified)stdout
-d, --crawl-delay <SECONDS>Delay between requests in seconds1
-c, --concurrency <N>Number of concurrent workers1
--depth <DEPTH>Maximum crawl depth (0 = start URL only)unlimited
-q, --mq-query <QUERY>mq-lang query for processing content
--robots-path <PATH>Custom robots.txt file path
--allowed-domains <DOMAINS>Comma-separated list of extra domains to crawl; the start URL’s domain is always includedstart domain only
--headlessUse built-in headless Chrome (Chrome/Chromium must be installed)
--chrome-path <PATH>Path to Chrome/Chromium executable (requires --headless)auto-detect
-U, --webdriver-url <URL>External WebDriver URL for browser-based crawling
--page-load-timeout <SECONDS>Timeout for loading a single page30
--script-timeout <SECONDS>Timeout for executing scripts on the page10
--implicit-timeout <SECONDS>Timeout for element finding5
--extract-scripts-as-code-blocksExtract <script> tags as code blocks
--generate-front-matterGenerate YAML front matter from page metadata
--use-title-as-h1Use the HTML <title> as the first H1 heading
-f, --format <FORMAT>Output format: text or jsontext

Examples

# Basic crawling to stdout
mq-crawl https://example.com

# Save to directory with custom delay
mq-crawl -o ./output -d 2 https://example.com

# Limit crawl depth and use concurrent workers
mq-crawl --depth 2 -c 3 https://example.com

# Process with mq-lang query
mq-crawl -q '.h | select(contains("News"))' https://example.com

# Extract code blocks from a docs site
mq-crawl -q '.code' https://docs.example.com

Domain Filtering

By default, only the start URL’s domain is crawled. Use --allowed-domains to include additional domains:

# Also crawl docs.example.com and blog.example.com
# The start URL's domain is always included automatically
mq-crawl --allowed-domains docs.example.com,blog.example.com https://example.com

Headless Chrome

For JavaScript-heavy sites, use the built-in headless Chrome without an external server:

# Use built-in headless Chrome (Chrome or Chromium must be installed)
mq-crawl --headless https://spa-example.com

# Specify a custom Chrome/Chromium executable path
mq-crawl --headless --chrome-path /usr/bin/chromium https://spa-example.com

WebDriver

Alternatively, use an external Selenium WebDriver server:

# Start Selenium server first
# docker run -d -p 4444:4444 selenium/standalone-chrome

# Crawl with WebDriver
mq-crawl -U http://localhost:4444 https://spa-example.com

# Custom timeouts
mq-crawl -U http://localhost:4444 \
  --page-load-timeout 60 \
  --script-timeout 30 \
  --implicit-timeout 10 \
  https://spa-example.com

HTML to Markdown Options

# Generate YAML front matter with metadata
mq-crawl --generate-front-matter https://example.com

# Use page title as H1 heading
mq-crawl --use-title-as-h1 https://example.com

# Extract <script> tags as code blocks
mq-crawl --extract-scripts-as-code-blocks https://example.com

# Combine options
mq-crawl --generate-front-matter --use-title-as-h1 -o ./docs https://example.com

Output Formats

# Output as JSON
mq-crawl --format json https://example.com

# Output as plain text (default)
mq-crawl --format text https://example.com

MCP

The mq MCP server enables integration with AI applications that support the Model Context Protocol (MCP). This server provides tools for processing Markdown content using mq queries.

Overview

The MCP server exposes four main tools:

  • html_to_markdown - Converts HTML to Markdown and applies mq queries
  • extract_markdown - Extracts content from Markdown using mq queries
  • available_functions - Lists available mq functions
  • available_selectors - Lists available mq selectors

Configuration

Claude Desktop

Add the following to your Claude Desktop configuration file:

{
  "mcpServers": {
    "mq": {
      "command": "/path/to/mq",
      "args": ["mcp"]
    }
  }
}

Claude Code

$ claude mcp add mq-mcp -- mq mcp

VS Code

Add the following to your VS Code settings:

{
  "mcp": {
    "servers": {
      "mq-mcp": {
        "type": "stdio",
        "command": "/path/to/mq",
        "args": ["mcp"]
      }
    }
  }
}

Replace /path/to/mq with the actual path to your mq binary.

Usage

Converting HTML to Markdown

The html_to_markdown tool converts HTML content to Markdown format and applies an optional mq query:

html_to_markdown({
  "html": "<h1>Title</h1><p>Content</p>",
  "query": ".h1"
})

Extracting from Markdown

The extract_markdown tool processes Markdown content with mq queries:

extract_markdown({
  "markdown": "# Title\n\nContent",
  "query": ".h1"
})

Getting Available Functions

The available_functions tool returns all available mq functions:

available_functions()

Returns JSON with function names, descriptions, parameters, and examples.

Getting Available Selectors

The available_selectors tool returns all available mq selectors:

available_selectors()

Returns JSON with selector names, descriptions, and parameters.

Query Examples

Common mq queries you can use with the MCP tools:

  • .h1 - Select all h1 headings
  • select(.code.lang == "js") - Select JavaScript code blocks
  • .text - Extract all text content
  • select(.h1, .h2) - Select h1 and h2 headings
  • select(not(.code)) - Select everything except code blocks

Debugger

The mq debugger allows you to step through execution, set breakpoints, and inspect the state of your mq programs during runtime. This is particularly useful for debugging complex queries and understanding how data flows through your transformations.

Installation

To use the debugger, you need to install mq with the debugger feature enabled.

Quick Install

curl -sSL https://mqlang.org/install.sh | bash -s -- --with-debug

Cargo

You can do this by building from source:

cargo install --git https://github.com/harehare/mq.git mq-run --bin mq-dbg

Alternatively, if a prebuilt binary is available for your platform, download it from the releases page and ensure it is in your PATH.

Homebrew

If you use Homebrew, you can install the debugger-enabled mq with:

brew install harehare/tap/mq-dbg

Getting Started

The debugger is available through the mq-dbg binary when the debugger feature is enabled.

# Enable debugging for an mq script
mq-dbg -f your-script.mq input.md

Debugger Interface

Once the debugger starts, you’ll see a prompt (mqdbg) where you can enter debugging commands. The debugger will automatically display the current source code location with line numbers, highlighting the current execution point.

   10| def process_headers() {
=> 11|   . | select(.type == "heading")
   12|     | map(.level)
   13| }
(mqdbg)

Available Commands

The debugger supports the following commands:

CommandAliasDescription
stepsStep into the next expression, diving into function calls
nextnStep over the current expression, skipping over function calls
finishfRun until the current function returns
continuecContinue normal execution until the next breakpoint

Breakpoint Commands

CommandAliasDescription
breakpoint [line]b [line]Set a breakpoint at the specified line number
breakpointbList all active breakpoints
clear [id]cl [id]Clear a specific breakpoint by ID
clearclClear all breakpoints

Inspection Commands

CommandAliasDescription
infoiDisplay current environment variables and context
listlShow source code around the current execution point
long-listllShow the entire source code with line numbers
backtracebtPrint the current call stack

Control Commands

CommandAliasDescription
help-Display help information for all commands
quitqQuit the debugger and exit

Setting Breakpoints

You can set breakpoints in several ways:

Interactive Breakpoints

You can set breakpoints interactively from the debugger prompt:

(mqdbg) breakpoint 15

(mqdbg) breakpoint
Breakpoints:
  [1] 15:10 (enabled)

Programmatic Breakpoints

You can also set breakpoints directly in your mq code using the breakpoint() function:

def process_data(items) {
   breakpoint()  # Execution will pause here when debugger is attached
   | items | filter(fn(item): item == "test")
}

When the debugger encounters a breakpoint() function call during execution, it will automatically pause and enter interactive debugging mode.

Note: The breakpoint() function only has an effect when running under the debugger (mq-dbg). In normal execution (mq), it is ignored and has no impact on performance.

External Subcommands

You can extend mq with custom subcommands by placing executable files starting with mq- in ~/.local/bin/ or anywhere in your PATH.

Command Resolution

When you run mq <subcommand>, mq searches for an executable named mq-<subcommand> in the following order:

  1. ~/.local/bin/ directory
  2. Directories in PATH

The first match found is used.

Listing Available Subcommands

Use the --list flag to see all available subcommands:

mq --list

This makes it easy to add your own tools and workflows to mq without modifying the core binary.

External Tools

The following external tools are available to extend mq’s functionality:

  • mq-check - A syntax and semantic checker for mq files.
  • mq-conv - A CLI tool for converting various file formats to Markdown.
  • mq-crawler - A web crawler that extracts structured data from websites and outputs it in Markdown format.
  • mq-docs - A documentation generator for mq functions, macros, and selectors.
  • mq-fmt- Formatter for mq query language (.mq) files.
  • mq-http - A lightweight HTTP server that executes mq scripts for each request.
  • mq-lsp - Language Server Protocol (LSP) implementation for mq query files, providing IDE features like completion, hover, and diagnostics.
  • mq-mcp - Model Context Protocol (MCP) server implementation for AI assistants.
  • mq-serve - A browser-based Markdown viewer with mq query support.
  • mq-task - Task runner using mq for Markdown-based task definitions.
  • mq-tui - Terminal User Interface (TUI) for interactive mq query.
  • mq-update - Update mq binary to the latest version.
  • mq-view - Viewer for Markdown content.

AI Assistant Integration

  • MCP: mq-mcp provides a Model Context Protocol server, enabling mq to be used from any MCP-compatible AI assistant.
  • Skill: The processing-markdown skill adds mq-aware assistance directly to your AI coding workflow.

Language Bindings

mq provides official language bindings for several programming languages, allowing you to integrate mq’s Markdown processing capabilities directly into your applications.

Available Bindings

LanguageRepository
Elixirmq_elixir
Pythonmq-python
Rubymq-ruby
Javamq-java
Gomq-go

Example

This page demonstrates practical examples of mq queries for common Markdown processing tasks. Each example includes the query, explanation, and typical use cases.

Basic Element Selection

Select All Headings

Extract all headings from a markdown document:

.h

Input example:

# Main Title
## Section 1
### Subsection 1.1
## Section 2

Output: Returns all heading elements with their levels and text.

Extract Specific Table Row

Extract the second row from a markdown table:

.[1][]

Input example:

| Name  | Age | City |
| ----- | --- | ---- |
| Alice | 30  | NYC  |
| Bob   | 25  | LA   |

Output: Returns ["Bob", "25", "LA"]

Extract Specific List

Extract the second list from the document:

.[1]

Code Block Operations

Exclude Code Blocks

Filter out all code blocks from a document, keeping only prose content:

select(!.code)

Input example:

This is a paragraph.

```js
console.log("code");
```

Another paragraph.

Output: Returns only the paragraph elements, excluding the code block.

Extract JavaScript Code Blocks

Select only code blocks with a specific language:

select(.code.lang == "js")

Input example:

```js
const x = 1;
```

```python
x = 1
```

```js
const y = 2;
```

Output: Returns only the two JavaScript code blocks.

Extract Language Names

Get a list of all programming languages used in code blocks:

.code.lang

Example output: ["js", "python", "rust", "bash"]

Extract MDX Components

Select all MDX components (JSX-like elements in Markdown):

select(is_mdx())

Input example:

Regular paragraph.

<CustomComponent prop="value" />

Another paragraph.

<AnotherComponent>
  Content
</AnotherComponent>

Output: Returns only the MDX component elements.

Get all URLs from markdown links:

.link.url

Input example:

Check out [mq](https://mqlang.org) and [GitHub](https://github.com).

Example output: ["https://mqlang.org", "https://github.com"]

Advanced Markdown Processing

Generate Table of Contents

Create a hierarchical table of contents from headings:

.h
| let link = to_link("#" + to_text(self), to_text(self), "")
| let level = .h.depth
| if (!is_none(level)): to_md_list(link, level - 1)

Input example:

# Introduction
## Getting Started
### Installation
## Usage

Output:

- [Introduction](#introduction)
  - [Getting Started](#getting-started)
    - [Installation](#installation)
  - [Usage](#usage)

Generate XML Sitemap

Create an XML sitemap from markdown files:

def sitemap(item, base_url):
    let path = replace(to_text(item), ".md", ".html")
    | let loc = base_url + path
    | s"<url>
  <loc>${loc}</loc>
  <priority>1.0</priority>
  </url>"
end

Usage example:

$ mq 'sitemap(__FILE__, "https://example.com")' docs/**/*.md

Example output:

<url>
  <loc>https://example.com/docs/intro.html</loc>
  <priority>1.0</priority>
</url>

Section Operations

The section module provides functions for splitting and filtering Markdown documents by section. There are three ways to use it:

StyleSyntaxNotes
importimport "section" then section::fn()Namespaced — recommended
includeinclude "section" then fn()No namespace prefix
-A flagmq -A 'section::fn()'Aggregate mode: processes all nodes at once

Note: Section functions need all document nodes at once. Use -A on the command line, or nodes in inline queries.

Extract Sections by Title

-A flag (command line):

$ mq -A 'section::section("Installation")' README.md

import + nodes (inline query or script):

import "section"
| nodes
| section::section("Installation")

include (no namespace prefix):

include "section"
| nodes
| section("Installation")

Section objects are automatically expanded to Markdown nodes in CLI output, so collect() is not needed.

Note (code usage): When using the section module from Rust or other code (not the CLI), section objects are plain dicts and must be explicitly converted with section::collect():

import "section"
| nodes
| section::section("Installation")
| section::collect()

Input example:

# Introduction

Welcome to the project.

## Installation

Run the following command.

## Usage

Use the tool like this.

Output:

## Installation

Run the following command.

Extract Body Only

Use bodies() to get section content without the header:

$ mq -A 'section::section("Installation") | section::bodies() | first()' README.md

Output: Returns only the body nodes of the “Installation” section, without the ## header.

Filter by Heading Level

Use by_level() to filter sections by heading level. Accepts a number or a range:

# h2 sections only
$ mq -A 'section::sections() | section::by_level(2)' README.md

# h1 and h2 sections (1..2 includes both)
$ mq -A 'section::sections() | section::by_level(1..2)' README.md

Input example:

# Chapter 1

Intro.

## Section 1.1

Detail.

# Chapter 2

Content.

by_level(1) output:

# Chapter 1

Intro.

# Chapter 2

Content.

Split Document by Header Level

Split a document into sections at a specific heading level and flatten back to Markdown:

$ mq -A 'section::split(2) | section::collect()' README.md

Or with nodes:

import "section"
| nodes
| section::split(2)
| section::collect()

Generate Table of Contents from Sections

$ mq -A 'section::sections() | section::toc()' README.md

Input example:

# Introduction

## Getting Started

### Prerequisites

## Advanced Usage

Output: ["- Introduction", " - Getting Started", " - Prerequisites", " - Advanced Usage"]

Filter Sections with Content

Filter sections that have content beyond the header:

$ mq -A 'section::sections() | filter(fn(s): section::has_content(s);) | section::titles()' README.md

Input example:

# Introduction

Welcome to the project.

## Empty Section

## Usage

Use the tool like this.

Output: ["Introduction", "Usage"]

Table Operations

The table module provides functions for extracting and transforming Markdown tables.

Note: Table functions need all document nodes at once. Use -A on the command line, or nodes in inline queries. Unlike the section module, import "table" must be written explicitly.

Extract Tables

-A flag (command line):

$ mq -A 'import "table" | table::tables()' README.md

import + nodes (inline query or script):

import "table"
| nodes
| table::tables()

Table objects are automatically expanded to Markdown nodes in CLI output, so to_markdown() is not needed.

Note (code usage): When using the table module from Rust or other code (not the CLI), table objects are plain dicts and must be explicitly converted with table::to_markdown():

import "table"
| nodes
| table::tables()
| table::to_markdown()

Input example:

| Name  | Age |
| ----- | --- |
| Alice | 30  |
| Bob   | 25  |

Output:

| Name  | Age |
| ----- | --- |
| Alice | 30  |
| Bob   | 25  |

Add a Row to a Table

$ mq -A 'import "table" | table::tables() | first() | table::add_row(["Charlie", "35"])' README.md

Input example:

| Name  | Age |
| ----- | --- |
| Alice | 30  |

Output:

| Name    | Age |
| ------- | --- |
| Alice   | 30  |
| Charlie | 35  |

Convert Table to CSV

$ mq -A 'import "table" | table::tables() | first() | table::to_csv()' README.md

Output: Returns the table as a CSV string.

Custom Functions and Programming

Define Custom Function

Create reusable functions for complex transformations:

def snake_to_camel(x):
  let words = split(x, "_")
  | foreach (word, words):
      let first_char = upcase(first(word))
      | let rest_str = downcase(slice(word, 1, len(word)))
      | s"${first_char}${rest_str}";
  | join("")
end
| snake_to_camel("hello_world")

Example input: "user_name" Example output: "UserName"

Map Over Arrays

Transform each element in an array:

map([1, 2, 3, 4, 5], fn(x): x + 1;)

Example output: [2, 3, 4, 5, 6]

Filter Arrays

Select elements that meet a condition:

filter([5, 15, 8, 20, 3], fn(x): x > 10;)

Example output: [15, 20]

Fold Arrays

Combine array elements into a single value:

fold([1, 2, 3, 4], 0, fn(acc, x): acc + x;)

Example output: 10

File Processing

CSV to Markdown Table

Convert CSV data to a formatted markdown table:

$ mq 'include "csv" | csv_parse(true) | csv_to_markdown_table()' example.csv

Use case: Convert spreadsheet data to markdown format for documentation. The csv_parse(true) treats the first row as headers.

Input example (example.csv):

Name,Age,City
Alice,30,NYC
Bob,25,LA

Example output:

| Name  | Age | City |
| ----- | --- | ---- |
| Alice | 30  | NYC  |
| Bob   | 25  | LA   |

Merge Multiple Files

Combine multiple markdown files with file path separators:

$ mq -S 's"\n${__FILE__}\n"' 'identity()' docs/books/**/**.md

The -S flag adds a separator between files, and __FILE__ is a special variable containing the current file path.

Example output:

docs/intro.md

# Introduction
...

docs/usage.md

# Usage
...

Process Files in Parallel

Process large numbers of files efficiently:

$ mq -P 5 '.h1' docs/**/*.md

LLM Workflows

Extract Context for LLM Prompts

Extract specific sections to create focused context for LLM inputs:

select(.h || .code) | self[:10]

Example: Extract first 10 sections with headings or code for a code review prompt.

Document Statistics

$ mq -A 'let headers = count_by(fn(x): x | select(.h);)
| let paragraphs = count_by(fn(x): x | select(.text);)
| let code_blocks = count_by(fn(x): x | select(.code);)
| let links = count_by(fn(x): x | select(.link);)
| s"Headers: ${headers}, Paragraphs: ${paragraphs}, Code: ${code_blocks}, Links: ${links}"'' docs/books/**/**.md

Generate Documentation Index

.h
| let level = .h.level
| let text = to_text(self)
| let indent = repeat("  ", level - 1)
| let anchor = downcase(replace(text, " ", "-"))
| if (!is_empty(text)): s"${indent}- [${text}](#${anchor})"

Frontmatter Operations

Extract frontmatter metadata from markdown files:

.yaml | frontmatter()

Sample Projects

The following projects are real-world examples built entirely with mq, demonstrating its expressive power for complex tasks.

Interpreters

  • lisp.mq — A Lisp interpreter implemented in mq
  • bf.mq — A Brainfuck interpreter implemented in mq

Parsers

  • kdl.mq — A KDL document language parser written in mq
  • json5.mq — A JSON5 parser written in mq
  • regex.mq — A regular expression engine written in mq

Functional Programming

  • monad.mq — Monadic abstractions implemented in mq

Simulations

Reference

Reference

This is a reference documentation for the mq.

CLI

The mq command-line interface provides tools for querying and manipulating markdown content. Below is the complete reference for all available commands and options.

Usage: mq [OPTIONS] [QUERY OR FILE] [FILES]... [COMMAND]

Commands:
  repl  Start a REPL session for interactive query execution
  help  Print this message or the help of the given subcommand(s)

Arguments:
  [QUERY OR FILE]  
  [FILES]...       

Options:
  -A, --aggregate
          Aggregate all input files/content into a single array
  -f, --from-file
          load filter from the file
  -I, --input-format <INPUT_FORMAT>
          Set input format [possible values: markdown, mdx, html, text, null, raw, bytes, cbor, csv, hcl, json, psv, toml, toon, tsv, xml, yaml]
  -L, --directory <MODULE_DIRECTORIES>
          Search modules from the directory
  -M, --module-names <MODULE_NAMES>
          Load additional modules from specified files
  -m, --import-module-names <IMPORT_MODULE_NAMES>
          Import modules by name, making them available as `name::fn()` in queries
      --args <NAME> <VALUE>
          Sets a named string argument. NAME is accessible directly in queries, and also via ARGS."named" when --args or --argv is given
      --rawfile <NAME> <FILE>
          Sets file contents that can be referenced at runtime
      --stream
          Enable streaming mode for processing large files line by line
  -F, --output-format <OUTPUT_FORMAT>
          Set output format [default: markdown] [possible values: markdown, html, text, json, table, grep, raw, none]
  -U, --update
          Update the input markdown (aliases: -i, --in-place, --inplace)
      --unbuffered
          Unbuffered output
      --list-style <LIST_STYLE>
          Set the list style for markdown output [default: dash] [possible values: dash, plus, star]
      --link-title-style <LINK_TITLE_STYLE>
          Set the link title surround style for markdown output [default: double] [possible values: double, single, paren]
      --link-url-style <LINK_URL_STYLE>
          Set the link URL surround style for markdown links [default: none] [possible values: none, angle]
  -S, --separator <QUERY>
          Specify a query to insert between files as a separator
  -o, --output <FILE>
          Output to the specified file
  -C, --color-output
          Colorize markdown output
  -B, --before-context <NUM>
          Show NUM nodes before each match. Only effective with -F grep
      --after-context <NUM>
          Show NUM nodes after each match. Only effective with -F grep
      --context <NUM>
          Show NUM nodes before and after each match. Only effective with -F grep
      --list
          List all available subcommands (built-in and external)
      --doc
          Use the built-in reference document as input instead of a file
  -P <PARALLEL_THRESHOLD>
          Number of files to process before switching to parallel processing [default: 10]
      --argv [<ARGV>...]
          Positional string arguments, available as ARGS."positional" in queries
  -O, --optimize-level <OPTIMIZE_LEVEL>
          Optimization level for AST transformations (none = no changes, basic = constant folding and dead-branch elimination, full = all passes) [default: none] [possible values: none, basic, full]
  -h, --help
          Print help
  -V, --version
          Print version

# Examples

mq 'query' file.md
mq -f 'file' file.md        # read query from file
mq repl                     # start a REPL session

# Auto-parsing by file extension or -I flag

mq automatically imports the matching module based on the file extension.
Use -I <format> to force a specific format:

.cbor / -I cbor  import "cbor" | cbor::cbor_parse()  (reads as bytes)
.csv  / -I csv   import "csv"  | csv::csv_parse(true)
.hcl  / -I hcl   import "hcl"  | hcl::hcl_parse()
.json / -I json  import "json" | json::json_parse()
.psv  / -I psv   import "csv"  | csv::psv_parse(true)
.toml / -I toml  import "toml" | toml::toml_parse()
.toon / -I toon  import "toon" | toon::toon_parse()
.tsv  / -I tsv   import "csv"  | csv::tsv_parse(true)
.xml  / -I xml   import "xml"  | xml::xml_parse()
.yaml / -I yaml  import "yaml" | yaml::yaml_parse()

Use -I raw   to disable auto-parsing and receive the raw string.
Use -I bytes to read input as raw bytes without parsing.

# Passing arguments to queries (ARGS)

When --args or --argv is given, ARGS = {"positional": [...], "named": {...}}

mq -I null 'name' --args name Alice
mq -I null 'ARGS | ."named"' --args name Alice
# => {"name": "Alice"}

mq -I null 'ARGS | ."positional"' --argv x y z  # must come after query and files
# => ["x", "y", "z"]

mq -I null 'ARGS' file.md --args name Alice --argv x y z
# => {"positional": ["x","y","z"], "named": {"name": "Alice"}}

Types and Values

Values

  • 42 (a number)
  • "Hello, world!" (a string)
  • b"abc" (a bytes literal)
  • :value (a symbol)
  • [1, 2, 3], array(1, 2, 3) (an array)
  • {"a": 1, "b": 2, "c": 3}, dict(["a", 1], ["b", 2], ["c", 3]) (a dictionary)
  • true, false (a boolean)
  • None

Types

TypeDescriptionExamples
NumberRepresents numeric values.1, 3.14, -42
StringRepresents sequences of characters, including Unicode code points and escape sequences in the form of \{0x000}."hello", "123", "😊", "\u{1F600}"
BytesRepresents a raw byte sequence. Written with a b prefix. Only ASCII characters are allowed unescaped.b"abc", b"\xf0\x9f\x99\x82", b""
SymbolRepresents immutable, interned identifiers prefixed with :. Used for constant values and keys.:value, :success, :error, :ok
BooleanRepresents truth values.true, false
ArrayRepresents ordered collections of values.[1, 2, 3], array(1, 2, 3)
DictRepresents key-value mappings (dictionaries).{"a": 1, "b": 2}, dict(["a", 1], ["b", 2])
FunctionRepresents executable code.def foo(): 42; let name = def foo(): 42;

Byte String Literals

Byte string literals use the b"..." syntax and represent raw sequences of bytes (u8 values).

b"hello"            # 5-byte sequence [104, 101, 108, 108, 111]
b"\xf0\x9f\x99\x82" # 4-byte emoji encoded as raw bytes
b""                 # empty byte sequence

Allowed characters

Only ASCII characters (code points 0–127) may appear unescaped inside a byte literal. Non-ASCII characters such as é or 😊 must be written using \xNN hex escapes:

# Correct — use \xNN for non-ASCII bytes
b"\xc3\xa9"    # UTF-8 encoding of 'é' (2 bytes: 0xc3, 0xa9)

# Wrong — non-ASCII characters are not accepted in b"..."
# b"é"          ← syntax error; use \xNN escapes instead

Supported escape sequences

EscapeByte value
\\0x5c (backslash)
\"0x22 (double quote)
\n0x0a (newline)
\r0x0d (carriage return)
\t0x09 (tab)
\00x00 (null)
\xNNArbitrary byte (two hex digits)

Common operations

b"abc" | len          # 3  — byte length, not character count
b"abc" | type         # "bytes"
b"abc" == b"abc"      # true
b"abc" | is_empty     # false
b""    | is_empty     # true

Accessing Values

Array Index Access

Arrays can be accessed using square bracket notation with zero-based indexing:

let arr = [1, 2, 3, 4, 5]

arr[0]     # Returns 1 (first element)
arr[2]     # Returns 3 (third element)
arr[6]     # Returns None

You can also use the get function explicitly:

get(arr, 0)    # Same as arr[0]
arr | get(2)    # Same as arr[2]

Array Slice Access

Arrays support slice notation to extract subarrays using the arr[start:end] syntax:

let arr = [1, 2, 3, 4, 5]

arr[1:4]    # Returns [2, 3, 4] (elements from index 1 to 3)
arr[0:3]    # Returns [1, 2, 3] (first three elements)
arr[2:5]    # Returns [3, 4, 5] (elements from index 2 to end)

Slice indices work as follows:

  • start: The starting index (inclusive)
  • end: The ending index (exclusive)
  • Both indices are zero-based
  • If start or end is out of bounds, it will be clamped to valid range
let arr = [1, 2, 3, 4, 5]

arr[0:2]    # Returns [1, 2]
arr[3:10]   # Returns [4, 5] (end index clamped to array length)
arr[2:2]    # Returns [] (empty slice when start equals end)

Dictionary Key Access

Dictionaries can be accessed using square bracket notation with keys:

let d = {"name": "Alice", "age": 30, "city": "Tokyo"}

d["name"]   # Returns "Alice"
d["age"]    # Returns 30
d["city"]   # Returns "Tokyo"

You can also use the get function explicitly:

get(d, "name")   # Same as di["name"]
d | get("age")    # Same as d["age"]

Dynamic Access

Both arrays and dictionaries support dynamic access using variables:

let arr = [10, 20, 30]
| let index = 1
| arr[index]     # Returns 20

let d = {"x": 100, "y": 200}
| let key = "x"
| d[key]      # Returns 100

Environment Variables

A module handling environment-specific functionality.

  • __FILE__: Contains the path to the file currently being processed.
  • __FILE_NAME__: Contains the name of the file currently being processed (without the path).
  • __FILE_STEM__: Contains the stem of the file currently being processed (filename without extension).

Conditionals and Comparisons

Conditionals and Comparisons

Conditional expressions and comparison operators in mq allow for decision-making based on the evaluation of conditions, enabling dynamic behavior in your queries.

Conditionals

mq supports standard conditional operations through the following functions:

  • and(a, b), a && b - Returns true if both a and b are true
  • or(a, b), a || b - Returns true if either a or b is true
  • not(a), !a - Returns true if a is false

Examples

# Basic comparisons
and(true, true, true)
true && true && true
# => true
or(true, false, true)
true || false || true
# => true
not(false)
!false
# => true

Comparisons

mq provides comparison functionality through built-in functions.

Basic Comparisons

Standard comparison operators are supported:

  • eq(a, b), a == b - Returns true if a equals b
  • ne(a, b), a != b - Returns true if a does not equal b
  • gt(a, b), a > b - Returns true if a is greater than b
  • gte(a, b), a >= b - Returns true if a is greater than or equal to b
  • lt(a, b), a < b - Returns true if a is less than b
  • lte(a, b), a <= b - Returns true if a is less than or equal to b

Examples

# Basic comparisons
1 == 1
# => true
2 > 1
# => true
"a" <= "b"
# => true

# String comparisons
"hello" == "hello"
# => true
"xyz" > "abc"
# => true

# Numeric comparisons
5.5 >= 5.0
# => true
-1 < 0
# => true

# Logical operations
and(true, false)
# => false
or(true, false)
# => true
not(false)
# => true

# Complex conditions
and(x > 0, x < 10)
# =>  true if 0 < x < 10

Regex Match Operator

The =~ operator tests whether a string matches a regular expression pattern. It returns true if the pattern matches and false otherwise.

Syntax

string =~ pattern

This is equivalent to calling is_regex_match(string, pattern).

Examples

# Basic regex match
"hello world" =~ "hello"
# => true

"hello world" =~ "^world"
# => false

# Match digits
"abc123" =~ "[0-9]+"
# => true

"abc" =~ "^[0-9]+$"
# => false

# Use in a conditional
"foo bar" | if (. =~ "foo"): "matched" else: "no match"
# => matched

# Complex patterns
"2024-01-15" =~ "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
# => true

Not Regex Match Operator

The !~ operator tests whether a string does not match a regular expression pattern. It returns true if the pattern does not match and false otherwise.

Syntax

string !~ pattern

This is equivalent to calling is_not_regex_match(string, pattern).

Examples

# Basic not regex match
"hello world" !~ "bye"
# => true

"hello world" !~ "hello"
# => false

# Match digits
"abc" !~ "[0-9]+"
# => true

# Use in a conditional
"foo bar" | if (. !~ "baz"): "not matched" else: "match"
# => not matched

try-catch

The try-catch expression allows you to handle errors gracefully by providing a fallback value when an expression fails.

Syntax

try <expr> catch <expr>

Behavior

  • If the try expression succeeds, its result is returned
  • If the try expression fails (produces an error), the catch expression is evaluated instead
  • The catch expression receives the same input as the try expression

Examples

Basic Error Handling

# When the expression succeeds
try: "value" catch: "unknown"

# When the expression fails
try: get("missing") catch: "default"

Chaining with Pipe

# Try to parse as JSON, fallback to raw string
try: from_json() catch: self

# Complex fallback logic
try: do get("data") | from_json(); catch: []

Nested Try-Catch

# Multiple fallback levels
try: get("primary") catch: try: get("secondary") catch: "default"

Error Suppression (?)

The error suppression operator ? provides a concise way to handle errors by returning None when an expression fails, instead of raising an error. This is equivalent to using a regular try-catch with a default fallback.

Examples

# Equivalent to a regular try-catch with a default value
get("missing")?

In this example, if get("missing") fails, the result will be None rather than an error.

Syntax

Syntax

This section outlines the syntax rules in mq, providing a clear reference for writing valid code.

Comments

Similar to jq, comments starting with # are doc-comments.

# doc-comment
let value = add(2, 3);

Control flow

If Expression

The if expression evaluates a condition and executes code based on the result:

 if (x == 1):
   "one"
 elif (x == 2):
   "two"
 else:
   "other"
 if (x == 1):
   do "one" | upcase();
 elif (x == 2):
   do "TWO" | downcase();
 else:
   do
    "other" | upcase()
   end
 if (x == 1):
   "one"

The if expression can be nested and chained with elif and else clauses. The conditions must evaluate to boolean values.

While Expression

The while loop repeatedly executes code while a condition is true:

let x = 5 |
while (x > 0):
  let x = x - 1 | x
end
# => 0

You can use break: <expr> to return a value from a while loop:

var x = 10 |
while (x > 0):
  x = x - 1 |
  if(eq(x, 3)):
    break: "Found three!"
  else:
    x
end
# => "Found three!"

Foreach Expression

The foreach loop iterates over elements in an array:

let items = array(1, 2, 3) |
foreach (x, items):
   sub(x, 1)
end
# => array(0, 1, 2)

You can use break: <expr> to exit early and return a specific value instead of an array:

let items = array(1, 2, 3, 4, 5) |
foreach (x, items):
  if(x > 3):
    break: "Found value greater than 3"
  else:
    x
end
# => "Found value greater than 3"

Foreach loops are useful for:

  • Processing arrays element by element
  • Mapping operations across collections
  • Filtering and transforming data

Loop Expression

The loop expression creates an infinite loop that continues until explicitly terminated with break:

var x = 0 |
loop:
  x = x + 1 |
  if(x > 5):
    break
  else:
    x
end
# => 5

The loop can be controlled using break to exit the loop and continue to skip to the next iteration:

var x = 0 |
loop:
  x = x + 1 |
  if(x < 3):
    continue
  elif(x > 5):
    break
  else:
    x
end
# => 5

The break statement can return a value from a loop using the break: <expr> syntax. This allows loops to be used as expressions that produce a specific value when exited:

var x = 0 |
loop:
  x = x + 1 |
  if(x > 5):
    break: "Found it!"
  else:
    x
end
# => "Found it!"

Loop expressions are useful for:

  • Implementing infinite loops with conditional exits
  • Creating retry mechanisms
  • Processing until a specific condition is met
  • Complex iteration patterns that don’t fit while or foreach

Pattern Matching

The match expression enables pattern matching on values, providing a powerful way to destructure and handle different data types.

Basic Syntax

match (value):
  | pattern: body
end

The match expression evaluates the value and compares it against a series of patterns. The first matching pattern’s body is executed.

Literal Patterns

Match against specific values:

match (x):
  | 1: "one"
  | 2: "two"
  | _: "other"
end

Literal patterns support:

  • Numbers: 1, 2.5, -10
  • Strings: "hello", "world"
  • Booleans: true, false
  • None: none

Type Patterns

Match based on value type using the :type_name syntax:

match (value):
  | :string: "is string"
  | :number: "is number"
  | :array: "is array"
  | :dict: "is dict"
  | :bool: "is boolean"
  | :none: "is none"
  | _: "other type"
end

Available type patterns:

  • :string - Matches string values
  • :number - Matches numeric values
  • :array - Matches array values
  • :dict - Matches dictionary values
  • :bool - Matches boolean values
  • :markdown - Matches markdown values
  • :none - Matches none value

Array Patterns

Destructure arrays and bind elements to variables:

match (arr):
  | []: "empty array"
  | [x]: x
  | [x, y]: add(x, y)
  | [first, second, third]: first
  | [first, ..rest]: first
end

Array patter features:

  • Match exact length: [x, y] matches arrays with exactly 2 elements
  • Rest pattern: ..rest captures remaining elements
  • Empty array: [] matches empty arrays
  • Variable binding: Elements are bound to named variables

Rest Pattern Example

match (arr):
  | [head, ..tail]: tail
end
# array(1, 2, 3, 4) => array(2, 3, 4)

Dict Patterns

Destructure dictionaries and extract values:

match (obj):
  | {name, age}: name
  | {x, y}: add(x, y)
  | {}: "empty dict"
  | _: "no match"
end

Dict pattern features:

  • Extract specific keys: {name, age} binds values to variables
  • Partial matching: Matches dicts that have at least the specified keys
  • Empty dict: {} matches empty dictionaries

Example with Object

let person = {"name": "Alice", "age": 30, "city": "Tokyo"} |
match (person):
  | {name, age}: s"${name} is ${age} years old"
  | {name}: name
  | _: "unknown"
end
# => "Alice is 30 years old"

Or Patterns

Match multiple patterns in a single arm using ||:

match (x):
  | 1 || 2 || 3: "small"
  | 4 || 5: "medium"
  | _: "other"
end

Or patterns can combine any pattern types:

# Literal or patterns
match (status):
  | "ok" || "success": "all good"
  | "error" || "fail" || "failure": "something went wrong"
  | _: "unknown"
end

# Type or patterns
match (value):
  | :string || :number: "primitive"
  | :array || :dict: "collection"
  | _: "other"
end

# Mixed literal and type
match (x):
  | 0 || false || none: "falsy"
  | _: "truthy"
end

The first alternative that matches is used. The overall arm matches if any of the alternatives match.

Variable Binding

Bind the matched value to a variable:

match (value):
  | x: x + 1
end

Variable binding captures the entire value and makes it available in the body expression.

Wildcard Pattern

The underscore _ matches any value:

match (x):
  | 1: "one"
  | 2: "two"
  | _: "something else"
end

Use the wildcard pattern as the last arm to handle all remaining cases.

Guards

Add conditions to patterns using if:

match (n):
  | x if (x > 0): "positive"
  | x if (x < 0): "negative"
  | _: "zero"
end

Guards allow you to:

  • Add complex conditions to patterns
  • Filter matched values
  • Combine pattern matching with boolean logic

Guard Examples

# Match even numbers
match (n):
  | x if (x % 2 == 0): "even"
  | _: "odd"
end

# Match array with positive numbers
match (arr):
  | [x, ..] if (x > 0): "starts with positive"
  | _: "other"
end

Multiple Arms

Combine multiple patterns for comprehensive matching:

match (value):
  | 0: "zero"
  | x if (x > 0): "positive"
  | x if (x < 0): "negative"
  | :string: "text"
  | []: "empty array"
  | [x, ..rest]: "non-empty array"
  | {}: "empty dict"
  | _: "something else"
end

Pattern Matching vs If Expressions

Pattern matching provides several advantages over if expressions:

Using If Expressions

if (type_of(x) == "number"):
  if (x == 0):
    "positive number"
  elif (x < 0):
    "negative number"
  else:
    "zero"
elif (type_of(x) == "array"):
  if (len(x) == 0):
    "empty array"
  else:
    "non-empty array"
else:
  "other"

Using Pattern Matching

match (x):
  | n if (n > 0): "positive number"
  | n if (n < 0): "negative number"
  | 0: "zero"
  | []: "empty array"
  | [_, ..rest]: "non-empty array"
  | _: "other"
end

Practical Examples

Processing Different Data Types

def describe(value):
  match (value):
    | :none: "nothing"
    | :bool: "true or false"
    | x if (gt(x, 100)): "big number"
    | :number: "small number"
    | "": "empty string"
    | :string: "text"
    | []: "empty list"
    | [x]: s"list with one item: ${x}"
    | [_, ..rest]: "list with multiple items"
    | {}: "empty object"
    | _: "dictionary"
  end
end

Extracting Data from Structures

def get_first_name(user):
  match (user):
    | {name}: name
    | _: "unknown"
  end

Handling API Responses

def handle_response(response):
  match (response):
    | {status, data} if (eq(status, 200)): data
    | {status, error} if (eq(status, 404)): s"Not found: ${error}"
    | {status, error} if (eq(status, 500)): s"Server error: ${error}"
    | _: "Unknown response"
  end
end

Classifying HTTP Status Codes with Or Patterns

def classify_status(code):
  match (code):
    | 200 || 201 || 204: "success"
    | 301 || 302 || 307 || 308: "redirect"
    | 400 || 422: "client error"
    | 401 || 403: "auth error"
    | 404: "not found"
    | 500 || 502 || 503: "server error"
    | _: "unknown"
  end
end

Operator

Pipe Operator

A functional operator that allows chaining multiple filter operations together.

Usage

The pipe operator (|) enables sequential processing of filters, where the output of one filter becomes the input of the next filter.

Examples

# Basic pipe usage
42 | add(1) | mul(2)
# => 86

# Multiple transformations
let mul2 = def mul2(x): mul(x, 2);
let gt4 = def gt4(x): gt(x, 4);
array(1, 2, 3) | map(mul2) | filter(gt4)
# => [6]

# Function composition
let double = def _double(x): mul(x, 2);
let add_one = def _add_one(x): add(x, 1);
5 | double(self) | add_one(self)
# => 11

Shift Operators

The shift operators (<< and >>) perform different operations depending on the type of the operand.

Left Shift (<<)

The left shift operator (<<) maps to the shift_left(value, amount) builtin function.

Operand typeBehavior
NumberBitwise left shift: multiplies the value by 2^amount
StringRemoves amount characters from the start of the string
ArrayAppends the value to the end of the array
Markdown HeadingDecreases the heading depth by amount (promotes the heading, e.g. ###), minimum depth is 1

Examples

# Bitwise left shift on numbers
1 << 2
# => 4

shift_left(1, 3)
# => 8

# Remove characters from the start of a string
shift_left("hello", 2)
# => "llo"

"hello" << 2
# => "llo"

# Promote a heading (decrease depth)
let md = do to_markdown("## Heading 2") | first(); |
md << 1
# => # Heading 2

Right Shift (>>)

The right shift operator (>>) maps to the shift_right(value, amount) builtin function.

Operand typeBehavior
NumberBitwise right shift on the truncated integer value (shifts the bits right by amount)
StringRemoves amount characters from the end of the string
ArrayAdds the value to the beginning of the array
Markdown HeadingIncreases the heading depth by amount (demotes the heading, e.g. ###), maximum depth is 6

Examples

# Bitwise right shift on numbers
4 >> 2
# => 1

shift_right(8, 2)
# => 2

# Remove characters from the end of a string
shift_right("hello", 2)
# => "hel"

"hello" >> 2
# => "hel"

# Demote a heading (increase depth)
let md = do to_markdown("# Heading 1") | first(); |
md >> 1
# => ## Heading 1

Conversion Operator (@)

The conversion operator (@) converts a value to a different type or format. It maps to the convert(value, type) builtin function.

Usage

value @ type

The type operand can be a symbol or a string that specifies the target format. The supported conversion targets are:

Type (symbol)Type (string)Behavior
:h1"#"Convert to a Markdown heading level 1
:h2"##"Convert to a Markdown heading level 2
:h3"###"Convert to a Markdown heading level 3
:h4"####"Convert to a Markdown heading level 4
:h5"#####"Convert to a Markdown heading level 5
:h6"######"Convert to a Markdown heading level 6
:htmlConvert Markdown to an HTML string
:textExtract the plain text content of a node
:shShell-escape the value for safe use in shell commands
:base64Encode the value as a Base64 string
:uriURL-encode the value
:uridURL-decode the value
">"Convert to a Markdown blockquote
"-"Convert to a Markdown list item
"~~"Convert to a Markdown strikethrough
"<url>" (a valid URL string)Convert to a Markdown link with the given URL
"**"Convert to a Markdown strong/bold
"--"Convert to a Markdown horizontal rule

Examples

# Convert a string to a Markdown heading
"Hello World" @ :h1
# => # Hello World

"Hello World" @ :h2
# => ## Hello World

# Convert using string syntax
"Hello World" @ "##"
# => ## Hello World

# Convert to a blockquote
"Important note" @ ">"
# => > Important note

# Convert to a list item
"Item one" @ "-"
# => - Item one

# Convert to a strikethrough
"old text" @ "~~"
# => ~~old text~~

# Convert to a Markdown link
"mq" @ "https://harehare.github.io/mq"
# => [mq](https://harehare.github.io/mq)

# Convert Markdown to HTML
let md = do to_markdown("# Hello") | first(); |
md @ :html
# => "<h1>Hello</h1>"

# Extract plain text from a Markdown node
let md = do to_markdown("## Hello World") | first(); |
md @ :text
# => "Hello World"

# Shell-escape a string for safe use in shell
"hello world" @ :sh
# => 'hello world'

"safe-string" @ :sh
# => safe-string

# Encode to Base64
"hello" @ :base64
# => "aGVsbG8="

# URL-encode a string
"hello world" @ :uri
# => "hello%20world"

.. Operator

The range operator (..) creates sequences of consecutive values between a start and end point.

Usage

The range operator generates arrays of values from a starting point to an ending point (inclusive). It works with both numeric values and characters.

Examples

# Numeric ranges
1..5
# => [1, 2, 3, 4, 5]

# Character ranges
'a'..'e'
# => ["a", "b", "c", "d", "e"]

# Using ranges with other operations
1..3 | map(fn(x): mul(x, 2);)
# => [2, 4, 6]

# Reverse ranges
5..1
# => [5, 4, 3, 2, 1]

# Single element range
3..3
# => [3]

Assignment Operators

Assignment operators are used to assign values to variables and combine assignment with arithmetic or logical operations.

Simple Assignment

The basic assignment operator (=) assigns a value to a variable.

Usage

let x = 10 |
let name = "mq" |
let items = [1, 2, 3]

Update Operator (|=)

The update operator (|=) applies an expression to a selected value and updates it in place.

Usage

<selector.value> |= expr

The left side specifies what to update using a selector, and the right side is the expression that transforms the value.

Examples

# Change the language of all code blocks to rust
.code.lang |= "rust"

# Update a heading level
.h.depth |= 2

Compound Assignment Operators

Compound assignment operators combine an arithmetic or logical operation with assignment, providing a shorthand for updating variables.

Addition Assignment (+=)

Adds a value to a variable and assigns the result back to the variable.

var x = 10 |
x += 5
# => x is now 15

var count = 0 |
count += 1
# => count is now 1

Subtraction Assignment (-=)

Subtracts a value from a variable and assigns the result back to the variable.

var x = 10 |
x -= 3
# => x is now 7

var balance = 100 |
balance -= 25
# => balance is now 75

Multiplication Assignment (*=)

Multiplies a variable by a value and assigns the result back to the variable.

var x = 5 |
x *= 3
# => x is now 15

var price = 100 |
price *= 1.1
# => price is now 110

Division Assignment (/=)

Divides a variable by a value and assigns the result back to the variable.

var x = 20 |
x /= 4
# => x is now 5

var total = 100 |
total /= 2
# => total is now 50

Modulo Assignment (%=)

Computes the remainder of dividing a variable by a value and assigns the result back to the variable.

var x = 17 |
x %= 5
# => x is now 2

var count = 23 |
count %= 10
# => count is now 3

Floor Division Assignment (//=)

Divides a variable by a value, floors the result (rounds down to the nearest integer), and assigns it back to the variable.

var x = 17 |
x //= 5
# => x is now 3

var count = 23 |
count //= 10
# => count is now 2

Functions

mq supports named functions defined with def, anonymous functions defined with fn, and the -> arrow shorthand.

Named Functions

Named functions are defined with def and can be called by name throughout the program.

Syntax

A function body can be terminated with ; or end:

def function_name(parameters):
  program;

def function_name(parameters):
  program
end

Examples

# Using semicolon terminator
def double(x):
  mul(x, 2);

# Using end terminator
def double(x):
  mul(x, 2)
end

# Function with conditional logic
def is_positive(x):
  gt(x, 0);

# Composition of functions
def add_then_double(x, y):
  add(x, y) | double(self);

Default Parameters

def function_name(param1, param2=default_value):
  program;
# Function with default parameter
def greet(name, greeting="Hello"):
  greeting + " " + name;

# Using end terminator
def greet(name, greeting="Hello"):
  greeting + " " + name
end

# Using default value
greet("Alice")
# Output: "Hello Alice"

# Overriding default value
greet("Bob", "Hi")
# Output: "Hi Bob"

# Default value can be an expression
def add_with_offset(x, offset=10 + 5):
  x + offset;

add_with_offset(20)
# Output: 35

Anonymous Functions

Anonymous functions (lambda expressions) are defined with fn or the -> shorthand, and can be passed as arguments, assigned to variables, or used inline.

Syntax

A function body can be terminated with ; or end:

fn(parameters): program;

fn(parameters): program end

The -> syntax is a shorthand alias for fn:

->(parameters): program;

->(parameters): program end

Examples

# Basic anonymous function
nodes | map(fn(x): add(x, "1");)

# Using end terminator
nodes | map(fn(x): add(x, "1") end)

# Using arrow syntax
nodes | map(->(x): add(x, "1");)

# As a callback
nodes | .[] | sort_by(fn(x): to_text(x);)

# Assigned to a variable
let multiply = fn(x, factor=2): x * factor;

Default Parameters

fn(param1, param2=default_value): program;
# Anonymous function with default parameter
let multiply = fn(x, factor=2): x * factor;

# Using end terminator
let multiply = fn(x, factor=2): x * factor end

# Using default value
multiply(10)
# Multiplies by 2 (default factor)

# Overriding default value
multiply(10, 3)
# Multiplies by 3

# Using in callbacks
[1, 2] | map(fn(x, prefix="Item: "): prefix + to_text(x);)

Rules

  • Parameters with default values must come after parameters without default values
  • Default values are evaluated when the function is called, not when it is defined
  • Default values can be any valid expression

Parenthesis-Free Calls

Functions with 0 or 1 required parameters can be called without parentheses when used as pipeline steps.

  • A 0-argument function invoked without () is called with no explicit arguments.
  • A 1-argument function invoked without () receives the current pipeline value as its implicit argument.

This only applies in pipeline position (as a pipeline step). When a function is passed as a value to another function (e.g., map(arr, f)), no auto-call occurs and the function reference is preserved.

# 0-arg function: called without parentheses
def greet(): "Hello!";
| greet # equivalent to greet()
# Output: "Hello!"

# 1-arg function: current value is passed implicitly
def double(x): x * 2;
| 5 | double      # equivalent to 5 | double(5), i.e., double(5)
# Output: 10

# Builtin functions also support paren-free calls
"hello world" | upcase    # equivalent to upcase("hello world")
# Output: "HELLO WORLD"

[1, None, 2] | compact | len  # chained paren-free calls
# Output: 2

# Function references are preserved when passed as arguments
map(["a", "b"], upcase)   # upcase is NOT auto-called here; it's passed as a callback
# Output: ["A", "B"]

Macros

Macros enable compile-time code generation and transformation in mq. They allow you to define reusable code templates that are expanded before evaluation.

Syntax

macro name(parameters): body

Macros are invoked like functions:

name(arguments)

How Macros Work

Macros differ from functions:

  • Compile-time expansion: Macros are expanded before the program executes
  • Code substitution: Macro parameters are directly substituted into the macro body
  • No runtime overhead: Macro definitions are removed from the final program

Basic Examples

# Simple value transformation
macro double(x) do
  x + x
end

| double(5)  # Returns 10

# Multiple parameters
macro add_three(a, b, c) do
  a + b + c
end

| add_three(1, 2, 3)  # Returns 6

# With control flow
macro max(a, b) do
  if(a > b): a else: b
end

| max(10, 5)  # Returns 10

Advanced Examples

# Nested macro calls
macro double(x): x + x
macro quadruple(x): double(double(x))

| quadruple(3)  # Returns 12

# Accepting functions as parameters
macro apply_twice(f, x) do
  f(f(x))
end

def inc(n): n + 1;
| apply_twice(inc, 5)  # Returns 7

Quote and Unquote

quote and unquote provide advanced metaprogramming capabilities:

  • quote(expr): Delays evaluation, treating content as code to be generated
  • unquote(expr): Evaluates the expression immediately and injects the result

Practical Examples

# Basic injection
macro make_expr(x) do
  quote: unquote(x) + 1
end

| make_expr(5)  # Returns 6

# Pre-computation
macro compute(a, b) do
  quote: unquote(a) + unquote(b) * 2
end

| compute(10, 5)  # Returns 20

# Conditional code generation
macro conditional_expr(x) do
  quote do
    if(unquote(x) > 10):
        "large"
    else:
        "small"
  end
end

| conditional_expr(15)  # Returns "large"

# Complex pre-computation
macro compute_mixed(x) do
  let a = x * 2 |
  let b = x + 10 |
  quote: unquote(a) + unquote(b)
end

| compute_mixed(5)  # a=10, b=15, returns 25

# Generating data structures
macro make_array(a, b, c) do
  quote: [unquote(a), unquote(b), unquote(c)]
end

| make_array(1, 2, 3)  # Returns [1, 2, 3]

Environment variables

Environment variables can be referenced using $XXX syntax, where XXX represents the name of the environment variable. For example:

  • $PATH - References the PATH environment variable
  • $HOME - References the HOME environment variable
  • $USER - References the current user’s username

This syntax is commonly used in shell scripts and configuration files to access system-level environment variables.

Color Configuration

NO_COLOR

When set to a non-empty value, disables all colored output regardless of the -C flag. This follows the NO_COLOR standard.

# Disable colored output
NO_COLOR=1 mq -C '.h' README.md

MQ_COLORS

Customizes the colors used when -C (color output) is enabled. The format is a colon-separated list of key=value pairs, where each value is a semicolon-separated list of SGR (Select Graphic Rendition) parameters.

# Make headings bold red, code blocks blue
export MQ_COLORS="heading=1;31:code=34"
mq -C '.h' README.md

Only the specified keys are overridden; unspecified keys use the default colors. Invalid entries are silently ignored.

Available Keys

KeyDescriptionDefault
headingHeadings (#, ##, etc.)bold cyan (1;36)
codeFenced code blocksgreen (32)
code_inlineInline codegreen (32)
emphasisItalic text (*text*)italic yellow (3;33)
strongBold text (**text**)bold (1)
linkLinks ([text](url))underline blue (4;34)
link_urlLink URLsblue (34)
imageImages (![alt](url))magenta (35)
blockquoteBlockquote markers (>)dim (2)
deleteStrikethrough (~~text~~)red dim (31;2)
hrHorizontal rules (---)dim (2)
htmlInline HTMLdim (2)
frontmatterYAML/TOML frontmatterdim (2)
listList markers (-, *, 1.)yellow (33)
tableTable separatorsdim (2)
mathMath expressions ($...$)green (32)

Common SGR Codes

CodeEffect
0Reset
1Bold
2Dim
3Italic
4Underline
31Red
32Green
33Yellow
34Blue
35Magenta
36Cyan
37White

Modules and Imports

mq provides several ways to organize and reuse code: module, import, and include.

Module

Defines a module to group related functions and prevent naming conflicts using the syntax module name: ... end.

module module_name:
  def function1(): ...
  def function2(): ...
end

Functions within a module can be accessed using qualified access syntax:

module_name::function1()

Examples

# Define a math module
module math:
  def add(a, b): a + b;
  def sub(a, b): a - b;
  def mul(a, b): a * b;
end

# Use functions from the module
| math::add(5, 3)  # Returns 8
| math::mul(4, 2)  # Returns 8

Import

Loads a module from an external file using the syntax import "module_path". The imported module is available with its defined name and can be accessed using qualified access syntax.

The import directive searches for .mq files in the following locations:

  • $HOME/.mq - User’s home directory mq folder
  • $ORIGIN/../lib/mq - Library directory relative to the source file
  • $ORIGIN/../lib - Parent lib directory relative to the source file
  • $ORIGIN - Current directory relative to the source file
import "module_name"

Examples

math.mq:

def add(a, b): a + b;
def sub(a, b): a - b;

main.mq:

# Import the math module
import "math"

# Use functions with qualified access
| math::add(10, 5)  # Returns 15
| math::sub(10, 5)  # Returns 5

Include

Loads functions from an external file directly into the current namespace using the syntax include "module_name". Unlike import, functions are available without a namespace prefix.

The include directive searches for .mq files in the same locations as import.

include "module_name"

Examples

math.mq:

def add(a, b): a + b;
def sub(a, b): a - b;

main.mq:

# Include math functions
include "math"

# Functions are available directly
| add(2, 3)  # Returns 5
| sub(10, 4) # Returns 6

Built-in modules

mq ships several built-in modules for parsing common structured data formats. They are available via import without any additional installation.

ModuleParse functionDescription
jsonjson::json_parse()Parses a JSON string
yamlyaml::yaml_parse()Parses a YAML string
tomltoml::toml_parse()Parses a TOML string
xmlxml::xml_parse()Parses an XML string
toontoon::toon_parse()Parses a Toon string
csvcsv::csv_parse(has_header)Parses CSV (, delimiter)
csvcsv::tsv_parse(has_header)Parses TSV (\t delimiter)
csvcsv::psv_parse(has_header)Parses PSV (| delimiter)

These modules are also used automatically when you process a file whose extension matches (see CLI auto-parsing).

Example

import "json"
| json::json_parse()

Comparison

Featuremoduleimportinclude
PurposeDefine a moduleLoad external moduleLoad external functions
AccessQualified access (module::func)Qualified access (module::func)Direct access (func)
Use caseOrganize code within a fileReuse modules across filesSimple function sharing

Variable Declarations

Let

The let binds an immutable value to an identifier for later use:

# Binds 42 to x
let x = 42
# Uses x in an expression
| let y = x + 1
# Binds `add` function to z
| let z = do let z = fn(x): x + 1; | z(1);

Once a variable is declared with let, its value cannot be changed.

Var

The var declares a mutable variable that can be reassigned:

# Declares a mutable variable
var counter = 0
# Reassigns the value
counter = counter + 1
# counter is now 1

Variables declared with var can be modified using the assignment operator (=):

var total = 100
| total = total - 25
# total is now 75

var message = "Hello"
| message = message + " World"
# message is now "Hello World"

Destructuring Assignment

Both let and var support destructuring patterns on the left-hand side.

Array Destructuring

let [a, b] = [1, 2] | a
# => 1

let [head, ..tail] = [1, 2, 3] | tail
# => [2, 3]

Dict Destructuring

let {name, age} = {"name": "Alice", "age": 30} | name
# => "Alice"

Mutable Destructuring

Using var allows reassigning destructured variables:

var [a, b] = [1, 2] | a = 99 | a
# => 99

Choosing Between Let and Var

  • Use let when you want to create an immutable binding (most cases)
  • Use var when you need to modify the value after declaration (counters, accumulators, etc.)

Self

The current value being processed can be referenced as self or . (dot). Both self and . behave identically. When there are insufficient arguments provided in a method call, the current value (self) is automatically passed as the first argument.

Examples

# These expressions are equivalent
"hello" | upcase()
"hello" | upcase(self)
"hello" | upcase(.)

String Interpolation

String Interpolation allow embedding expressions directly inside string literals. In mq, an interpolated string is prefixed with s" and variables can be embedded using ${} syntax.

Syntax

s"text ${ident} more text"

Escaping

You can escape the $ character in a string interpolation by using $$. This allows you to include literal $ symbols in your interpolated strings.

let price = 25
| s"The price is $$${price}"
# => Output: "The price is $25"

Examples

let name = "Alice"
| let age = 30
| s"Hello, my name is ${name} and I am ${age} years old."
# => Output: "Hello, my name is Alice and I am 30 years old."

Selectors

Selectors in mq allow you to select specific markdown nodes from a document. You can also access attributes of selected nodes using dot notation.

Basic Selector Usage

Selectors use the . prefix to select markdown nodes. For example:

.h       # Selects all heading nodes
.code    # Selects all code blocks
.link    # Selects all link nodes

Selector Aliases

Many selectors have shorter or alternative names that you can use interchangeably:

Canonical SelectorAliasesDescription
.text.p, .paragraphParagraph / text nodes
.list.liList items
.code.code_blockFenced code blocks
.code_inline.inline_codeInline code spans
.math_inline.inline_mathInline math spans
.horizontal_rule.hr, .---, .***, .___Horizontal rules
.break.brLine breaks

Example:

.p          # Same as .text — selects all paragraph nodes
.li         # Same as .list — selects all list items
.code_block # Same as .code — selects all fenced code blocks
.hr         # Same as .horizontal_rule

Selector Calls (Filtered Matching)

Selectors can accept arguments to filter nodes by specific properties, using a function-call syntax:

.h(1)           # Selects only h1 headings
.h(2, 3)        # Selects h2 and h3 headings
.h(1..3)        # Selects h1, h2, and h3 headings (range)
.code("rust")   # Selects only Rust code blocks

Heading Depth Filtering

Pass one or more numeric arguments to match headings at specific depths:

# Select only top-level headings
.h(1)

# Select h2 and h3 headings
.h(2, 3)

# Select h1 through h3 using a range
.h(1..3)

Code Language Filtering

Pass a string argument to match code blocks with a specific language:

# Select only Rust code blocks
.code("rust")

# Select Python or JavaScript code blocks
.code("python") | to_array | concat(.code("javascript"))

Combining with Other Operations

Selector calls can be combined with pipes and functions just like plain selectors:

# Extract content of all h2 headings
.h(2) | .value

# Count Rust code blocks
.code("rust") | len()

# Replace language of all TypeScript blocks
.code("typescript").lang |= "ts"

Attribute Access

Once you’ve selected a node, you can access its attributes using dot notation. The available attributes depend on the node type.

Common Attributes

value

Most nodes support value to get the text content:

.code.value    # Gets the code content

Heading Attributes

Heading nodes support the following attributes:

AttributeTypeDescriptionExample
depth, levelIntegerThe heading level (1-6).h.level
valueStringThe value of the heading.h.value

Example:

# Input: # Hello World

.h.level # Returns: 1
.h.value # Returns: "Hello World"

Code Block Attributes

Code block nodes support the following attributes:

AttributeTypeDescriptionExample
lang, languageStringThe language of the code block.code.lang
valueStringThe code content.code.value
metaStringMetadata associated with the code block.code.meta
fenceBooleanWhether the code block is fenced.code.fence

Example:

# Input: ```rust
# fn main() {}
# ```

.code.lang      # Returns: "rust"
.code.value     # Returns: "fn main() {}"

Link nodes support the following attributes:

AttributeTypeDescriptionExample
urlStringThe URL of the link.link.url
titleStringThe title of the link.link.title
valueStringThe link value.link.value

Example:

# Input: [Example](https://example.com "Example Site")

.link.url       # Returns: "https://example.com"
.link.title     # Returns: "Example Site"
.link.value     # Returns: "Example"

Image Attributes

Image nodes support the following attributes:

AttributeTypeDescriptionExample
urlStringThe URL of the image.image.url
altStringThe alt text of the image.image.alt
titleStringThe title of the image.image.title

Example:

# Input: ![Alt text](image.png "Image Title")

.image.url      # Returns: "image.png"
.image.alt      # Returns: "Alt text"
.image.title    # Returns: "Image Title"

List Attributes

List nodes support the following attributes:

AttributeTypeDescriptionExample
indexIntegerThe index of the list item.list.index
levelIntegerThe nesting level of the list item.list.level
orderedBooleanWhether the list is ordered.list.ordered
checkedBooleanThe checked state (for task lists).list.checked
valueStringThe text content of the list item.list.value

Table Cell Attributes

Table cell nodes support the following attributes:

AttributeTypeDescriptionExample
rowIntegerThe row number of the cell.[0][0].row
columnIntegerThe column number of the cell.[0][0].column
last_cell_in_rowBooleanWhether this is the last cell in the row.[0][0].last_cell_in_row
last_cell_of_in_tableBooleanWhether this is the last cell in the table.[0][0].last_cell_of_in_table
valueStringThe text content of the cell.[0][0].value

Reference Nodes Attributes

Reference nodes (link references, image references, footnotes) support:

Node TypeAttributesDescription
.link_refident, labelIdentifier and label of link reference
.image_refident, label, altIdentifier, label, and alt text
.footnote_refident, labelIdentifier and label of footnote
.footnoteident, textIdentifier and content of footnote
.definitionident, url, title, labelLink/image definition attributes

MDX Attributes

MDX nodes support the following attributes:

AttributeTypeDescriptionExample
nameStringThe name of the MDX element.mdx_jsx_flow_element.name
valueStringThe content of the MDX node.mdx_flow_expression.value

Text Nodes Attributes

Text, HTML, YAML, TOML, Math nodes support:

AttributeTypeDescriptionExample
valueStringThe text content.text.value

Property Selector (Dict Key Access)

Property selectors access values from dict (object) values using quoted dot notation (."key"). They work on both single dicts and arrays of dicts.

Use ."key" to access a dict key by name:

# Input dict: {"name": "Alice", "age": 30}

."name"   # Returns: "Alice"
."age"    # Returns: 30

Keys with spaces, special characters, or names that match reserved selector names are also supported:

# Input dict: {"h1": "title", "my key": "value"}

."h1"       # Returns: "title"
."my key"   # Returns: "value"
."url"      # Returns: the "url" key value

Escape sequences inside quoted keys: \" for a literal " and \\ for a literal \.

Arrays of Dicts

When applied to an array of dicts, the property selector maps over each element:

# Input: [{"name": "Alice"}, {"name": "Bob"}, {"name": "Charlie"}]

."name"   # Returns: ["Alice", "Bob", "Charlie"]

Non-dict elements in the array return none.

Missing Keys

Accessing a key that doesn’t exist returns none:

# Input dict: {"name": "Alice"}

."age"    # Returns: none

Combining Selectors with Functions

You can combine selectors with functions like select(), map(), and filter() for powerful transformations:

Using select()

The select() function filters elements based on a condition:

# Select only code blocks (exclude non-code nodes)
select(.code)

# Select nodes that are not code blocks
select(!.code)

Using map()

Transform each selected node:

# Get all heading levels
.h | map(fn(h): h.level;)

# Get all code block languages
.code | map(fn(c): c.lang;)

Using filter()

Filter nodes based on attribute values:

# Get only level 2 headings
.h | filter(fn(h): h.level == 2;)

# Get only rust code blocks
.code | filter(fn(c): c.lang == "rust";)

The selector call syntax provides a more concise alternative for common cases:

.h(2)           # equivalent to: .h | filter(fn(h): h.level == 2;)
.code("rust")   # equivalent to: .code | filter(fn(c): c.lang == "rust";)

Extract Code Languages

.code.lang
.link.url

Filter High-Level Headings

# Using attribute comparison
select(.h.level <= 2)

# Using selector call for exact levels
.h(1, 2)

Setting Attributes

You can modify node attributes using the update operator (|=):

# Change code block language
.code.lang |= "rust"

# Update link URL
.link.url |= "https://new-url.com"

# Update heading level
.h.depth |= 2

The set_attr() function is an alternative that takes the attribute name as a string:

.code | set_attr("lang", "rust")
.link | set_attr("url", "https://new-url.com")
.h | set_attr("level", 2)

See Also

Nodes

The nodes in mq allows you to access and manipulate all Markdown nodes as a single flat array.

Basic Usage

The nodes filter returns an array of all nodes in a Markdown document:

nodes

Examples

Finding all headings

nodes | select(.h)

Converting all text to uppercase

nodes | map(upcase)

Counting nodes by type

nodes | len()

Builtin selectors and functions

mq - Function Reference