Skip to content

panuoksala/mqnet

Repository files navigation

MQNet

Tests CI NuGet

A .NET wrapper for mq — a jq-like query tool for Markdown. Query headings, code blocks, paragraphs, and more using a simple, composable query language.

How it works

MQNet is a thin managed wrapper around the native mq-ffi library, which is compiled from the mq Rust crate. At runtime, .NET's P/Invoke loads the platform-native binary (mq_ffi.dll / libmq_ffi.so / libmq_ffi.dylib) and marshals calls across the native boundary.

The package ships in two layers:

  • MQNet — the managed API you reference in your project.
  • MQNet.Runtime.<rid> — thin packages containing only the native binary for a specific platform (e.g. MQNet.Runtime.linux-x64). NuGet's runtime identifier graph selects the right one automatically at restore time.

You only need to install MQNet; the correct native binary is pulled in automatically.

Installation

dotnet add package MQNet

MQNet uses a split NuGet package model. The main package contains the managed library; native binaries are in separate runtime packages that NuGet resolves automatically based on your platform:

Platform Runtime Package
Windows x64 MQNet.Runtime.win-x64
Windows ARM64 MQNet.Runtime.win-arm64
Linux x64 MQNet.Runtime.linux-x64
Linux ARM64 MQNet.Runtime.linux-arm64
macOS x64 MQNet.Runtime.osx-x64
macOS ARM64 MQNet.Runtime.osx-arm64

Quick Start

using MQNet;

// Fluent API — quick one-shot queries
var result = Mq.Query(".h(1)")
    .On("# Hello\n\n## World\n\n# Another")
    .Run();

// result[0]  → "# Hello"
// result[1]  → "# Another"
// result.Text → "# Hello\n# Another"

Usage

Fluent API

// Extract all H2 headings
var headings = Mq.Query(".h(2)").On(markdown).Run();

// Filter headings containing a word
var filtered = Mq.Query(".h | select(contains(\"API\"))").On(markdown).Run();

// Extract code blocks by language
var rustBlocks = Mq.Query(".code(\"rust\")").On(markdown).Run();

// Query from HTML input
var result = Mq.Query(".h(1)")
    .On("<h1>Title</h1><p>Body</p>")
    .WithFormat(InputFormat.Html)
    .Run();

// Query plain text line by line
var matches = Mq.Query("select(contains(\"error\"))")
    .On(logOutput)
    .WithFormat(InputFormat.Text)
    .Run();

Typed Markdown Tags

Instead of writing raw mq selector strings, use MarkdownTag for strongly-typed, IntelliSense-discoverable selectors:

// Before — raw strings (still supported)
Mq.Query(".h(1)").On(markdown).Run();
Mq.Query(".code(\"rust\")").On(markdown).Run();

// After — typed selectors
Mq.Query(MarkdownTag.H1).On(markdown).Run();
Mq.Query(MarkdownTag.H2).On(markdown).Run();
Mq.Query(MarkdownTag.AllHeadings).On(markdown).Run(); // all headings, any level
Mq.Query(MarkdownTag.Code).On(markdown).Run();       // all code blocks
Mq.Query(MarkdownTag.Link).On(markdown).Run();
Mq.Query(MarkdownTag.List).On(markdown).Run();

Well-known tags

Tag Selector Description
MarkdownTag.H1MarkdownTag.H6 .h(1).h(6) Heading at a specific level
MarkdownTag.AllHeadings .h All headings (any level)
MarkdownTag.Paragraph / MarkdownTag.Text .text Paragraph / text nodes
MarkdownTag.Code .code All fenced code blocks
MarkdownTag.InlineCode .code_inline Inline code spans
MarkdownTag.Link .link Link nodes
MarkdownTag.Image .image Image nodes
MarkdownTag.List .list List items
MarkdownTag.Blockquote .blockquote Block quotes
MarkdownTag.Table .table Tables
MarkdownTag.HorizontalRule .horizontal_rule Horizontal rules
MarkdownTag.LineBreak .break Line breaks
MarkdownTag.Footnote .footnote Footnotes
MarkdownTag.MathInline .math_inline Inline math
MarkdownTag.Html .html Raw HTML nodes

Factory methods

// Language-filtered code blocks
Mq.Query(MarkdownTag.CodeBlock("rust")).On(markdown).Run();

// Heading at a specific level (1–6)
Mq.Query(MarkdownTag.HeadingLevel(3)).On(markdown).Run();

// Heading range — inclusive (both ends included)
Mq.Query(MarkdownTag.HeadingRange(1, 3)).On(markdown).Run();  // H1, H2, H3

// Heading range — C# Range syntax (exclusive end, follows C# Range convention)
Mq.Query(MarkdownTag.Heading(1..3)).On(markdown).Run();   // H1, H2 (NOT H3 — exclusive end)
Mq.Query(MarkdownTag.Heading(1..)).On(markdown).Run();    // H1–H6 (open end)
Mq.Query(MarkdownTag.Heading(^3..)).On(markdown).Run();   // H3–H6 (from-end index)

Range semantics note: HeadingRange(from, to) uses inclusive bounds — HeadingRange(1, 3) selects H1, H2, and H3. Heading(Range) follows standard C# Range semantics with an exclusive end — Heading(1..3) selects H1 and H2 only.

Convenience methods

// Shorthand for Mq.Query(MarkdownTag.HeadingLevel(1))
Mq.Heading(1).On(markdown).Run();

// Shorthand for Mq.Query(MarkdownTag.CodeBlock("rust"))
Mq.CodeBlock("rust").On(markdown).Run();

MqEngine (reuse across multiple queries)

using var engine = new MqEngine();

var h1 = engine.Eval(".h(1)", markdown);
var code = engine.Eval(".code", markdown);
var links = engine.Eval(".link", markdown);

HTML to Markdown conversion

// Basic conversion
string markdown = MqEngine.HtmlToMarkdown("<h1>Hello</h1><p>World</p>");

// With options
string markdown = MqEngine.HtmlToMarkdown(html, new ConversionOptions
{
    UseTitleAsH1 = true,
    GenerateFrontMatter = true,
    ExtractScriptsAsCodeBlocks = true
});

Working with results

MqResult result = Mq.Query(".h").On(markdown).Run();

result.Count       // number of matches
result[0]          // first match
result.Values      // IReadOnlyList<string>
result.Text        // all matches joined by "\n"

foreach (var item in result)
    Console.WriteLine(item);

Plain text output (no Markdown formatting)

mq has a built-in to_text() function that strips Markdown formatting at the AST level. Use it by piping your query through to_text, or use the WithPlainText() convenience method on the fluent API:

// Fluent API — WithPlainText() appends "| to_text" to the query
var result = Mq.Query(".h(1)")
    .On("# Hello **World**\n\n## Section")
    .WithPlainText()
    .Run();

// result[0] → "Hello World"  (no # or ** markers)

// Equivalent using MqEngine directly
using var engine = new MqEngine();
var result = engine.Eval(".h | to_text", "# Hello\n\n## World");

// result.Values → ["Hello", "World"]

Built-in Modules (v0.6+)

mq ships 15 standard modules embedded in the native binary. Load them with LoadModule (global scope) or ImportModule (namespaced) on an MqEngine instance — no file paths required.

using var engine = new MqEngine();

// LoadModule puts functions in global scope
engine.LoadModule("json");
var result = engine.Eval("""
    .code("json") | to_text | json_parse | json_stringify
    """, markdown);

// ImportModule requires a namespace prefix
engine.ImportModule("semver");
var ok = engine.Eval(
    """semver::semver_satisfies("1.5.0", ">=1.0.0,<2.0.0")""",
    "ignored", InputFormat.Text);
// → ["true"]

Available modules

Module Load name Key functions
md "md" Build Markdown nodes — h(), code(), text(), strong(), em(), link(), image(), list(), table_row(), doc()
section "section" Structure by headings — sections(), section(), title_contains(), body(), toc(), collect(), split(), by_level()
json "json" json_parse(), json_stringify(), json_to_markdown_table()
yaml "yaml" yaml_parse(), yaml_stringify(), yaml_to_markdown_table(), to_frontmatter()
toml "toml" toml_parse(), toml_stringify(), toml_to_json(), toml_to_markdown_table()
csv "csv" csv_parse(), tsv_parse(), psv_parse(), csv_stringify(), csv_to_markdown_table()
xml "xml" xml_parse(), xml_stringify(), xml_to_markdown_table()
hcl "hcl" hcl_parse(), hcl_stringify() — HashiCorp Configuration Language
semver "semver" semver_parse(), semver_compare(), semver_gt/lt/eq/gte/lte(), semver_sort(), semver_bump_major/minor/patch(), semver_satisfies()
table "table" Structured Markdown table manipulation — tables(), add_row(), add_column(), filter_rows(), sort_rows(), to_csv()
fuzzy "fuzzy" Fuzzy string matching — levenshtein(), jaro(), jaro_winkler(), fuzzy_match(), fuzzy_filter()
cbor "cbor" cbor_parse() (base64 or raw bytes), cbor_stringify()
toon "toon" TOON format — toon_parse(), toon_stringify()
ast "ast" mq AST introspection — get_args(), to_code()
test "test" Testing framework — assert_eq(), assert_true(), run_tests(), test_case()

Module examples

Parse a JSON code block and reformat it:

using var engine = new MqEngine();
engine.LoadModule("json");

string markdown = """
    # Config

    ```json
    {"name":"acme","version":"1.0.0"}
    ```
    """;

// Extract the JSON code block, parse it, then stringify
var result = engine.Eval(
    """.code("json") | to_text | json_parse | json_stringify""",
    markdown);
// result[0] → "{\"name\": \"acme\", \"version\": \"1.0.0\"}"

Collect all headings as a table of contents:

using var engine = new MqEngine();
engine.LoadModule("section");

var toc = engine.Eval("sections(.) | toc", markdown);
// result → ["- Introduction", "  - Background", "- Usage", ...]

Check if a version matches a range:

using var engine = new MqEngine();
engine.ImportModule("semver");

var result = engine.Eval(
    """semver::semver_satisfies("2.3.1", ">=2.0.0,<3.0.0")""",
    "ignored", InputFormat.Text);
// result[0] → "true"

Build Markdown programmatically:

using var engine = new MqEngine();
engine.LoadModule("md");

var result = engine.Eval(
    """doc(h("Hello", 1), text("World"), code("let x = 1;", "rust"))""",
    "ignored", InputFormat.Text);
// result[0] → "# Hello\n\nWorld\n\n```rust\nlet x = 1;\n```"

User-defined modules

Load .mq files from the file system with SetSearchPaths:

using var engine = new MqEngine();
engine.SetSearchPaths(["/path/to/my/modules"]);

// Load myutils.mq — functions available in global scope
engine.LoadModule("myutils");

// Import myutils.mq — functions available as myutils::fn_name()
engine.ImportModule("myutils");

Input Formats

Format Description
InputFormat.Markdown CommonMark / GFM Markdown (default)
InputFormat.Mdx Markdown with JSX (MDX)
InputFormat.Html HTML — auto-converted to Markdown before querying
InputFormat.Text Plain text, split by lines
InputFormat.Raw Raw string, no parsing

Requirements

  • .NET 8 or .NET 10
  • Supported platforms: Windows x64/ARM64, Linux x64/ARM64, macOS x64/ARM64

mq Query Language

MQNet wraps the mq Rust library. For the full query language reference, see the mq documentation.

Some common queries:

.h          # all headings
.h(1)       # H1 headings only
.h(2)       # H2 headings only
.code       # all code blocks
.code("go") # code blocks with language "go"
.text       # paragraphs / text nodes
.link       # links
.image      # images
.list       # list items

# Combinators
.h | select(contains("API"))         # headings containing "API"
.h | map(ascii_downcase)             # lowercase all headings
.code | select(startswith("fn "))    # code blocks starting with "fn "

# Plain text (strip Markdown formatting)
.h | to_text                         # heading text without # markers
.h(1) | to_text                      # H1 text only, no formatting

License

MIT — see LICENSE.

This project wraps mq by harehare, which is also MIT licensed.

About

.NET Wrapping for MQ project

Topics

Resources

License

Stars

3 stars

Watchers

1 watching

Forks

Contributors