What is mq?
mainmq is a command-line tool designed for Markdown processing. It functions similarly to jq but is specifically optimized for Markdown structures, allowing you to query, filter, and transform Markdown content.repository·main·Indexed 21 days ago
https://github.com/harehare/mqA command-line tool for querying, filtering, and transforming Markdown files using a jq-like syntax. The ecosystem includes mq-crawl for converting HTML to Markdown with support for headless Chrome and WebDriver, mq-check for type inference and checking of mq code, and mq-dap for debugging via the Debug Adapter Protocol.
mq is a command-line tool designed for Markdown processing. It functions similarly to jq but is specifically optimized for Markdown structures, allowing you to query, filter, and transform Markdown content.The mq-lsp implementation provides the following IDE capabilities for mq development:
mq-lint diagnostics (toggled via --enable-lint).include/import), Refactoring (extract/inline), and Rename (symbol updates across files).The mq MCP server exposes four primary tools for processing content via mq queries:
html_to_markdown: Converts HTML to Markdown and applies an optional mq query.extract_markdown: Extracts content from existing Markdown using mq queries.available_functions: Lists all available mq functions (returns names, descriptions, parameters, and examples).available_selectors: Lists all available mq selectors (returns names, descriptions, and parameters).The mq ecosystem provides three primary tools for web scraping and data extraction, depending on your requirements:
mq-crawl: Best for fetching or crawling URLs to Markdown. It supports JavaScript rendering, multi-page crawling, and respects robots.txt.mq: Best for filtering or transforming Markdown or HTML using jq-like selectors. Use -I html to process HTML.mq's http() builtin: Best for making specific HTTP requests (like POST or custom headers) directly inside a query using the --allow-net flag. Use this when mq-crawl cannot satisfy the specific request requirements.Note: mq-crawl sends logs and statistics to stderr. When scripting, redirect these to avoid polluting your output: 2>/dev/null.
For advanced metaprogramming, mq provides quote and unquote to control when code is evaluated:
quote(expr): Delays evaluation, treating the content as raw code to be generated rather than executing it immediately.unquote(expr): Evaluates the expression immediately and injects the resulting value into the quoted block.This allows you to perform pre-computation or conditional code generation during the expansion phase.
# Basic injection
macro make_expr(x) do
quote: unquote(x) + 1
end
| make_expr(5) # Returns 6Shift operators perform different operations based on the operand type. They map to the shift_left(value, amount) and shift_right(value, amount) builtin functions respectively.
<<)2^amount).amount characters from the start of the string.## → #), minimum depth is 1.>>)amount characters from the end of the string.# → ##), maximum depth is 6.# Bitwise left shift on numbers
1 << 2
# => 4
# Remove characters from the start of a string
"hello" << 2
# => "llo"
# Promote a heading
let md = do to_markdown("## Heading 2") | first; |
md << 1
# => # Heading 2
# Bitwise right shift on numbers
4 >> 2
# => 1
# Remove characters from the end of a string
"hello" >> 2
# => "hel"
# Demote a heading
let md = do to_markdown("# Heading 1") | first; |
md >> 1
# => ## Heading 1When deciding how to declare a variable in mq, follow these guidelines:
let for most cases where you want to create an immutable binding.var only when you specifically need to modify the value after declaration (e.g., for counters or accumulators).You can extend mq functionality by adding custom subcommands. mq will look for executables starting with the prefix mq- in your PATH or in ~/.local/bin/.
This allows you to build your own specialized tools and integrate them into mq workflows seamlessly.
The mq-test runner uses the CST (Concrete Syntax Tree) to identify test functions in .mq files. A function is treated as a test if it meets any of these criteria:
test_.# @test or # [test] annotation comment.# @parametrize(...) annotation comment.mq supports several core data types for representing values:
| Type | Description | Examples |
|---|---|---|
| Number | Numeric values. | 1, 3.14, -42 |
| String | Unicode sequences using \{0x000} escapes. | "hello", "😊", "\u{1F600}" |
| Bytes | Raw byte sequences prefixed with b. Only ASCII allowed unescaped. | b"abc", b"\xf0\x9f\x99\x82" |
| Symbol | Immutable, interned identifiers prefixed with :. | :value, :success, :ok |
| Boolean | Truth values. | true, false |
| Array | Ordered collections. | [1, 2, 3], array(1, 2, 3) |
| Dict | Key-value mappings. | {"a": 1}, dict(["a", 1]) |
| Function | Executable code. | def foo(): 42; |
If the http-import feature is enabled, import and include support URLs.
Security: By default, only github.com/harehare (via raw.githubusercontent.com) is allowed. Use --allowed-domain <domain> to permit others.
GitHub Shorthand: You can omit the scheme for GitHub. The path is mapped to raw.githubusercontent.com using the following logic:
github.com/{owner}/{path}[@{version}]Caching and Reproducibility:
{system_cache_dir}/mq/.mq.lock file is created to record SHA-256 hashes of fetched content, ensuring reproducible builds. If remote content changes, mq will error unless you use --refresh-modules.# Plain URL
import "https://example.com/mymod.mq"
# GitHub shorthand
import "github.com/harehare/kdl.mq"
| kdl::kdl_parse("title \"Hello, World!\"")The section module allows splitting and filtering Markdown documents by their heading structure.
Section functions require access to all document nodes at once. If you call a section function on a single node, mq will issue a warning and treat the node as a one-element array. To use section functions correctly, you must:
-A flag on the command line.nodes function in your query to pipe the full document into the section functions.| Style | Syntax | Notes |
|---|---|---|
import | import "section" then section::fn() | Namespaced (Recommended) |
include | include "section" then fn() | No namespace prefix |
-A flag | mq -A 'section::fn()' | Aggregate mode (processes all nodes) |
When using the section module outside the CLI, section objects are returned as plain dicts. You must explicitly convert them using section::collect() to get Markdown nodes.
# Programmatic conversion example
import "section"
| nodes
| section::section("Installation")
| section::collect()