Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

zig-codeblocks

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

zig-codeblocks - pypi Package Compare versions

Comparing version
0.2.3
to
0.3.0
+273
.github/workflows/release.yml
# This file is autogenerated by maturin v1.8.3
# To update, run
#
# maturin generate-ci --pytest github
#
name: Release
on:
push:
tags:
- '*'
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
linux:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: ubuntu-22.04
target: x86_64
- runner: ubuntu-22.04
target: aarch64
- runner: ubuntu-22.04
target: armv7
- runner: ubuntu-22.04
target: s390x
- runner: ubuntu-22.04
target: ppc64le
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist
sccache: false
manylinux: 2_28
- name: Build free-threaded wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist -i python3.13t
sccache: false
manylinux: 2_28
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-linux-${{ matrix.platform.target }}
path: dist
- name: pytest
if: ${{ startsWith(matrix.platform.target, 'x86_64') }}
shell: bash
run: |
set -e
python3 -m venv .venv
source .venv/bin/activate
pip install zig-codeblocks --find-links dist --force-reinstall
pip install pytest
pytest
- name: pytest
if: ${{ !startsWith(matrix.platform.target, 'x86') && matrix.platform.target != 'ppc64' }}
uses: uraimo/run-on-arch-action@v2
with:
arch: ${{ matrix.platform.target }}
distro: ubuntu22.04
githubToken: ${{ github.token }}
install: |
apt-get update
apt-get install -y --no-install-recommends python3 python3-pip
pip3 install -U pip pytest
run: |
set -e
pip3 install zig-codeblocks --find-links dist --force-reinstall
pytest
musllinux:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: ubuntu-22.04
target: x86_64
- runner: ubuntu-22.04
target: x86
- runner: ubuntu-22.04
target: aarch64
- runner: ubuntu-22.04
target: armv7
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
manylinux: musllinux_1_2
- name: Build free-threaded wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist -i python3.13t
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
manylinux: musllinux_1_2
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-musllinux-${{ matrix.platform.target }}
path: dist
- name: pytest
if: ${{ startsWith(matrix.platform.target, 'x86_64') }}
uses: addnab/docker-run-action@v3
with:
image: alpine:latest
options: -v ${{ github.workspace }}:/io -w /io
run: |
set -e
apk add py3-pip py3-virtualenv
python3 -m virtualenv .venv
source .venv/bin/activate
pip install zig-codeblocks --no-index --find-links dist --force-reinstall
pip install pytest
pytest
- name: pytest
if: ${{ !startsWith(matrix.platform.target, 'x86') }}
uses: uraimo/run-on-arch-action@v2
with:
arch: ${{ matrix.platform.target }}
distro: alpine_latest
githubToken: ${{ github.token }}
install: |
apk add py3-virtualenv
run: |
set -e
python3 -m virtualenv .venv
source .venv/bin/activate
pip install pytest
pip install zig-codeblocks --find-links dist --force-reinstall
pytest
windows:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: windows-latest
target: x64
- runner: windows-latest
target: x86
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
architecture: ${{ matrix.platform.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
- name: Build free-threaded wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist -i python3.13t
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-windows-${{ matrix.platform.target }}
path: dist
- name: pytest
if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
shell: bash
run: |
set -e
python3 -m venv .venv
source .venv/Scripts/activate
pip install zig-codeblocks --find-links dist --force-reinstall
pip install pytest
pytest
macos:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: macos-13
target: x86_64
- runner: macos-14
target: aarch64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
- name: Build free-threaded wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist -i python3.13t
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-macos-${{ matrix.platform.target }}
path: dist
- name: pytest
run: |
set -e
python3 -m venv .venv
source .venv/bin/activate
pip install zig-codeblocks --find-links dist --force-reinstall
pip install pytest
pytest
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: dist
release:
name: Release
runs-on: ubuntu-latest
if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
needs: [linux, musllinux, windows, macos, sdist]
permissions:
# Use to sign the release artifacts
id-token: write
# Used to upload release artifacts
contents: write
# Used to generate artifact attestation
attestations: write
steps:
- uses: actions/download-artifact@v4
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-path: 'wheels-*/*'
- name: Publish to PyPI
if: ${{ startsWith(github.ref, 'refs/tags/') }}
uses: PyO3/maturin-action@v1
with:
command: upload
args: --non-interactive --skip-existing wheels-*/*
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "cc"
version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "indexmap"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "indoc"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pyo3"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f1c6c3591120564d64db2261bec5f910ae454f01def849b9c22835a84695e86"
dependencies = [
"cfg-if",
"indoc",
"libc",
"memoffset",
"once_cell",
"portable-atomic",
"pyo3-build-config",
"pyo3-ffi",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-build-config"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9b6c2b34cf71427ea37c7001aefbaeb85886a074795e35f161f5aecc7620a7a"
dependencies = [
"once_cell",
"python3-dll-a",
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5507651906a46432cdda02cd02dd0319f6064f1374c9147c45b978621d2c3a9c"
dependencies = [
"libc",
"pyo3-build-config",
]
[[package]]
name = "pyo3-macros"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d394b5b4fd8d97d48336bb0dd2aebabad39f1d294edd6bcd2cccf2eefe6f42"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd72da09cfa943b1080f621f024d2ef7e2773df7badd51aa30a2be1f8caa7c8e"
dependencies = [
"heck",
"proc-macro2",
"pyo3-build-config",
"quote",
"syn",
]
[[package]]
name = "python3-dll-a"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49fe4227a288cf9493942ad0220ea3f185f4d1f2a14f197f7344d6d02f4ed4ed"
dependencies = [
"cc",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"indexmap",
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "target-lexicon"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
[[package]]
name = "tree-sitter"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ac5ea5e7f2f1700842ec071401010b9c59bf735295f6e9fa079c3dc035b167"
dependencies = [
"cc",
"regex",
"regex-syntax",
"serde_json",
"streaming-iterator",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-language"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8"
[[package]]
name = "tree-sitter-zig"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab11fc124851b0db4dd5e55983bbd9631192e93238389dcd44521715e5d53e28"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unindent"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
[[package]]
name = "zig_codeblocks"
version = "0.3.0"
dependencies = [
"pyo3",
"regex",
"serde",
"serde_json",
"tree-sitter",
"tree-sitter-zig",
]
[package]
name = "zig_codeblocks"
version = "0.3.0"
edition = "2021"
[lib]
name = "_core"
# "cdylib" is necessary to produce a shared library for Python to import from.
crate-type = ["cdylib"]
[dependencies]
# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so)
# "abi3-py39" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.9
pyo3 = { version = "0.24.0", features = ["extension-module", "abi3-py39", "generate-import-lib"] }
regex = "1.11.1"
tree-sitter = "0.25.3"
tree-sitter-zig = "1.1.2"
[dev-dependencies]
serde_json = "1.0.140"
serde = { version = "1.0.219", features = ["derive"] }
unstable_features = true
group_imports = "StdExternalCrate"
imports_granularity = "Crate"
reorder_imports = true
reorder_modules = true
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
hash::{DefaultHasher, Hash, Hasher},
};
use pyo3::{exceptions::PyValueError, prelude::*};
use crate::{
parse::{extract_codeblocks, tokenize_zig, Token},
style::{Style, Theme, TokenType},
};
pub const RESET: &str = "\x1b[0m";
const CODEBLOCK_START: &[u8] = b"```zig\n";
const CODEBLOCK_END: &[u8] = b"```";
const KEYWORDS: [&str; 49] = [
"addrspace",
"align",
"allowzero",
"and",
"anyframe",
"anytype",
"asm",
"async",
"await",
"break",
"callconv",
"catch",
"comptime",
"const",
"continue",
"defer",
"else",
"enum",
"errdefer",
"error",
"export",
"extern",
"fn",
"for",
"if",
"inline",
"linksection",
"noalias",
"noinline",
"nosuspend",
"opaque",
"or",
"orelse",
"packed",
"pub",
"resume",
"return",
"struct",
"suspend",
"switch",
"test",
"threadlocal",
"try",
"union",
"unreachable",
"usingnamespace",
"var",
"volatile",
"while",
];
const IDENTIFIERS: [&str; 2] = ["identifier", "c"];
const NUMERIC_LITERALS: [&str; 2] = ["integer", "float"];
const PRIMITIVE_VALUES: [&str; 4] = ["true", "false", "null", "undefined"];
const STRINGLIKE: [&str; 5] = [
"string_content",
"multiline_string",
"\"",
"'",
"character_content",
];
const TYPES: [&str; 26] = [
"anyerror",
"anyopaque",
"bool",
"builtin_type",
"c_char",
"c_int",
"c_long",
"c_longdouble",
"c_longlong",
"c_short",
"c_uint",
"c_ulong",
"c_ulonglong",
"c_ushort",
"comptime_float",
"comptime_int",
"f128",
"f16",
"f32",
"f64",
"f80",
"isize",
"noreturn",
"type",
"usize",
"void",
];
#[derive(Clone, PartialEq, Eq)]
enum Frag<'a> {
Text(Cow<'a, str>),
Sgr(Style),
}
impl std::fmt::Display for Frag<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Self::Text(t) => t.to_string(),
Self::Sgr(s) => s.to_string(),
};
write!(f, "{str}")
}
}
fn last_applied_style<'a>(body: &[Frag<'a>]) -> Option<Frag<'a>> {
body.iter()
.rev()
.find(|item| match item {
Frag::Text(s) => s == RESET,
Frag::Sgr(_) => true,
})
.cloned()
}
fn get_style<'a>(kind: &str, theme: &'a Theme) -> Option<&'a Style> {
let variants: [(&[&str], TokenType); 6] = [
(&IDENTIFIERS, TokenType::Identifier),
(&KEYWORDS, TokenType::Keyword),
(&NUMERIC_LITERALS, TokenType::Numeric),
(&PRIMITIVE_VALUES, TokenType::PrimitiveValue),
(&STRINGLIKE, TokenType::String),
(&TYPES, TokenType::Type),
];
for (options, token_type) in variants {
if options.contains(&kind) {
return theme.get(&token_type);
}
}
match kind {
"comment" => theme.get(&TokenType::Comment),
"builtin_identifier" => theme.get(&TokenType::BuiltinIdentifier),
_ => None,
}
}
fn skip_to_token(source: &[u8], pointer: &mut usize, body: &mut Vec<Frag>, current_token: &Token) {
let filler = String::from_utf8_lossy(&source[*pointer..current_token.byte_range.0]).to_string();
let last_style = last_applied_style(body);
match (filler.trim().is_empty(), last_style) {
(
true,
Some(Frag::Sgr(
Style {
underline: true, ..
}
| Style { bold: true, .. },
)),
)
| (false, Some(Frag::Sgr(Style { .. }))) => body.push(Frag::Text(RESET.into())),
_ => (),
};
body.push(Frag::Text(filler.into()));
*pointer = current_token.byte_range.0;
}
fn check_end_index(body: &[Frag], idx: usize, target: &Frag) -> bool {
body.len()
.checked_sub(idx)
.is_some_and(|idx| body[idx] == *target)
}
fn adjust_string_idents(body: &mut Vec<Frag>, token: &Token, theme: &Theme) {
// Special case for `whatever.@"something here"`,
// which is an identifier, so should have no string highlighting
let identifier_style = theme.get(&TokenType::Identifier);
let string_style = theme.get(&TokenType::String);
if !((identifier_style.is_some() || string_style.is_some()) && token.kind == "\"") {
return;
}
let at = Frag::Text("@".into());
if let Some(style) = string_style {
if last_applied_style(body) == Some(Frag::Sgr(*style)) && check_end_index(body, 4, &at) {
let idx = body.len() - 3;
if let Some(style) = identifier_style {
body[idx] = Frag::Sgr(*style);
} else {
body.remove(idx);
}
}
} else if check_end_index(body, 3, &at) {
body.insert(
body.len() - 2,
Frag::Sgr(*identifier_style.expect("guaranteed by prior logic")),
);
}
}
fn process_zig_tokens(source: &[u8], tokens: Vec<Token>, theme: &Theme) -> String {
let mut body: Vec<Frag> = Vec::with_capacity(tokens.len() * 2);
let mut pointer = 0;
let mut tokens = tokens.into_iter().peekable();
let Some(mut token) = tokens.next() else {
return String::from_utf8_lossy(source).into();
};
while pointer < source.len() {
if token.byte_range.0 > pointer {
skip_to_token(source, &mut pointer, &mut body, &token);
continue;
}
let style =
if token.kind == "identifier" && tokens.peek().map(|v| v.kind == "(") == Some(true) {
theme.get(&TokenType::Call)
} else {
get_style(&token.kind, theme)
};
match style {
None => {
if matches!(last_applied_style(&body), Some(Frag::Sgr(_))) {
body.push(Frag::Text(RESET.into()));
}
}
Some(style) => {
if last_applied_style(&body) != Some(Frag::Sgr(*style)) {
body.push(Frag::Sgr(*style));
}
}
}
adjust_string_idents(&mut body, &token, theme);
body.push(Frag::Text(Cow::Owned(
String::from_utf8_lossy(&token.value).into_owned(),
)));
pointer = token.byte_range.1;
if let Some(next_token) = tokens.next() {
token = next_token;
} else {
break;
}
}
body.into_iter().map(|x| x.to_string()).collect::<String>()
}
pub fn build_theme(theme: HashMap<String, Style>) -> PyResult<Theme> {
theme
.into_iter()
.map(|(k, v)| (TokenType::try_from(k), v))
.map(|(res, u)| res.map(|t| (t, u)))
.collect::<Result<HashMap<_, _>, _>>()
.map_err(|key| PyValueError::new_err(format!("Invalid theme key: {key:?}")))
}
pub fn highlight_zig_code(source: &[u8], theme: &Theme) -> String {
process_zig_tokens(source, tokenize_zig(source), theme)
}
fn get_body_hash(body: &[u8]) -> u64 {
let mut hasher = DefaultHasher::new();
body.hash(&mut hasher);
hasher.finish()
}
pub fn process_markdown(source: &[u8], theme: &Theme, only_code: bool) -> String {
let zig_codeblocks = extract_codeblocks(source)
.into_iter()
.filter_map(|cb| (cb.lang == Some("zig".into())).then_some(cb.body.as_bytes().to_vec()));
if only_code {
return zig_codeblocks
.map(|cb| format!("```ansi\n{}\n```", highlight_zig_code(&cb, theme)))
.collect::<Vec<_>>()
.join("\n");
}
// Deduplicating the codeblocks
let mut seen = HashSet::new();
let zig_codeblocks = zig_codeblocks.filter(|item| seen.insert(get_body_hash(item)));
let mut new_source = source.to_vec();
for cb in zig_codeblocks {
let mut needle = Vec::with_capacity(CODEBLOCK_START.len() + cb.len() + CODEBLOCK_END.len());
needle.extend_from_slice(CODEBLOCK_START);
needle.extend_from_slice(&cb);
needle.extend_from_slice(CODEBLOCK_END);
let start_positions = new_source
.windows(needle.len())
.enumerate()
.filter_map(|(i, w)| if w == needle { Some(i) } else { None })
.rev()
.collect::<Vec<_>>();
let mut highlighted = highlight_zig_code(&cb, theme);
highlighted.insert_str(0, "```ansi\n");
highlighted.push_str("\n```");
let highlighted_bytes = highlighted.as_bytes();
let end_pos_offset = needle.len();
for start_pos in start_positions {
new_source.splice(
start_pos..start_pos + end_pos_offset,
highlighted_bytes.to_vec(),
);
}
}
String::from_utf8_lossy(&new_source).to_string()
}
use std::collections::HashMap;
use pyo3::prelude::*;
mod format;
mod parse;
mod style;
#[derive(FromPyObject)]
pub enum StrOrBytes {
#[pyo3(transparent)]
Str(String),
#[pyo3(transparent)]
Bytes(Vec<u8>),
}
impl StrOrBytes {
fn resolve(&self) -> &[u8] {
match self {
Self::Str(s) => s.as_bytes(),
Self::Bytes(b) => b,
}
}
}
/// Return an ANSI syntax-highlighted version of the given Zig source code. Assumes UTF-8.
#[pyfunction]
#[pyo3(signature = (source, theme=style::DEFAULT_THEME_PREPROCESSED.clone()))]
pub fn highlight_zig_code(
source: StrOrBytes,
theme: HashMap<String, style::Style>,
) -> PyResult<String> {
Ok(format::highlight_zig_code(
source.resolve(),
&format::build_theme(theme)?,
))
}
/// Return a Markdown source with Zig code blocks syntax-highlighted.
/// If `only_code` is True, only processed Zig code blocks will be returned. Assumes UTF-8.
#[pyfunction]
#[pyo3(signature = (source, theme=style::DEFAULT_THEME_PREPROCESSED.clone(), *, only_code=false))]
pub fn process_markdown(
source: StrOrBytes,
theme: HashMap<String, style::Style>,
only_code: bool,
) -> PyResult<String> {
Ok(format::process_markdown(
source.resolve(),
&format::build_theme(theme)?,
only_code,
))
}
/// Return CodeBlocks from a Markdown source. Assumes UTF-8.
#[pyfunction]
pub fn extract_codeblocks(source: StrOrBytes) -> Vec<parse::CodeBlock> {
parse::extract_codeblocks(source.resolve())
}
/// A Python module implemented in Rust. The name of this function must match
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
/// import the module.
#[pymodule]
fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(extract_codeblocks, m)?)?;
m.add_function(wrap_pyfunction!(highlight_zig_code, m)?)?;
m.add_function(wrap_pyfunction!(process_markdown, m)?)?;
m.add_class::<parse::CodeBlock>()?;
m.add_class::<style::Color>()?;
m.add_class::<style::Style>()?;
Ok(())
}
use std::{borrow::Cow, sync::LazyLock};
use pyo3::prelude::*;
use regex::bytes::{Match, Regex};
#[derive(Debug, PartialEq, Eq)]
pub struct Token<'a> {
pub kind: Cow<'a, str>,
pub value: Cow<'a, [u8]>,
pub byte_range: (usize, usize),
}
/// A code block extracted from a Markdown source.
#[pyclass(eq, get_all, module = "zig_codeblocks._core")]
#[derive(PartialEq, Eq)]
pub struct CodeBlock {
pub lang: Option<String>,
pub body: String,
}
#[pymethods]
impl CodeBlock {
#[new]
pub fn new(lang: Option<String>, body: &str) -> Self {
Self {
lang,
body: body.to_owned(),
}
}
fn __repr__(&self) -> String {
let lang_repr = if let Some(lang) = &self.lang {
format!("{lang:?}")
} else {
String::from("None")
};
format!("CodeBlock(lang={lang_repr}, body={:?})", self.body)
}
fn __str__(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for CodeBlock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(lang) = &self.lang {
write!(f, "```{lang}\n{}```", self.body)
} else {
write!(f, "```{}```", self.body)
}
}
}
static CODE_BLOCK_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?s)```(?:([A-Za-z0-9\-_\+\.#]+)(?:\r?\n)+([^\r\n].*?)|(.*?))```")
.expect("guaranteed to be valid")
});
fn get_parser() -> tree_sitter::Parser {
let mut parser = tree_sitter::Parser::new();
let language = tree_sitter_zig::LANGUAGE;
parser
.set_language(&language.into())
.expect("tree-sitter-zig should work correctly");
parser
}
pub fn tokenize_zig(src: &[u8]) -> Vec<Token> {
let mut parser = get_parser();
let tree = parser
.parse(src, None)
.expect("should be Some, the parser was assigned a language");
traverse(tree.root_node(), src)
}
fn traverse<'a>(root: tree_sitter::Node, src: &'a [u8]) -> Vec<Token<'a>> {
let mut tokens = Vec::new();
let mut stack = vec![root];
while let Some(node) = stack.pop() {
if node.child_count() > 0 {
stack.extend(
(0..node.child_count())
.rev()
.map(|i| node.child(i).expect("should be in-bounds")),
);
continue;
}
tokens.push(Token {
kind: Cow::Borrowed(node.kind()),
value: Cow::Borrowed(&src[node.start_byte()..node.end_byte()]),
byte_range: (node.start_byte(), node.end_byte()),
});
}
tokens
}
fn match_to_utf8(r#match: Match<'_>) -> Cow<'_, str> {
String::from_utf8_lossy(r#match.as_bytes())
}
pub fn extract_codeblocks(source: &[u8]) -> Vec<CodeBlock> {
CODE_BLOCK_PATTERN
.captures_iter(source)
.map(|m| {
let (lang, body, no_lang_body) = (m.get(1), m.get(2), m.get(3));
if lang.is_some() {
CodeBlock::new(
lang.map(|m| match_to_utf8(m).to_string()),
&match_to_utf8(body.expect("should be present when lang is present")),
)
} else {
CodeBlock::new(
None,
&match_to_utf8(no_lang_body.expect("should be present when lang is absent")),
)
}
})
.collect()
}
#[cfg(test)]
mod tests {
use std::fs::read_to_string;
use super::{tokenize_zig, Cow, Token};
#[derive(serde::Serialize, serde::Deserialize)]
struct OutputToken {
kind: String,
value: Option<String>,
range: (usize, usize),
}
macro_rules! make_test {
($name:ident) => {
#[test]
fn $name() {
let src_path = concat!("tests/sources/zig_inputs/", stringify!($name), ".zig");
let out_path =
concat!("tests/sources/parsing_results/", stringify!($name), ".json");
let source = read_to_string(src_path)
.expect("the file should be valid")
.replace("\r\n", "\n")
.into_bytes();
let out_json = read_to_string(out_path).expect("the file should be valid");
assert_eq!(tokenize_zig(&source), read_expected_tokens(&out_json));
}
};
}
fn read_expected_tokens(content: &str) -> Vec<Token> {
let t: Vec<OutputToken> =
serde_json::from_str(content).expect("the output json should be valid");
t.into_iter()
.map(|ot| Token {
value: Cow::Owned(ot.value.unwrap_or(ot.kind.clone()).as_bytes().to_vec()),
kind: Cow::Owned(ot.kind),
byte_range: ot.range,
})
.collect::<Vec<_>>()
}
make_test!(assign_undefined);
make_test!(comments);
make_test!(emoji);
make_test!(global_assembly);
make_test!(hello_again);
make_test!(identifiers);
}
use std::{collections::HashMap, sync::LazyLock};
use pyo3::{exceptions::PyValueError, prelude::*};
/// An enumeration of 3-bit ANSI colors.
/// Some names were adjusted to match Discord's style.
#[pyclass(eq, eq_int, module = "zig_codeblocks._core")]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Color {
Gray,
Red,
Green,
Orange,
Blue,
Magenta,
Cyan,
White,
}
#[pymethods]
impl Color {
#[staticmethod]
fn from_string(value: &str) -> PyResult<Self> {
match &value.to_lowercase()[..] {
"gray" => Ok(Self::Gray),
"red" => Ok(Self::Red),
"green" => Ok(Self::Green),
"orange" => Ok(Self::Orange),
"blue" => Ok(Self::Blue),
"magenta" => Ok(Self::Magenta),
"cyan" => Ok(Self::Cyan),
"white" => Ok(Self::White),
_ => Err(PyValueError::new_err(format!("Invalid color: {value:?}"))),
}
}
}
impl Color {
const fn code(self) -> &'static str {
match self {
Self::Gray => "30",
Self::Red => "31",
Self::Green => "32",
Self::Orange => "33",
Self::Blue => "34",
Self::Magenta => "35",
Self::Cyan => "36",
Self::White => "37",
}
}
}
const fn python_bool_repr(value: bool) -> &'static str {
if value { "True" } else { "False" }
}
/// A style for syntax highlighting.
/// Takes a `Color` and can optionally be bold and/or underlined.
/// Produces an SGR sequence when converted to a string.
#[pyclass(eq, module = "zig_codeblocks._core")]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct Style {
#[pyo3(get, set)]
color: Color,
#[pyo3(get, set)]
pub bold: bool,
#[pyo3(get, set)]
pub underline: bool,
}
#[pymethods]
impl Style {
#[new]
#[pyo3(signature = (color, *, bold=false, underline=false))]
fn new(color: Color, bold: bool, underline: bool) -> Self {
Self {
color,
bold,
underline,
}
}
#[staticmethod]
fn from_string(value: &str) -> PyResult<Self> {
let mut color: Option<Color> = None;
let mut bold = false;
let mut underline = false;
for part in value.to_lowercase().split('+') {
match part {
"bold" => bold = true,
"underline" => underline = true,
c => {
let Ok(valid_color) = Color::from_string(c) else {
return Err(PyValueError::new_err(format!("Invalid color {c:?}")));
};
if let Some(applied) = color {
return Err(PyValueError::new_err(format!(
"Multiple colors found: {applied:?} and {valid_color:?}"
)));
}
color = Some(valid_color);
}
}
}
if let Some(color) = color {
Ok(Self {
color,
bold,
underline,
})
} else {
Err(PyValueError::new_err("Missing color in input string"))
}
}
fn __repr__(&self) -> String {
format!(
"Style({}, bold={}, underline={})",
self.color.__pyo3__repr__(),
python_bool_repr(self.bold),
python_bool_repr(self.underline)
)
}
fn __str__(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for Style {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let values = [self.color.code(), "1", "4"]
.iter()
.zip([true, self.bold, self.underline])
.filter_map(|(code, enabled)| enabled.then_some(*code))
.collect::<Vec<_>>();
write!(f, "\x1b[{}m", values.join(";"))
}
}
impl From<Color> for Style {
fn from(color: Color) -> Self {
Self {
color,
bold: false,
underline: false,
}
}
}
#[derive(Hash, Eq, PartialEq, Clone, Copy)]
pub enum TokenType {
BuiltinIdentifier,
Call,
Comment,
Identifier,
Keyword,
Numeric,
String,
PrimitiveValue,
Type,
}
impl TryFrom<String> for TokenType {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"BuiltinIdentifier" => Ok(Self::BuiltinIdentifier),
"Call" => Ok(Self::Call),
"Comment" => Ok(Self::Comment),
"Identifier" => Ok(Self::Identifier),
"Keyword" => Ok(Self::Keyword),
"Numeric" => Ok(Self::Numeric),
"String" => Ok(Self::String),
"PrimitiveValue" => Ok(Self::PrimitiveValue),
"Type" => Ok(Self::Type),
_ => Err(value),
}
}
}
pub type Theme = HashMap<TokenType, Style>;
pub static DEFAULT_THEME_PREPROCESSED: LazyLock<HashMap<String, Style>> = LazyLock::new(|| {
HashMap::from([
(
"BuiltinIdentifier".into(),
Style {
color: Color::Blue,
bold: true,
underline: false,
},
),
("Call".into(), Style::from(Color::Blue)),
("Comment".into(), Style::from(Color::Gray)),
("Keyword".into(), Style::from(Color::Magenta)),
("Numeric".into(), Style::from(Color::Cyan)),
("PrimitiveValue".into(), Style::from(Color::Cyan)),
("String".into(), Style::from(Color::Green)),
("Type".into(), Style::from(Color::Orange)),
])
});
import sys
from pathlib import Path
from typing import Annotated, Optional, Union, cast
import zig_codeblocks as zc
try:
import typer
except ModuleNotFoundError:
sys.exit(
"Cannot launch the CLI (failed to import typer)."
" Make sure you have the [cli] extra installed."
)
PATH_HELP_TEXT = "Path to read the {} source from. Reads from STDIN if omitted."
THEME_OPTION = typer.Option(
"-t",
"--theme",
metavar="THEME",
help="An optional custom theme to use for Zig highlighting.",
)
THEME_OVERRIDE_OPTION = typer.Option(
"-o",
"--theme-override",
metavar="THEME",
help="Optional changes to apply to the default theme for Zig highlighting.",
)
app = typer.Typer()
def read_in(path: Optional[Path]) -> str:
if path is None:
return sys.stdin.read()
return path.read_text()
def parse_theme_config(config: str) -> dict[str, Union[str, zc.Style]]:
theme = {}
for token_type, style in (item.split(":") for item in config.split(",")):
if token_type not in zc.Theme.__optional_keys__:
msg = f"invalid token type: {token_type!r}"
raise ValueError(msg)
theme[token_type] = style if style == "none" else zc.Style.from_string(style)
return theme
def resolve_theme(theme: Optional[str], theme_overrides: Optional[str]) -> zc.Theme:
if theme is None:
if theme_overrides is None:
return zc.DEFAULT_THEME
new_theme = zc.DEFAULT_THEME.copy()
for k, v in parse_theme_config(theme_overrides).items():
if v == "none":
del new_theme[k] # type: ignore[misc]
else:
new_theme[k] = v # type: ignore[literal-required]
return new_theme
if theme_overrides is None:
return cast(zc.Theme, parse_theme_config(theme))
msg = "can't provide --theme and --theme-overrides at once"
raise ValueError(msg)
@app.command(name="zig")
def process_zig(
path: Annotated[
Optional[Path],
typer.Argument(help=PATH_HELP_TEXT.format("Zig")),
] = None,
theme: Annotated[Optional[str], THEME_OPTION] = None,
theme_overrides: Annotated[Optional[str], THEME_OVERRIDE_OPTION] = None,
) -> None:
print(zc.highlight_zig_code(read_in(path), resolve_theme(theme, theme_overrides)))
@app.command(name="markdown")
def process_markdown(
path: Annotated[
Optional[Path],
typer.Argument(help=PATH_HELP_TEXT.format("Markdown")),
] = None,
only_code: Annotated[
bool,
typer.Option(
"-c",
"--only-code",
help="If true, only Zig codeblocks will be included in the output.",
),
] = False,
theme: Annotated[Optional[str], THEME_OPTION] = None,
theme_overrides: Annotated[Optional[str], THEME_OVERRIDE_OPTION] = None,
) -> None:
theme_ = resolve_theme(theme, theme_overrides)
print(zc.process_markdown(read_in(path), theme_, only_code=only_code))
@app.command(name="codeblocks")
def extract_codeblocks(
path: Annotated[
Optional[Path],
typer.Argument(help=PATH_HELP_TEXT.format("Markdown")),
] = None,
langs: Annotated[
Optional[str],
typer.Option(
"-l",
"--langs",
metavar="LANGS",
help=(
"A comma-separated list of languages to include."
" Includes all if unspecified."
),
),
] = None,
) -> None:
allowed_langs = set((langs or "").split(","))
for cb in zc.extract_codeblocks(read_in(path)):
if langs is None:
print(cb)
continue
if cb.lang in allowed_langs:
print(cb)
if __name__ == "__main__":
app()
from enum import Enum
from typing import TypedDict
class CodeBlock:
"""A code block extracted from a Markdown source."""
lang: str | None
body: str
def __init__(self, lang: str | None, body: str) -> None: ...
class Color(Enum):
"""
An enumeration of 3-bit ANSI colors.
Some names were adjusted to match Discord's style.
"""
Gray = ...
Red = ...
Green = ...
Orange = ...
Blue = ...
Magenta = ...
Cyan = ...
White = ...
@staticmethod
def from_string(color: str) -> Color: ...
class Style:
"""
A style for syntax highlighting.
Takes a `Color` and can optionally be bold and/or underlined.
Produces an SGR sequence when converted to a string.
"""
color: Color
bold: bool
underline: bool
def __init__(
self, color: Color, *, bold: bool = False, underline: bool = False
) -> None: ...
@staticmethod
def from_string(value: str) -> Style: ...
class Theme(TypedDict, total=False):
BuiltinIdentifier: Style
Call: Style
Comment: Style
Identifier: Style
Keyword: Style
Numeric: Style
PrimitiveValue: Style
String: Style
Type: Style
def highlight_zig_code(source: str | bytes, theme: Theme = ...) -> str:
"""
Return an ANSI syntax-highlighted version of the given Zig source code.
Assumes UTF-8.
"""
def process_markdown(
source: str | bytes, theme: Theme = ..., *, only_code: bool = False
) -> str:
"""
Return a Markdown source with Zig code blocks syntax-highlighted.
If `only_code` is True, only processed Zig code blocks will be returned.
Assumes UTF-8.
"""
def extract_codeblocks(source: str | bytes) -> list[CodeBlock]:
"""Return CodeBlocks from a Markdown source. Assumes UTF-8."""
from zig_codeblocks import highlight_zig_code
print(highlight_zig_code("const stdip@0'sirjgoicjgoii gueve up"))
+26
-9

@@ -11,14 +11,31 @@ name: CI

PY_COLORS: 1
UV_VERSION: 0.5.28
UV_VERSION: 0.6.7
CARGO_TERM_COLOR: always
jobs:
ci:
rust-ci:
runs-on: "${{ matrix.platform }}-latest"
strategy:
fail-fast: false
matrix:
platform: ["ubuntu", "macos", "windows"]
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Run Clippy
run: cargo clippy --verbose
- name: Run Rust tests (parsing)
run: cargo test --verbose
python-ci:
runs-on: "${{ matrix.platform }}-latest"
strategy:
fail-fast: false
# https://blog.jaraco.com/efficient-use-of-ci-resources/
matrix:
python: ["3.10", "3.13"]
python: ["3.9", "3.13"]
platform: ["ubuntu", "macos", "windows"]
include:
- python: "3.10"
platform: "ubuntu"
- python: "3.11"

@@ -35,12 +52,12 @@ platform: "ubuntu"

- name: Install project
run: uv sync -p ${{ matrix.python }}
- name: Run tests (with coverage)
run: uv run pytest --cov src --cov-report term-missing
run: uv sync -p ${{ matrix.python }} --extra cli
- name: Run Python tests (parsing + highlighting + styling)
run: uv run -p ${{ matrix.python }} pytest
- name: Lint code
if: always()
run: |
uv run ruff check
uv run ruff format --check
uv run -p ${{ matrix.python }} ruff check
uv run -p ${{ matrix.python }} ruff format --check
- name: Run mypy
if: always()
run: uv run mypy --strict src
run: uv run -p ${{ matrix.python }} mypy --strict src tests

@@ -11,1 +11,5 @@ # Python-generated files

.venv
# Rust/PyO3-generated files
target/
*.so

@@ -8,4 +8,33 @@ # Changelog

## [v0.2.3] - 2025-02-15
## [v0.3.0] - 2025-04-15
### Added
- A command-line interface
- Python 3.9 support
- `CodeBlock` objects now return the fenced Markdown code block form when
converted to string
- `Style` objects can now be created from strings, e.g.
`Style.from_string("cyan+bold")` is the same as `Style(Color.Cyan, bold=True)`
### Changed
- Most of the parsing/highlighting logic was moved to Rust, leading to
significant speedups for both small (<300 lines) and large (>10k lines)
inputs:
- `highlight_zig_code` is 4–10x faster for small inputs and ~300x faster for
large inputs
- `process_markdown(only_code=False)` is 3–6x faster for small inputs and ~50x
faster for large inputs
- `process_markdown(only_code=True)` is ~4x faster for all inputs
- `extract_codeblocks` is ~10% faster for large inputs
- `Color`:
- can no longer be instantiated with `Color["red"]`, use
`Color.from_string("red")` instead
- members now use PascalCase names, e.g. `Color.BLUE` is now `Color.Blue`
- `extract_codeblocks`:
- no longer strips CR and LF characters
- now returns a list instead of an iterator
- `Theme` keys are now PascalCase instead of snake_case
## [v0.2.3] - 2025-02-16
### Fixed

@@ -74,1 +103,2 @@ - Fixed call-highlight checks consuming tokens instead of peeking them

[v0.2.3]: https://github.com/trag1c/zig-codeblocks/compare/v0.2.2...v0.2.3
[v0.3.0]: https://github.com/trag1c/zig-codeblocks/compare/v0.2.3...v0.3.0
+69
-50
Metadata-Version: 2.4
Name: zig-codeblocks
Version: 0.2.3
Version: 0.3.0
Requires-Dist: typer-slim>=0.15.2 ; extra == 'cli'
Provides-Extra: cli
License-File: LICENSE
Summary: Zig ANSI syntax highlighting library
Author-email: trag1c <dev@jakubr.me>
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: repository, https://github.com/trag1c/zig-codeblocks
Author-email: trag1c <trag1cdev@yahoo.com>
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: more-itertools~=10.6
Requires-Dist: tree-sitter-zig<2,>=1.1.2
Requires-Dist: tree-sitter~=0.24.0
Description-Content-Type: text/markdown

@@ -20,7 +19,18 @@ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)

`zig-codeblocks` is a CPython 3.10+ library for adding syntax highlighting to
Zig code blocks in Markdown files through ANSI escape codes.
`zig-codeblocks` is a Rust-powered CPython 3.9+ library for adding syntax
highlighting to Zig code blocks in Markdown files through ANSI escape codes.
Originally intended for patching the lack of syntax highlighting for Zig on
Discord.
- [Installation](#installation)
- [API Reference](#api-reference)
- [`extract_codeblocks`](#extract_codeblocks)
- [`highlight_zig_code`](#highlight_zig_code)
- [`process_markdown`](#process_markdown)
- [`CodeBlock`](#codeblock)
- [`Color`](#color)
- [`Style`](#style)
- [`Theme`](#theme)
- [`DEFAULT_THEME`](#default_theme)
- [License](#license)

@@ -36,2 +46,11 @@ ## Installation

```
---
`zig-codeblocks` also exposes a CLI via a `cli` extra:
```py
pip install "zig-codeblocks[cli]"
```
If the CLI is all you need, you can run it with `uvx`:
```sh
uvx "zig-codeblocks[cli]" --help
```

@@ -43,3 +62,3 @@

```py
def extract_codeblocks(source: str | bytes) -> Iterator[CodeBlock]
def extract_codeblocks(source: str | bytes) -> list[CodeBlock]
```

@@ -92,5 +111,5 @@ Yields [`CodeBlock`](#codeblock)s from a Markdown source.

theme = DEFAULT_THEME.copy()
theme.builtin_identifiers = Style(Color.ORANGE, underline=True)
theme.strings = Style(Color.CYAN)
theme.types = None
theme["BuiltinIdentifier"] = Style(Color.Orange, underline=True)
theme["String"] = Style(Color.Cyan)
del theme["Type"]

@@ -135,7 +154,7 @@ print(

```py
class CodeBlock(NamedTuple):
class CodeBlock:
lang: str
body: str
```
A code block extracted from a Markdown source.
A code block extracted from a Markdown source. Immutable.

@@ -146,13 +165,14 @@

class Color(Enum):
GRAY = "30"
RED = "31"
GREEN = "32"
ORANGE = "33"
BLUE = "34"
MAGENTA = "35"
CYAN = "36"
WHITE = "37" # Black for light mode
Gray = "30"
Red = "31"
Green = "32"
Orange = "33"
Blue = "34"
Magenta = "35"
Cyan = "36"
White = "37" # Black for light mode
```
An enumeration of 3-bit ANSI colors.
Some names were adjusted to match Discord's style.
A color can be instantiated from a string too: `Color.from_string("Blue")`.

@@ -162,6 +182,4 @@

```py
@dataclass(slots=True, frozen=True)
class Style:
color: Color
_: KW_ONLY
bold: bool = False

@@ -179,11 +197,11 @@ underline: bool = False

class Theme(TypedDict, total=False):
builtin_identifiers: Style
calls: Style
comments: Style
identifiers: Style
keywords: Style
numeric: Style
strings: Style
primitive_values: Style
types: Style
BuiltinIdentifier: Style
Call: Style
Comment: Style
Identifier: Style
Keyword: Style
Numeric: Style
PrimitiveValue: Style
String: Style
Type: Style
```

@@ -198,9 +216,9 @@ A theme dict for syntax highlighting Zig code.

theme_foo = Theme(
numeric=Style(Color.BLUE),
strings=Style(Color.GREEN),
Numeric=Style(Color.Blue),
String=Style(Color.Green),
)
theme_bar: Theme = {
"numeric": Style(Color.BLUE),
"strings": Style(Color.GREEN),
"Numeric": Style(Color.Blue),
"String": Style(Color.Green),
}

@@ -215,12 +233,12 @@

```py
DEFAULT_THEME = Theme(
builtin_identifiers=Style(Color.BLUE, bold=True),
calls=Style(Color.BLUE),
comments=Style(Color.GRAY),
keywords=Style(Color.MAGENTA),
numeric=Style(Color.CYAN),
primitive_values=Style(Color.CYAN),
strings=Style(Color.GREEN),
types=Style(Color.ORANGE),
)
DEFAULT_THEME: Theme = {
"BuiltinIdentifier": Style(Color.Blue, bold=True),
"Call": Style(Color.Blue),
"Comment": Style(Color.Gray),
"Keyword": Style(Color.Magenta),
"Numeric": Style(Color.Cyan),
"PrimitiveValue": Style(Color.Cyan),
"String": Style(Color.Green),
"Type": Style(Color.Orange),
}
```

@@ -235,1 +253,2 @@

[trag1c]: https://github.com/trag1c/
[project]
name = "zig-codeblocks"
version = "0.2.3"
version = "0.3.0"
description = "Zig ANSI syntax highlighting library"
readme = "README.md"
license = "MIT"
authors = [{ name = "trag1c", email = "trag1cdev@yahoo.com" }]
requires-python = ">=3.10"
dependencies = [
"more-itertools~=10.6",
"tree-sitter~=0.24.0",
"tree-sitter-zig>=1.1.2,<2",
]
authors = [{ name = "trag1c", email = "dev@jakubr.me" }]
requires-python = ">=3.9"
dependencies = []

@@ -18,11 +14,19 @@ [project.urls]

[project.scripts]
zig-codeblocks = "zig_codeblocks.__main__:app"
[project.optional-dependencies]
cli = ["typer-slim>=0.15.2"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[dependency-groups]
dev = ["mypy>=1.14.1", "pytest>=8.3.4", "pytest-cov>=6.0.0", "ruff>=0.9.4"]
dev = ["mypy>=1.14.1", "pytest>=8.3.4", "ruff>=0.9.4"]
[tool.coverage.report]
exclude_also = ["if TYPE_CHECKING:"]
[tool.maturin]
module-name = "zig_codeblocks._core"
python-packages = ["zig_codeblocks"]
python-source = "src"

@@ -32,11 +36,11 @@ [tool.ruff.lint]

ignore = [
"COM",
"D",
"FIX",
"ANN401",
"ISC001",
"T201",
"TD003",
"PLR2004",
"S105",
"COM",
"D",
"FIX",
"ANN401",
"ISC001",
"T201",
"TD003",
"PLR2004",
"S105",
]

@@ -46,1 +50,3 @@

"tests/*" = ["INP", "FBT", "PLC2701", "S101", "SLF001"]
"_core.pyi" = ["PYI021"]
"__main__.py" = ["FA", "TCH", "FBT002", "UP00"]
+60
-41

@@ -6,7 +6,18 @@ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)

`zig-codeblocks` is a CPython 3.10+ library for adding syntax highlighting to
Zig code blocks in Markdown files through ANSI escape codes.
`zig-codeblocks` is a Rust-powered CPython 3.9+ library for adding syntax
highlighting to Zig code blocks in Markdown files through ANSI escape codes.
Originally intended for patching the lack of syntax highlighting for Zig on
Discord.
- [Installation](#installation)
- [API Reference](#api-reference)
- [`extract_codeblocks`](#extract_codeblocks)
- [`highlight_zig_code`](#highlight_zig_code)
- [`process_markdown`](#process_markdown)
- [`CodeBlock`](#codeblock)
- [`Color`](#color)
- [`Style`](#style)
- [`Theme`](#theme)
- [`DEFAULT_THEME`](#default_theme)
- [License](#license)

@@ -22,2 +33,11 @@ ## Installation

```
---
`zig-codeblocks` also exposes a CLI via a `cli` extra:
```py
pip install "zig-codeblocks[cli]"
```
If the CLI is all you need, you can run it with `uvx`:
```sh
uvx "zig-codeblocks[cli]" --help
```

@@ -29,3 +49,3 @@

```py
def extract_codeblocks(source: str | bytes) -> Iterator[CodeBlock]
def extract_codeblocks(source: str | bytes) -> list[CodeBlock]
```

@@ -78,5 +98,5 @@ Yields [`CodeBlock`](#codeblock)s from a Markdown source.

theme = DEFAULT_THEME.copy()
theme.builtin_identifiers = Style(Color.ORANGE, underline=True)
theme.strings = Style(Color.CYAN)
theme.types = None
theme["BuiltinIdentifier"] = Style(Color.Orange, underline=True)
theme["String"] = Style(Color.Cyan)
del theme["Type"]

@@ -121,7 +141,7 @@ print(

```py
class CodeBlock(NamedTuple):
class CodeBlock:
lang: str
body: str
```
A code block extracted from a Markdown source.
A code block extracted from a Markdown source. Immutable.

@@ -132,13 +152,14 @@

class Color(Enum):
GRAY = "30"
RED = "31"
GREEN = "32"
ORANGE = "33"
BLUE = "34"
MAGENTA = "35"
CYAN = "36"
WHITE = "37" # Black for light mode
Gray = "30"
Red = "31"
Green = "32"
Orange = "33"
Blue = "34"
Magenta = "35"
Cyan = "36"
White = "37" # Black for light mode
```
An enumeration of 3-bit ANSI colors.
Some names were adjusted to match Discord's style.
A color can be instantiated from a string too: `Color.from_string("Blue")`.

@@ -148,6 +169,4 @@

```py
@dataclass(slots=True, frozen=True)
class Style:
color: Color
_: KW_ONLY
bold: bool = False

@@ -165,11 +184,11 @@ underline: bool = False

class Theme(TypedDict, total=False):
builtin_identifiers: Style
calls: Style
comments: Style
identifiers: Style
keywords: Style
numeric: Style
strings: Style
primitive_values: Style
types: Style
BuiltinIdentifier: Style
Call: Style
Comment: Style
Identifier: Style
Keyword: Style
Numeric: Style
PrimitiveValue: Style
String: Style
Type: Style
```

@@ -184,9 +203,9 @@ A theme dict for syntax highlighting Zig code.

theme_foo = Theme(
numeric=Style(Color.BLUE),
strings=Style(Color.GREEN),
Numeric=Style(Color.Blue),
String=Style(Color.Green),
)
theme_bar: Theme = {
"numeric": Style(Color.BLUE),
"strings": Style(Color.GREEN),
"Numeric": Style(Color.Blue),
"String": Style(Color.Green),
}

@@ -201,12 +220,12 @@

```py
DEFAULT_THEME = Theme(
builtin_identifiers=Style(Color.BLUE, bold=True),
calls=Style(Color.BLUE),
comments=Style(Color.GRAY),
keywords=Style(Color.MAGENTA),
numeric=Style(Color.CYAN),
primitive_values=Style(Color.CYAN),
strings=Style(Color.GREEN),
types=Style(Color.ORANGE),
)
DEFAULT_THEME: Theme = {
"BuiltinIdentifier": Style(Color.Blue, bold=True),
"Call": Style(Color.Blue),
"Comment": Style(Color.Gray),
"Keyword": Style(Color.Magenta),
"Numeric": Style(Color.Cyan),
"PrimitiveValue": Style(Color.Cyan),
"String": Style(Color.Green),
"Type": Style(Color.Orange),
}
```

@@ -213,0 +232,0 @@

@@ -1,6 +0,40 @@

from zig_codeblocks.consts import DEFAULT_THEME
from zig_codeblocks.formatting import highlight_zig_code, process_markdown
from zig_codeblocks.parsing import CodeBlock, extract_codeblocks
from zig_codeblocks.styling import Color, Style, Theme
from __future__ import annotations
from typing import TypedDict
from zig_codeblocks._core import (
CodeBlock,
Color,
Style,
extract_codeblocks,
highlight_zig_code,
process_markdown,
)
class Theme(TypedDict, total=False):
"""A theme for syntax highlighting Zig code."""
BuiltinIdentifier: Style
Call: Style
Comment: Style
Identifier: Style
Keyword: Style
Numeric: Style
PrimitiveValue: Style
String: Style
Type: Style
DEFAULT_THEME: Theme = {
"BuiltinIdentifier": Style(Color.Blue, bold=True),
"Call": Style(Color.Blue),
"Comment": Style(Color.Gray),
"Keyword": Style(Color.Magenta),
"Numeric": Style(Color.Cyan),
"PrimitiveValue": Style(Color.Cyan),
"String": Style(Color.Green),
"Type": Style(Color.Orange),
}
__all__ = (

@@ -7,0 +41,0 @@ "DEFAULT_THEME",

[
{
"kind": "const",
"start": 0,
"end": 5
"range": [0, 5]
},

@@ -10,9 +9,7 @@ {

"value": "print",
"start": 6,
"end": 11
"range": [6, 11]
},
{
"kind": "=",
"start": 12,
"end": 13
"range": [12, 13]
},

@@ -22,14 +19,11 @@ {

"value": "@import",
"start": 14,
"end": 21
"range": [14, 21]
},
{
"kind": "(",
"start": 21,
"end": 22
"range": [21, 22]
},
{
"kind": "\"",
"start": 22,
"end": 23
"range": [22, 23]
},

@@ -39,19 +33,15 @@ {

"value": "std",
"start": 23,
"end": 26
"range": [23, 26]
},
{
"kind": "\"",
"start": 26,
"end": 27
"range": [26, 27]
},
{
"kind": ")",
"start": 27,
"end": 28
"range": [27, 28]
},
{
"kind": ".",
"start": 28,
"end": 29
"range": [28, 29]
},

@@ -61,9 +51,7 @@ {

"value": "debug",
"start": 29,
"end": 34
"range": [29, 34]
},
{
"kind": ".",
"start": 34,
"end": 35
"range": [34, 35]
},

@@ -73,19 +61,15 @@ {

"value": "print",
"start": 35,
"end": 40
"range": [35, 40]
},
{
"kind": ";",
"start": 40,
"end": 41
"range": [40, 41]
},
{
"kind": "pub",
"start": 43,
"end": 46
"range": [43, 46]
},
{
"kind": "fn",
"start": 47,
"end": 49
"range": [47, 49]
},

@@ -95,29 +79,23 @@ {

"value": "main",
"start": 50,
"end": 54
"range": [50, 54]
},
{
"kind": "(",
"start": 54,
"end": 55
"range": [54, 55]
},
{
"kind": ")",
"start": 55,
"end": 56
"range": [55, 56]
},
{
"kind": "void",
"start": 57,
"end": 61
"range": [57, 61]
},
{
"kind": "{",
"start": 62,
"end": 63
"range": [62, 63]
},
{
"kind": "var",
"start": 68,
"end": 71
"range": [68, 71]
},

@@ -127,9 +105,7 @@ {

"value": "x",
"start": 72,
"end": 73
"range": [72, 73]
},
{
"kind": ":",
"start": 73,
"end": 74
"range": [73, 74]
},

@@ -139,19 +115,15 @@ {

"value": "i32",
"start": 75,
"end": 78
"range": [75, 78]
},
{
"kind": "=",
"start": 79,
"end": 80
"range": [79, 80]
},
{
"kind": "undefined",
"start": 81,
"end": 90
"range": [81, 90]
},
{
"kind": ";",
"start": 90,
"end": 91
"range": [90, 91]
},

@@ -161,9 +133,7 @@ {

"value": "x",
"start": 96,
"end": 97
"range": [96, 97]
},
{
"kind": "=",
"start": 98,
"end": 99
"range": [98, 99]
},

@@ -173,9 +143,7 @@ {

"value": "1",
"start": 100,
"end": 101
"range": [100, 101]
},
{
"kind": ";",
"start": 101,
"end": 102
"range": [101, 102]
},

@@ -185,14 +153,11 @@ {

"value": "print",
"start": 107,
"end": 112
"range": [107, 112]
},
{
"kind": "(",
"start": 112,
"end": 113
"range": [112, 113]
},
{
"kind": "\"",
"start": 113,
"end": 114
"range": [113, 114]
},

@@ -202,24 +167,19 @@ {

"value": "{d}",
"start": 114,
"end": 117
"range": [114, 117]
},
{
"kind": "\"",
"start": 117,
"end": 118
"range": [117, 118]
},
{
"kind": ",",
"start": 118,
"end": 119
"range": [118, 119]
},
{
"kind": ".",
"start": 120,
"end": 121
"range": [120, 121]
},
{
"kind": "{",
"start": 121,
"end": 122
"range": [121, 122]
},

@@ -229,25 +189,20 @@ {

"value": "x",
"start": 122,
"end": 123
"range": [122, 123]
},
{
"kind": "}",
"start": 123,
"end": 124
"range": [123, 124]
},
{
"kind": ")",
"start": 124,
"end": 125
"range": [124, 125]
},
{
"kind": ";",
"start": 125,
"end": 126
"range": [125, 126]
},
{
"kind": "}",
"start": 127,
"end": 128
"range": [127, 128]
}
]
]
[
{
"kind": "const",
"start": 0,
"end": 5
"range": [0, 5]
},

@@ -10,9 +9,7 @@ {

"value": "print",
"start": 6,
"end": 11
"range": [6, 11]
},
{
"kind": "=",
"start": 12,
"end": 13
"range": [12, 13]
},

@@ -22,14 +19,11 @@ {

"value": "@import",
"start": 14,
"end": 21
"range": [14, 21]
},
{
"kind": "(",
"start": 21,
"end": 22
"range": [21, 22]
},
{
"kind": "\"",
"start": 22,
"end": 23
"range": [22, 23]
},

@@ -39,19 +33,15 @@ {

"value": "std",
"start": 23,
"end": 26
"range": [23, 26]
},
{
"kind": "\"",
"start": 26,
"end": 27
"range": [26, 27]
},
{
"kind": ")",
"start": 27,
"end": 28
"range": [27, 28]
},
{
"kind": ".",
"start": 28,
"end": 29
"range": [28, 29]
},

@@ -61,9 +51,7 @@ {

"value": "debug",
"start": 29,
"end": 34
"range": [29, 34]
},
{
"kind": ".",
"start": 34,
"end": 35
"range": [34, 35]
},

@@ -73,19 +61,15 @@ {

"value": "print",
"start": 35,
"end": 40
"range": [35, 40]
},
{
"kind": ";",
"start": 40,
"end": 41
"range": [40, 41]
},
{
"kind": "pub",
"start": 43,
"end": 46
"range": [43, 46]
},
{
"kind": "fn",
"start": 47,
"end": 49
"range": [47, 49]
},

@@ -95,24 +79,19 @@ {

"value": "main",
"start": 50,
"end": 54
"range": [50, 54]
},
{
"kind": "(",
"start": 54,
"end": 55
"range": [54, 55]
},
{
"kind": ")",
"start": 55,
"end": 56
"range": [55, 56]
},
{
"kind": "void",
"start": 57,
"end": 61
"range": [57, 61]
},
{
"kind": "{",
"start": 62,
"end": 63
"range": [62, 63]
},

@@ -122,4 +101,3 @@ {

"value": "// Comments in Zig start with \"//\" and end at the next LF byte (end of line).",
"start": 68,
"end": 145
"range": [68, 145]
},

@@ -129,4 +107,3 @@ {

"value": "// The line below is a comment and won't be executed.",
"start": 150,
"end": 203
"range": [150, 203]
},

@@ -136,4 +113,3 @@ {

"value": "//print(\"Hello?\", .{});",
"start": 209,
"end": 232
"range": [209, 232]
},

@@ -143,14 +119,11 @@ {

"value": "print",
"start": 238,
"end": 243
"range": [238, 243]
},
{
"kind": "(",
"start": 243,
"end": 244
"range": [243, 244]
},
{
"kind": "\"",
"start": 244,
"end": 245
"range": [244, 245]
},

@@ -160,4 +133,3 @@ {

"value": "Hello, world!",
"start": 245,
"end": 258
"range": [245, 258]
},

@@ -167,39 +139,31 @@ {

"value": "\\n",
"start": 258,
"end": 260
"range": [258, 260]
},
{
"kind": "\"",
"start": 260,
"end": 261
"range": [260, 261]
},
{
"kind": ",",
"start": 261,
"end": 262
"range": [261, 262]
},
{
"kind": ".",
"start": 263,
"end": 264
"range": [263, 264]
},
{
"kind": "{",
"start": 264,
"end": 265
"range": [264, 265]
},
{
"kind": "}",
"start": 265,
"end": 266
"range": [265, 266]
},
{
"kind": ")",
"start": 266,
"end": 267
"range": [266, 267]
},
{
"kind": ";",
"start": 267,
"end": 268
"range": [267, 268]
},

@@ -209,10 +173,8 @@ {

"value": "// another comment",
"start": 269,
"end": 287
"range": [269, 287]
},
{
"kind": "}",
"start": 288,
"end": 289
"range": [288, 289]
}
]
]
[
{
"kind": "const",
"start": 0,
"end": 5
"range": [0, 5]
},

@@ -10,9 +9,7 @@ {

"value": "std",
"start": 6,
"end": 9
"range": [6, 9]
},
{
"kind": "=",
"start": 10,
"end": 11
"range": [10, 11]
},

@@ -22,14 +19,11 @@ {

"value": "@import",
"start": 12,
"end": 19
"range": [12, 19]
},
{
"kind": "(",
"start": 19,
"end": 20
"range": [19, 20]
},
{
"kind": "\"",
"start": 20,
"end": 21
"range": [20, 21]
},

@@ -39,29 +33,23 @@ {

"value": "std",
"start": 21,
"end": 24
"range": [21, 24]
},
{
"kind": "\"",
"start": 24,
"end": 25
"range": [24, 25]
},
{
"kind": ")",
"start": 25,
"end": 26
"range": [25, 26]
},
{
"kind": ";",
"start": 26,
"end": 27
"range": [26, 27]
},
{
"kind": "pub",
"start": 29,
"end": 32
"range": [29, 32]
},
{
"kind": "fn",
"start": 33,
"end": 35
"range": [33, 35]
},

@@ -71,34 +59,27 @@ {

"value": "main",
"start": 36,
"end": 40
"range": [36, 40]
},
{
"kind": "(",
"start": 40,
"end": 41
"range": [40, 41]
},
{
"kind": ")",
"start": 41,
"end": 42
"range": [41, 42]
},
{
"kind": "!",
"start": 43,
"end": 44
"range": [43, 44]
},
{
"kind": "void",
"start": 44,
"end": 48
"range": [44, 48]
},
{
"kind": "{",
"start": 49,
"end": 50
"range": [49, 50]
},
{
"kind": "const",
"start": 55,
"end": 60
"range": [55, 60]
},

@@ -108,9 +89,7 @@ {

"value": "stdout",
"start": 61,
"end": 67
"range": [61, 67]
},
{
"kind": "=",
"start": 68,
"end": 69
"range": [68, 69]
},

@@ -120,9 +99,7 @@ {

"value": "std",
"start": 70,
"end": 73
"range": [70, 73]
},
{
"kind": ".",
"start": 73,
"end": 74
"range": [73, 74]
},

@@ -132,9 +109,7 @@ {

"value": "io",
"start": 74,
"end": 76
"range": [74, 76]
},
{
"kind": ".",
"start": 76,
"end": 77
"range": [76, 77]
},

@@ -144,19 +119,15 @@ {

"value": "getStdOut",
"start": 77,
"end": 86
"range": [77, 86]
},
{
"kind": "(",
"start": 86,
"end": 87
"range": [86, 87]
},
{
"kind": ")",
"start": 87,
"end": 88
"range": [87, 88]
},
{
"kind": ".",
"start": 88,
"end": 89
"range": [88, 89]
},

@@ -166,24 +137,19 @@ {

"value": "writer",
"start": 89,
"end": 95
"range": [89, 95]
},
{
"kind": "(",
"start": 95,
"end": 96
"range": [95, 96]
},
{
"kind": ")",
"start": 96,
"end": 97
"range": [96, 97]
},
{
"kind": ";",
"start": 97,
"end": 98
"range": [97, 98]
},
{
"kind": "const",
"start": 104,
"end": 109
"range": [104, 109]
},

@@ -193,14 +159,11 @@ {

"value": "greeting",
"start": 110,
"end": 118
"range": [110, 118]
},
{
"kind": "=",
"start": 119,
"end": 120
"range": [119, 120]
},
{
"kind": "\"",
"start": 121,
"end": 122
"range": [121, 122]
},

@@ -210,4 +173,3 @@ {

"value": "Hello, 🌍!",
"start": 122,
"end": 134
"range": [122, 134]
},

@@ -217,19 +179,15 @@ {

"value": "\\n",
"start": 134,
"end": 136
"range": [134, 136]
},
{
"kind": "\"",
"start": 136,
"end": 137
"range": [136, 137]
},
{
"kind": ";",
"start": 137,
"end": 138
"range": [137, 138]
},
{
"kind": "try",
"start": 143,
"end": 146
"range": [143, 146]
},

@@ -239,9 +197,7 @@ {

"value": "stdout",
"start": 147,
"end": 153
"range": [147, 153]
},
{
"kind": ".",
"start": 153,
"end": 154
"range": [153, 154]
},

@@ -251,14 +207,11 @@ {

"value": "print",
"start": 154,
"end": 159
"range": [154, 159]
},
{
"kind": "(",
"start": 159,
"end": 160
"range": [159, 160]
},
{
"kind": "\"",
"start": 160,
"end": 161
"range": [160, 161]
},

@@ -268,24 +221,19 @@ {

"value": "{s}",
"start": 161,
"end": 164
"range": [161, 164]
},
{
"kind": "\"",
"start": 164,
"end": 165
"range": [164, 165]
},
{
"kind": ",",
"start": 165,
"end": 166
"range": [165, 166]
},
{
"kind": ".",
"start": 167,
"end": 168
"range": [167, 168]
},
{
"kind": "{",
"start": 168,
"end": 169
"range": [168, 169]
},

@@ -295,24 +243,19 @@ {

"value": "greeting",
"start": 169,
"end": 177
"range": [169, 177]
},
{
"kind": "}",
"start": 177,
"end": 178
"range": [177, 178]
},
{
"kind": ")",
"start": 178,
"end": 179
"range": [178, 179]
},
{
"kind": ";",
"start": 179,
"end": 180
"range": [179, 180]
},
{
"kind": "const",
"start": 186,
"end": 191
"range": [186, 191]
},

@@ -322,14 +265,11 @@ {

"value": "emoji_identifier",
"start": 192,
"end": 208
"range": [192, 208]
},
{
"kind": "=",
"start": 209,
"end": 210
"range": [209, 210]
},
{
"kind": "\"",
"start": 211,
"end": 212
"range": [211, 212]
},

@@ -339,4 +279,3 @@ {

"value": "✅ This is a test string with emojis 🚀🔥",
"start": 212,
"end": 258
"range": [212, 258]
},

@@ -346,19 +285,15 @@ {

"value": "\\n",
"start": 258,
"end": 260
"range": [258, 260]
},
{
"kind": "\"",
"start": 260,
"end": 261
"range": [260, 261]
},
{
"kind": ";",
"start": 261,
"end": 262
"range": [261, 262]
},
{
"kind": "try",
"start": 267,
"end": 270
"range": [267, 270]
},

@@ -368,9 +303,7 @@ {

"value": "stdout",
"start": 271,
"end": 277
"range": [271, 277]
},
{
"kind": ".",
"start": 277,
"end": 278
"range": [277, 278]
},

@@ -380,14 +313,11 @@ {

"value": "print",
"start": 278,
"end": 283
"range": [278, 283]
},
{
"kind": "(",
"start": 283,
"end": 284
"range": [283, 284]
},
{
"kind": "\"",
"start": 284,
"end": 285
"range": [284, 285]
},

@@ -397,24 +327,19 @@ {

"value": "{s}",
"start": 285,
"end": 288
"range": [285, 288]
},
{
"kind": "\"",
"start": 288,
"end": 289
"range": [288, 289]
},
{
"kind": ",",
"start": 289,
"end": 290
"range": [289, 290]
},
{
"kind": ".",
"start": 291,
"end": 292
"range": [291, 292]
},
{
"kind": "{",
"start": 292,
"end": 293
"range": [292, 293]
},

@@ -424,25 +349,20 @@ {

"value": "emoji_identifier",
"start": 293,
"end": 309
"range": [293, 309]
},
{
"kind": "}",
"start": 309,
"end": 310
"range": [309, 310]
},
{
"kind": ")",
"start": 310,
"end": 311
"range": [310, 311]
},
{
"kind": ";",
"start": 311,
"end": 312
"range": [311, 312]
},
{
"kind": "}",
"start": 313,
"end": 314
"range": [313, 314]
}
]
]
[
{
"kind": "const",
"start": 0,
"end": 5
"range": [0, 5]
},

@@ -10,9 +9,7 @@ {

"value": "std",
"start": 6,
"end": 9
"range": [6, 9]
},
{
"kind": "=",
"start": 10,
"end": 11
"range": [10, 11]
},

@@ -22,14 +19,11 @@ {

"value": "@import",
"start": 12,
"end": 19
"range": [12, 19]
},
{
"kind": "(",
"start": 19,
"end": 20
"range": [19, 20]
},
{
"kind": "\"",
"start": 20,
"end": 21
"range": [20, 21]
},

@@ -39,24 +33,19 @@ {

"value": "std",
"start": 21,
"end": 24
"range": [21, 24]
},
{
"kind": "\"",
"start": 24,
"end": 25
"range": [24, 25]
},
{
"kind": ")",
"start": 25,
"end": 26
"range": [25, 26]
},
{
"kind": ";",
"start": 26,
"end": 27
"range": [26, 27]
},
{
"kind": "const",
"start": 28,
"end": 33
"range": [28, 33]
},

@@ -66,9 +55,7 @@ {

"value": "expect",
"start": 34,
"end": 40
"range": [34, 40]
},
{
"kind": "=",
"start": 41,
"end": 42
"range": [41, 42]
},

@@ -78,9 +65,7 @@ {

"value": "std",
"start": 43,
"end": 46
"range": [43, 46]
},
{
"kind": ".",
"start": 46,
"end": 47
"range": [46, 47]
},

@@ -90,9 +75,7 @@ {

"value": "testing",
"start": 47,
"end": 54
"range": [47, 54]
},
{
"kind": ".",
"start": 54,
"end": 55
"range": [54, 55]
},

@@ -102,29 +85,23 @@ {

"value": "expect",
"start": 55,
"end": 61
"range": [55, 61]
},
{
"kind": ";",
"start": 61,
"end": 62
"range": [61, 62]
},
{
"kind": "comptime",
"start": 64,
"end": 72
"range": [64, 72]
},
{
"kind": "{",
"start": 73,
"end": 74
"range": [73, 74]
},
{
"kind": "asm",
"start": 79,
"end": 82
"range": [79, 82]
},
{
"kind": "(",
"start": 83,
"end": 84
"range": [83, 84]
},

@@ -134,29 +111,23 @@ {

"value": "\\\\.global my_func;\n \\\\.type my_func, @function;\n \\\\my_func:\n \\\\ lea (%rdi,%rsi,1),%eax\n \\\\ retq",
"start": 93,
"end": 218
"range": [93, 218]
},
{
"kind": ")",
"start": 223,
"end": 224
"range": [223, 224]
},
{
"kind": ";",
"start": 224,
"end": 225
"range": [224, 225]
},
{
"kind": "}",
"start": 226,
"end": 227
"range": [226, 227]
},
{
"kind": "extern",
"start": 229,
"end": 235
"range": [229, 235]
},
{
"kind": "fn",
"start": 236,
"end": 238
"range": [236, 238]
},

@@ -166,9 +137,7 @@ {

"value": "my_func",
"start": 239,
"end": 246
"range": [239, 246]
},
{
"kind": "(",
"start": 246,
"end": 247
"range": [246, 247]
},

@@ -178,9 +147,7 @@ {

"value": "a",
"start": 247,
"end": 248
"range": [247, 248]
},
{
"kind": ":",
"start": 248,
"end": 249
"range": [248, 249]
},

@@ -190,9 +157,7 @@ {

"value": "i32",
"start": 250,
"end": 253
"range": [250, 253]
},
{
"kind": ",",
"start": 253,
"end": 254
"range": [253, 254]
},

@@ -202,9 +167,7 @@ {

"value": "b",
"start": 255,
"end": 256
"range": [255, 256]
},
{
"kind": ":",
"start": 256,
"end": 257
"range": [256, 257]
},

@@ -214,9 +177,7 @@ {

"value": "i32",
"start": 258,
"end": 261
"range": [258, 261]
},
{
"kind": ")",
"start": 261,
"end": 262
"range": [261, 262]
},

@@ -226,19 +187,15 @@ {

"value": "i32",
"start": 263,
"end": 266
"range": [263, 266]
},
{
"kind": ";",
"start": 266,
"end": 267
"range": [266, 267]
},
{
"kind": "test",
"start": 269,
"end": 273
"range": [269, 273]
},
{
"kind": "\"",
"start": 274,
"end": 275
"range": [274, 275]
},

@@ -248,19 +205,15 @@ {

"value": "global assembly",
"start": 275,
"end": 290
"range": [275, 290]
},
{
"kind": "\"",
"start": 290,
"end": 291
"range": [290, 291]
},
{
"kind": "{",
"start": 292,
"end": 293
"range": [292, 293]
},
{
"kind": "try",
"start": 298,
"end": 301
"range": [298, 301]
},

@@ -270,9 +223,7 @@ {

"value": "expect",
"start": 302,
"end": 308
"range": [302, 308]
},
{
"kind": "(",
"start": 308,
"end": 309
"range": [308, 309]
},

@@ -282,9 +233,7 @@ {

"value": "my_func",
"start": 309,
"end": 316
"range": [309, 316]
},
{
"kind": "(",
"start": 316,
"end": 317
"range": [316, 317]
},

@@ -294,9 +243,7 @@ {

"value": "12",
"start": 317,
"end": 319
"range": [317, 319]
},
{
"kind": ",",
"start": 319,
"end": 320
"range": [319, 320]
},

@@ -306,14 +253,11 @@ {

"value": "34",
"start": 321,
"end": 323
"range": [321, 323]
},
{
"kind": ")",
"start": 323,
"end": 324
"range": [323, 324]
},
{
"kind": "==",
"start": 325,
"end": 327
"range": [325, 327]
},

@@ -323,20 +267,16 @@ {

"value": "46",
"start": 328,
"end": 330
"range": [328, 330]
},
{
"kind": ")",
"start": 330,
"end": 331
"range": [330, 331]
},
{
"kind": ";",
"start": 331,
"end": 332
"range": [331, 332]
},
{
"kind": "}",
"start": 333,
"end": 334
"range": [333, 334]
}
]
]
[
{
"kind": "const",
"start": 0,
"end": 5
"range": [0, 5]
},

@@ -10,9 +9,7 @@ {

"value": "std",
"start": 6,
"end": 9
"range": [6, 9]
},
{
"kind": "=",
"start": 10,
"end": 11
"range": [10, 11]
},

@@ -22,14 +19,11 @@ {

"value": "@import",
"start": 12,
"end": 19
"range": [12, 19]
},
{
"kind": "(",
"start": 19,
"end": 20
"range": [19, 20]
},
{
"kind": "\"",
"start": 20,
"end": 21
"range": [20, 21]
},

@@ -39,29 +33,23 @@ {

"value": "std",
"start": 21,
"end": 24
"range": [21, 24]
},
{
"kind": "\"",
"start": 24,
"end": 25
"range": [24, 25]
},
{
"kind": ")",
"start": 25,
"end": 26
"range": [25, 26]
},
{
"kind": ";",
"start": 26,
"end": 27
"range": [26, 27]
},
{
"kind": "pub",
"start": 29,
"end": 32
"range": [29, 32]
},
{
"kind": "fn",
"start": 33,
"end": 35
"range": [33, 35]
},

@@ -71,24 +59,19 @@ {

"value": "main",
"start": 36,
"end": 40
"range": [36, 40]
},
{
"kind": "(",
"start": 40,
"end": 41
"range": [40, 41]
},
{
"kind": ")",
"start": 41,
"end": 42
"range": [41, 42]
},
{
"kind": "void",
"start": 43,
"end": 47
"range": [43, 47]
},
{
"kind": "{",
"start": 48,
"end": 49
"range": [48, 49]
},

@@ -98,9 +81,7 @@ {

"value": "std",
"start": 54,
"end": 57
"range": [54, 57]
},
{
"kind": ".",
"start": 57,
"end": 58
"range": [57, 58]
},

@@ -110,9 +91,7 @@ {

"value": "debug",
"start": 58,
"end": 63
"range": [58, 63]
},
{
"kind": ".",
"start": 63,
"end": 64
"range": [63, 64]
},

@@ -122,14 +101,11 @@ {

"value": "print",
"start": 64,
"end": 69
"range": [64, 69]
},
{
"kind": "(",
"start": 69,
"end": 70
"range": [69, 70]
},
{
"kind": "\"",
"start": 70,
"end": 71
"range": [70, 71]
},

@@ -139,4 +115,3 @@ {

"value": "Hello, world!",
"start": 71,
"end": 84
"range": [71, 84]
},

@@ -146,45 +121,36 @@ {

"value": "\\n",
"start": 84,
"end": 86
"range": [84, 86]
},
{
"kind": "\"",
"start": 86,
"end": 87
"range": [86, 87]
},
{
"kind": ",",
"start": 87,
"end": 88
"range": [87, 88]
},
{
"kind": ".",
"start": 89,
"end": 90
"range": [89, 90]
},
{
"kind": "{",
"start": 90,
"end": 91
"range": [90, 91]
},
{
"kind": "}",
"start": 91,
"end": 92
"range": [91, 92]
},
{
"kind": ")",
"start": 92,
"end": 93
"range": [92, 93]
},
{
"kind": ";",
"start": 93,
"end": 94
"range": [93, 94]
},
{
"kind": "}",
"start": 95,
"end": 96
"range": [95, 96]
}
]
]
[
{
"kind": "const",
"start": 0,
"end": 5
"range": [0, 5]
},
{
"kind": "@",
"start": 6,
"end": 7
"range": [6, 7]
},
{
"kind": "\"",
"start": 7,
"end": 8
"range": [7, 8]
},

@@ -20,14 +17,11 @@ {

"value": "identifier with spaces in it",
"start": 8,
"end": 36
"range": [8, 36]
},
{
"kind": "\"",
"start": 36,
"end": 37
"range": [36, 37]
},
{
"kind": "=",
"start": 38,
"end": 39
"range": [38, 39]
},

@@ -37,24 +31,19 @@ {

"value": "0xff",
"start": 40,
"end": 44
"range": [40, 44]
},
{
"kind": ";",
"start": 44,
"end": 45
"range": [44, 45]
},
{
"kind": "const",
"start": 46,
"end": 51
"range": [46, 51]
},
{
"kind": "@",
"start": 52,
"end": 53
"range": [52, 53]
},
{
"kind": "\"",
"start": 53,
"end": 54
"range": [53, 54]
},

@@ -64,14 +53,11 @@ {

"value": "1SmallStep4Man",
"start": 54,
"end": 68
"range": [54, 68]
},
{
"kind": "\"",
"start": 68,
"end": 69
"range": [68, 69]
},
{
"kind": "=",
"start": 70,
"end": 71
"range": [70, 71]
},

@@ -81,14 +67,11 @@ {

"value": "112358",
"start": 72,
"end": 78
"range": [72, 78]
},
{
"kind": ";",
"start": 78,
"end": 79
"range": [78, 79]
},
{
"kind": "const",
"start": 81,
"end": 86
"range": [81, 86]
},

@@ -98,9 +81,7 @@ {

"value": "c",
"start": 87,
"end": 88
"range": [87, 88]
},
{
"kind": "=",
"start": 89,
"end": 90
"range": [89, 90]
},

@@ -110,14 +91,11 @@ {

"value": "@import",
"start": 91,
"end": 98
"range": [91, 98]
},
{
"kind": "(",
"start": 98,
"end": 99
"range": [98, 99]
},
{
"kind": "\"",
"start": 99,
"end": 100
"range": [99, 100]
},

@@ -127,19 +105,15 @@ {

"value": "std",
"start": 100,
"end": 103
"range": [100, 103]
},
{
"kind": "\"",
"start": 103,
"end": 104
"range": [103, 104]
},
{
"kind": ")",
"start": 104,
"end": 105
"range": [104, 105]
},
{
"kind": ".",
"start": 105,
"end": 106
"range": [105, 106]
},

@@ -149,24 +123,19 @@ {

"value": "c",
"start": 106,
"end": 107
"range": [106, 107]
},
{
"kind": ";",
"start": 107,
"end": 108
"range": [107, 108]
},
{
"kind": "pub",
"start": 109,
"end": 112
"range": [109, 112]
},
{
"kind": "extern",
"start": 113,
"end": 119
"range": [113, 119]
},
{
"kind": "\"",
"start": 120,
"end": 121
"range": [120, 121]
},

@@ -176,24 +145,19 @@ {

"value": "c",
"start": 121,
"end": 122
"range": [121, 122]
},
{
"kind": "\"",
"start": 122,
"end": 123
"range": [122, 123]
},
{
"kind": "fn",
"start": 124,
"end": 126
"range": [124, 126]
},
{
"kind": "@",
"start": 127,
"end": 128
"range": [127, 128]
},
{
"kind": "\"",
"start": 128,
"end": 129
"range": [128, 129]
},

@@ -203,44 +167,35 @@ {

"value": "error",
"start": 129,
"end": 134
"range": [129, 134]
},
{
"kind": "\"",
"start": 134,
"end": 135
"range": [134, 135]
},
{
"kind": "(",
"start": 135,
"end": 136
"range": [135, 136]
},
{
"kind": ")",
"start": 136,
"end": 137
"range": [136, 137]
},
{
"kind": "void",
"start": 138,
"end": 142
"range": [138, 142]
},
{
"kind": ";",
"start": 142,
"end": 143
"range": [142, 143]
},
{
"kind": "pub",
"start": 144,
"end": 147
"range": [144, 147]
},
{
"kind": "extern",
"start": 148,
"end": 154
"range": [148, 154]
},
{
"kind": "\"",
"start": 155,
"end": 156
"range": [155, 156]
},

@@ -250,24 +205,19 @@ {

"value": "c",
"start": 156,
"end": 157
"range": [156, 157]
},
{
"kind": "\"",
"start": 157,
"end": 158
"range": [157, 158]
},
{
"kind": "fn",
"start": 159,
"end": 161
"range": [159, 161]
},
{
"kind": "@",
"start": 162,
"end": 163
"range": [162, 163]
},
{
"kind": "\"",
"start": 163,
"end": 164
"range": [163, 164]
},

@@ -277,14 +227,11 @@ {

"value": "fstat$INODE64",
"start": 164,
"end": 177
"range": [164, 177]
},
{
"kind": "\"",
"start": 177,
"end": 178
"range": [177, 178]
},
{
"kind": "(",
"start": 178,
"end": 179
"range": [178, 179]
},

@@ -294,9 +241,7 @@ {

"value": "fd",
"start": 179,
"end": 181
"range": [179, 181]
},
{
"kind": ":",
"start": 181,
"end": 182
"range": [181, 182]
},

@@ -306,9 +251,7 @@ {

"value": "c",
"start": 183,
"end": 184
"range": [183, 184]
},
{
"kind": ".",
"start": 184,
"end": 185
"range": [184, 185]
},

@@ -318,9 +261,7 @@ {

"value": "fd_t",
"start": 185,
"end": 189
"range": [185, 189]
},
{
"kind": ",",
"start": 189,
"end": 190
"range": [189, 190]
},

@@ -330,14 +271,11 @@ {

"value": "buf",
"start": 191,
"end": 194
"range": [191, 194]
},
{
"kind": ":",
"start": 194,
"end": 195
"range": [194, 195]
},
{
"kind": "*",
"start": 196,
"end": 197
"range": [196, 197]
},

@@ -347,9 +285,7 @@ {

"value": "c",
"start": 197,
"end": 198
"range": [197, 198]
},
{
"kind": ".",
"start": 198,
"end": 199
"range": [198, 199]
},

@@ -359,24 +295,19 @@ {

"value": "Stat",
"start": 199,
"end": 203
"range": [199, 203]
},
{
"kind": ")",
"start": 203,
"end": 204
"range": [203, 204]
},
{
"kind": "c_int",
"start": 205,
"end": 210
"range": [205, 210]
},
{
"kind": ";",
"start": 210,
"end": 211
"range": [210, 211]
},
{
"kind": "const",
"start": 213,
"end": 218
"range": [213, 218]
},

@@ -386,19 +317,15 @@ {

"value": "Color",
"start": 219,
"end": 224
"range": [219, 224]
},
{
"kind": "=",
"start": 225,
"end": 226
"range": [225, 226]
},
{
"kind": "enum",
"start": 227,
"end": 231
"range": [227, 231]
},
{
"kind": "{",
"start": 232,
"end": 233
"range": [232, 233]
},

@@ -408,19 +335,15 @@ {

"value": "red",
"start": 238,
"end": 241
"range": [238, 241]
},
{
"kind": ",",
"start": 241,
"end": 242
"range": [241, 242]
},
{
"kind": "@",
"start": 247,
"end": 248
"range": [247, 248]
},
{
"kind": "\"",
"start": 248,
"end": 249
"range": [248, 249]
},

@@ -430,29 +353,23 @@ {

"value": "really red",
"start": 249,
"end": 259
"range": [249, 259]
},
{
"kind": "\"",
"start": 259,
"end": 260
"range": [259, 260]
},
{
"kind": ",",
"start": 260,
"end": 261
"range": [260, 261]
},
{
"kind": "}",
"start": 262,
"end": 263
"range": [262, 263]
},
{
"kind": ";",
"start": 263,
"end": 264
"range": [263, 264]
},
{
"kind": "const",
"start": 265,
"end": 270
"range": [265, 270]
},

@@ -462,9 +379,7 @@ {

"value": "color",
"start": 271,
"end": 276
"range": [271, 276]
},
{
"kind": ":",
"start": 276,
"end": 277
"range": [276, 277]
},

@@ -474,24 +389,19 @@ {

"value": "Color",
"start": 278,
"end": 283
"range": [278, 283]
},
{
"kind": "=",
"start": 284,
"end": 285
"range": [284, 285]
},
{
"kind": ".",
"start": 286,
"end": 287
"range": [286, 287]
},
{
"kind": "@",
"start": 287,
"end": 288
"range": [287, 288]
},
{
"kind": "\"",
"start": 288,
"end": 289
"range": [288, 289]
},

@@ -501,15 +411,12 @@ {

"value": "really red",
"start": 289,
"end": 299
"range": [289, 299]
},
{
"kind": "\"",
"start": 299,
"end": 300
"range": [299, 300]
},
{
"kind": ";",
"start": 300,
"end": 301
"range": [300, 301]
}
]
]

@@ -0,9 +1,14 @@

from __future__ import annotations
import json
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from zig_codeblocks.formatting import highlight_zig_code, process_markdown
from zig_codeblocks.styling import Color, Style, Theme
from zig_codeblocks import Color, Style, highlight_zig_code, process_markdown
if TYPE_CHECKING:
from zig_codeblocks._core import Theme
SOURCE_DIR = Path(__file__).parent / "sources"

@@ -50,4 +55,4 @@

[
{"identifiers": Style(Color.RED)},
{"identifiers": Style(Color.RED), "strings": Style(Color.GREEN)},
{"Identifier": Style(Color.Red)},
{"Identifier": Style(Color.Red), "String": Style(Color.Green)},
],

@@ -65,6 +70,6 @@ )

source = " const x = 0xff;"
theme = Theme(
identifiers=(red := Style(Color.RED)),
keywords=(blue_u := Style(Color.BLUE, underline=True)),
)
theme: Theme = {
"Identifier": (red := Style(Color.Red)),
"Keyword": (blue_u := Style(Color.Blue, underline=True)),
}
expected_highlighting = f" {blue_u}const\033[0m {red}x \033[0m= 0xff;"

@@ -78,3 +83,3 @@ assert highlight_zig_code(source, theme) == expected_highlighting

theme = Theme(identifiers=(red := Style(Color.RED)))
theme: Theme = {"Identifier": (red := Style(Color.Red))}
assert highlight_zig_code(src, theme) == f"{red}{src}"

@@ -1,2 +0,3 @@

import json
from __future__ import annotations
from pathlib import Path

@@ -6,3 +7,3 @@

from zig_codeblocks.parsing import CodeBlock, Token, extract_codeblocks, tokenize_zig
from zig_codeblocks._core import CodeBlock, extract_codeblocks

@@ -25,6 +26,8 @@ SOURCE_DIR = Path(__file__).parent / "sources"

for meth in (Path.read_text, Path.read_bytes):
assert list(extract_codeblocks(meth(src))) == [
CodeBlock(lang="py", body="print(1)"),
CodeBlock(lang="zig", body='const std = @import("std");'),
]
code_blocks = extract_codeblocks(meth(src))
assert len(code_blocks) == 2
py_block, zig_block = code_blocks
assert (py_block.lang, zig_block.lang) == ("py", "zig")
assert py_block.body.strip("\r\n") == "print(1)"
assert zig_block.body.strip("\r\n") == 'const std = @import("std");'

@@ -36,8 +39,8 @@

("rust", (None, "rust")),
("rust\n", (None, "rust")),
("rust\n", (None, "rust\n")),
("rust\nfn", ("rust", "fn")),
("zig\na", ("zig", "a")),
("zig\naa", ("zig", "aa")),
("zig\na\n", ("zig", "a")),
("rust\n\n\n\n\n\n", (None, "rust")),
("zig\na\n", ("zig", "a\n")),
("rust\n\n\n\n\n\n", (None, "rust\n\n\n\n\n\n")),
("ruśt\nfn", (None, "ruśt\nfn")),

@@ -49,3 +52,3 @@ ("1+2\nfn", ("1+2", "fn")),

def test_codeblock_with_language(source: str, expected: tuple[str | None, str]) -> None:
codeblocks = list(extract_codeblocks(f"```{source}```"))
codeblocks = extract_codeblocks(f"```{source}```")
assert len(codeblocks) == 1

@@ -55,32 +58,12 @@ assert codeblocks[0] == CodeBlock(*expected)

def read_expected_tokens(test_name: str) -> list[Token]:
return [
Token(
kind=t["kind"],
value=(t.get("value") or t["kind"]).encode(),
byte_range=range(t["start"], t["end"]),
)
for t in json.loads(
(SOURCE_DIR / "parsing_results" / f"{test_name}.json").read_bytes()
)
]
@pytest.mark.parametrize(
"test_name",
"source",
[
"assign_undefined",
"comments",
"emoji",
"global_assembly",
"hello_again",
"identifiers",
'```zig\nconst std = @import("std");\n```',
"```py\nprint(1)\n```\n```woah```",
"```\n\n\nhi\n\n\n```",
],
)
def test_zig_parser(test_name: str) -> None:
source = (
(SOURCE_DIR / "zig_inputs" / f"{test_name}.zig")
.read_bytes()
.replace(b"\r\n", b"\n")
)
assert list(tokenize_zig(source)) == read_expected_tokens(test_name)
def test_codeblocks_are_reproducible(source: str) -> None:
codeblocks = extract_codeblocks(source)
assert "\n".join(map(str, codeblocks)) == source

@@ -0,23 +1,72 @@

import re
import pytest
from zig_codeblocks.styling import Color, Style
from zig_codeblocks._core import Color, Style
@pytest.mark.parametrize(
("string", "expected"),
[
("Gray", Color.Gray),
("gray", Color.Gray),
("GRAY", Color.Gray),
("magenta", Color.Magenta),
],
)
def test_color_from_string(string: str, expected: Color) -> None:
assert Color.from_string(string) == expected
def test_color_from_string_fail() -> None:
with pytest.raises(ValueError, match=re.escape('Invalid color: "blurple"')):
Color.from_string("blurple")
@pytest.mark.parametrize(
("color", "bold", "underline", "expected_sgr"),
[
("WHITE", False, False, "\033[37m"),
("BLUE", True, False, "\033[34;1m"),
("ORANGE", False, False, "\033[33m"),
("RED", True, False, "\033[31;1m"),
("BLUE", True, True, "\033[34;1;4m"),
("MAGENTA", False, True, "\033[35;4m"),
("WHITE", True, True, "\033[37;1;4m"),
("CYAN", True, True, "\033[36;1;4m"),
("MAGENTA", True, True, "\033[35;1;4m"),
("GRAY", False, False, "\033[30m"),
("White", False, False, "\033[37m"),
("Blue", True, False, "\033[34;1m"),
("Orange", False, False, "\033[33m"),
("Red", True, False, "\033[31;1m"),
("Blue", True, True, "\033[34;1;4m"),
("Magenta", False, True, "\033[35;4m"),
("White", True, True, "\033[37;1;4m"),
("Cyan", True, True, "\033[36;1;4m"),
("Magenta", True, True, "\033[35;1;4m"),
("Gray", False, False, "\033[30m"),
],
)
def test_style(color: str, bold: bool, underline: bool, expected_sgr: str) -> None:
style = Style(Color[color], bold=bold, underline=underline)
style = Style(Color.from_string(color), bold=bold, underline=underline)
assert str(style) == expected_sgr
assert eval(repr(style)) == style # noqa: S307
@pytest.mark.parametrize(
("string", "expected"),
[
("red", Style(Color.Red)),
("blue+bold", Style(Color.Blue, bold=True)),
("oRange+BOLD", Style(Color.Orange, bold=True)),
("green+underline", Style(Color.Green, underline=True)),
("bold+cyan+underline", Style(Color.Cyan, bold=True, underline=True)),
("bold+underline+magenta", Style(Color.Magenta, bold=True, underline=True)),
],
)
def test_style_from_string(string: str, expected: Style) -> None:
assert Style.from_string(string) == expected
@pytest.mark.parametrize(
("string", "err_msg"),
[
("underline+bold", "Missing color in input string"),
("black+bold+underline", 'Invalid color "black"'),
("red+cyan", "Multiple colors found: Red and Cyan"),
],
)
def test_style_from_string_fail(string: str, err_msg: str) -> None:
with pytest.raises(ValueError, match=re.escape(err_msg)):
Style.from_string(string)
+32
-143
version = 1
revision = 1
requires-python = ">=3.10"
requires-python = ">=3.9"
[[package]]
name = "colorama"
version = "0.4.6"
name = "click"
version = "8.1.8"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
]
[[package]]
name = "coverage"
version = "7.6.10"
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/84/ba/ac14d281f80aab516275012e8875991bb06203957aa1e19950139238d658/coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23", size = 803868 }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c5/12/2a2a923edf4ddabdffed7ad6da50d96a5c126dae7b80a33df7310e329a1e/coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78", size = 207982 },
{ url = "https://files.pythonhosted.org/packages/ca/49/6985dbca9c7be3f3cb62a2e6e492a0c88b65bf40579e16c71ae9c33c6b23/coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c", size = 208414 },
{ url = "https://files.pythonhosted.org/packages/35/93/287e8f1d1ed2646f4e0b2605d14616c9a8a2697d0d1b453815eb5c6cebdb/coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a", size = 236860 },
{ url = "https://files.pythonhosted.org/packages/de/e1/cfdb5627a03567a10031acc629b75d45a4ca1616e54f7133ca1fa366050a/coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165", size = 234758 },
{ url = "https://files.pythonhosted.org/packages/6d/85/fc0de2bcda3f97c2ee9fe8568f7d48f7279e91068958e5b2cc19e0e5f600/coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988", size = 235920 },
{ url = "https://files.pythonhosted.org/packages/79/73/ef4ea0105531506a6f4cf4ba571a214b14a884630b567ed65b3d9c1975e1/coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5", size = 234986 },
{ url = "https://files.pythonhosted.org/packages/c6/4d/75afcfe4432e2ad0405c6f27adeb109ff8976c5e636af8604f94f29fa3fc/coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3", size = 233446 },
{ url = "https://files.pythonhosted.org/packages/86/5b/efee56a89c16171288cafff022e8af44f8f94075c2d8da563c3935212871/coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5", size = 234566 },
{ url = "https://files.pythonhosted.org/packages/f2/db/67770cceb4a64d3198bf2aa49946f411b85ec6b0a9b489e61c8467a4253b/coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244", size = 210675 },
{ url = "https://files.pythonhosted.org/packages/8d/27/e8bfc43f5345ec2c27bc8a1fa77cdc5ce9dcf954445e11f14bb70b889d14/coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e", size = 211518 },
{ url = "https://files.pythonhosted.org/packages/85/d2/5e175fcf6766cf7501a8541d81778fd2f52f4870100e791f5327fd23270b/coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3", size = 208088 },
{ url = "https://files.pythonhosted.org/packages/4b/6f/06db4dc8fca33c13b673986e20e466fd936235a6ec1f0045c3853ac1b593/coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43", size = 208536 },
{ url = "https://files.pythonhosted.org/packages/0d/62/c6a0cf80318c1c1af376d52df444da3608eafc913b82c84a4600d8349472/coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132", size = 240474 },
{ url = "https://files.pythonhosted.org/packages/a3/59/750adafc2e57786d2e8739a46b680d4fb0fbc2d57fbcb161290a9f1ecf23/coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f", size = 237880 },
{ url = "https://files.pythonhosted.org/packages/2c/f8/ef009b3b98e9f7033c19deb40d629354aab1d8b2d7f9cfec284dbedf5096/coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994", size = 239750 },
{ url = "https://files.pythonhosted.org/packages/a6/e2/6622f3b70f5f5b59f705e680dae6db64421af05a5d1e389afd24dae62e5b/coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99", size = 238642 },
{ url = "https://files.pythonhosted.org/packages/2d/10/57ac3f191a3c95c67844099514ff44e6e19b2915cd1c22269fb27f9b17b6/coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd", size = 237266 },
{ url = "https://files.pythonhosted.org/packages/ee/2d/7016f4ad9d553cabcb7333ed78ff9d27248ec4eba8dd21fa488254dff894/coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377", size = 238045 },
{ url = "https://files.pythonhosted.org/packages/a7/fe/45af5c82389a71e0cae4546413266d2195c3744849669b0bab4b5f2c75da/coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8", size = 210647 },
{ url = "https://files.pythonhosted.org/packages/db/11/3f8e803a43b79bc534c6a506674da9d614e990e37118b4506faf70d46ed6/coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609", size = 211508 },
{ url = "https://files.pythonhosted.org/packages/86/77/19d09ea06f92fdf0487499283b1b7af06bc422ea94534c8fe3a4cd023641/coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853", size = 208281 },
{ url = "https://files.pythonhosted.org/packages/b6/67/5479b9f2f99fcfb49c0d5cf61912a5255ef80b6e80a3cddba39c38146cf4/coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078", size = 208514 },
{ url = "https://files.pythonhosted.org/packages/15/d1/febf59030ce1c83b7331c3546d7317e5120c5966471727aa7ac157729c4b/coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0", size = 241537 },
{ url = "https://files.pythonhosted.org/packages/4b/7e/5ac4c90192130e7cf8b63153fe620c8bfd9068f89a6d9b5f26f1550f7a26/coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50", size = 238572 },
{ url = "https://files.pythonhosted.org/packages/dc/03/0334a79b26ecf59958f2fe9dd1f5ab3e2f88db876f5071933de39af09647/coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022", size = 240639 },
{ url = "https://files.pythonhosted.org/packages/d7/45/8a707f23c202208d7b286d78ad6233f50dcf929319b664b6cc18a03c1aae/coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b", size = 240072 },
{ url = "https://files.pythonhosted.org/packages/66/02/603ce0ac2d02bc7b393279ef618940b4a0535b0868ee791140bda9ecfa40/coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0", size = 238386 },
{ url = "https://files.pythonhosted.org/packages/04/62/4e6887e9be060f5d18f1dd58c2838b2d9646faf353232dec4e2d4b1c8644/coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852", size = 240054 },
{ url = "https://files.pythonhosted.org/packages/5c/74/83ae4151c170d8bd071924f212add22a0e62a7fe2b149edf016aeecad17c/coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359", size = 210904 },
{ url = "https://files.pythonhosted.org/packages/c3/54/de0893186a221478f5880283119fc40483bc460b27c4c71d1b8bba3474b9/coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247", size = 211692 },
{ url = "https://files.pythonhosted.org/packages/25/6d/31883d78865529257bf847df5789e2ae80e99de8a460c3453dbfbe0db069/coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9", size = 208308 },
{ url = "https://files.pythonhosted.org/packages/70/22/3f2b129cc08de00c83b0ad6252e034320946abfc3e4235c009e57cfeee05/coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b", size = 208565 },
{ url = "https://files.pythonhosted.org/packages/97/0a/d89bc2d1cc61d3a8dfe9e9d75217b2be85f6c73ebf1b9e3c2f4e797f4531/coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690", size = 241083 },
{ url = "https://files.pythonhosted.org/packages/4c/81/6d64b88a00c7a7aaed3a657b8eaa0931f37a6395fcef61e53ff742b49c97/coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18", size = 238235 },
{ url = "https://files.pythonhosted.org/packages/9a/0b/7797d4193f5adb4b837207ed87fecf5fc38f7cc612b369a8e8e12d9fa114/coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c", size = 240220 },
{ url = "https://files.pythonhosted.org/packages/65/4d/6f83ca1bddcf8e51bf8ff71572f39a1c73c34cf50e752a952c34f24d0a60/coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd", size = 239847 },
{ url = "https://files.pythonhosted.org/packages/30/9d/2470df6aa146aff4c65fee0f87f58d2164a67533c771c9cc12ffcdb865d5/coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e", size = 237922 },
{ url = "https://files.pythonhosted.org/packages/08/dd/723fef5d901e6a89f2507094db66c091449c8ba03272861eaefa773ad95c/coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694", size = 239783 },
{ url = "https://files.pythonhosted.org/packages/3d/f7/64d3298b2baf261cb35466000628706ce20a82d42faf9b771af447cd2b76/coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6", size = 210965 },
{ url = "https://files.pythonhosted.org/packages/d5/58/ec43499a7fc681212fe7742fe90b2bc361cdb72e3181ace1604247a5b24d/coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e", size = 211719 },
{ url = "https://files.pythonhosted.org/packages/ab/c9/f2857a135bcff4330c1e90e7d03446b036b2363d4ad37eb5e3a47bbac8a6/coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe", size = 209050 },
{ url = "https://files.pythonhosted.org/packages/aa/b3/f840e5bd777d8433caa9e4a1eb20503495709f697341ac1a8ee6a3c906ad/coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273", size = 209321 },
{ url = "https://files.pythonhosted.org/packages/85/7d/125a5362180fcc1c03d91850fc020f3831d5cda09319522bcfa6b2b70be7/coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8", size = 252039 },
{ url = "https://files.pythonhosted.org/packages/a9/9c/4358bf3c74baf1f9bddd2baf3756b54c07f2cfd2535f0a47f1e7757e54b3/coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098", size = 247758 },
{ url = "https://files.pythonhosted.org/packages/cf/c7/de3eb6fc5263b26fab5cda3de7a0f80e317597a4bad4781859f72885f300/coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb", size = 250119 },
{ url = "https://files.pythonhosted.org/packages/3e/e6/43de91f8ba2ec9140c6a4af1102141712949903dc732cf739167cfa7a3bc/coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0", size = 249597 },
{ url = "https://files.pythonhosted.org/packages/08/40/61158b5499aa2adf9e37bc6d0117e8f6788625b283d51e7e0c53cf340530/coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf", size = 247473 },
{ url = "https://files.pythonhosted.org/packages/50/69/b3f2416725621e9f112e74e8470793d5b5995f146f596f133678a633b77e/coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2", size = 248737 },
{ url = "https://files.pythonhosted.org/packages/3c/6e/fe899fb937657db6df31cc3e61c6968cb56d36d7326361847440a430152e/coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312", size = 211611 },
{ url = "https://files.pythonhosted.org/packages/1c/55/52f5e66142a9d7bc93a15192eba7a78513d2abf6b3558d77b4ca32f5f424/coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d", size = 212781 },
{ url = "https://files.pythonhosted.org/packages/a1/70/de81bfec9ed38a64fc44a77c7665e20ca507fc3265597c28b0d989e4082e/coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f", size = 200223 },
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
]
[package.optional-dependencies]
toml = [
{ name = "tomli", marker = "python_full_version <= '3.11'" },
]
[[package]]

@@ -97,11 +45,2 @@ name = "exceptiongroup"

[[package]]
name = "more-itertools"
version = "10.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/88/3b/7fa1fe835e2e93fd6d7b52b2f95ae810cf5ba133e1845f726f5a992d62c2/more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b", size = 125009 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/23/62/0fe302c6d1be1c777cab0616e6302478251dfbf9055ad426f5d0def75c89/more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89", size = 63038 },
]
[[package]]
name = "mypy"

@@ -141,2 +80,8 @@ version = "1.14.1"

{ url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276 },
{ url = "https://files.pythonhosted.org/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35", size = 11200493 },
{ url = "https://files.pythonhosted.org/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc", size = 10357702 },
{ url = "https://files.pythonhosted.org/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9", size = 12091104 },
{ url = "https://files.pythonhosted.org/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb", size = 12830167 },
{ url = "https://files.pythonhosted.org/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60", size = 13013834 },
{ url = "https://files.pythonhosted.org/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c", size = 9781231 },
{ url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905 },

@@ -190,15 +135,2 @@ ]

[[package]]
name = "pytest-cov"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coverage", extra = ["toml"] },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 },
]
[[package]]
name = "ruff"

@@ -268,50 +200,12 @@ version = "0.9.4"

[[package]]
name = "tree-sitter"
version = "0.24.0"
name = "typer-slim"
version = "0.15.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a7/a2/698b9d31d08ad5558f8bfbfe3a0781bd4b1f284e89bde3ad18e05101a892/tree-sitter-0.24.0.tar.gz", hash = "sha256:abd95af65ca2f4f7eca356343391ed669e764f37748b5352946f00f7fc78e734", size = 168304 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/08/9a/bd627a02e41671af73222316e1fcf87772c7804dc2fba99405275eb1f3eb/tree_sitter-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f3f00feff1fc47a8e4863561b8da8f5e023d382dd31ed3e43cd11d4cae445445", size = 140890 },
{ url = "https://files.pythonhosted.org/packages/5b/9b/b1ccfb187f8be78e2116176a091a2f2abfd043a06d78f80c97c97f315b37/tree_sitter-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f9691be48d98c49ef8f498460278884c666b44129222ed6217477dffad5d4831", size = 134413 },
{ url = "https://files.pythonhosted.org/packages/01/39/e25b0042a049eb27e991133a7aa7c49bb8e49a8a7b44ca34e7e6353ba7ac/tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:098a81df9f89cf254d92c1cd0660a838593f85d7505b28249216661d87adde4a", size = 560427 },
{ url = "https://files.pythonhosted.org/packages/1c/59/4d132f1388da5242151b90acf32cc56af779bfba063923699ab28b276b62/tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b26bf9e958da6eb7e74a081aab9d9c7d05f9baeaa830dbb67481898fd16f1f5", size = 574327 },
{ url = "https://files.pythonhosted.org/packages/ec/97/3914e45ab9e0ff0f157e493caa91791372508488b97ff0961a0640a37d25/tree_sitter-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2a84ff87a2f2a008867a1064aba510ab3bd608e3e0cd6e8fef0379efee266c73", size = 577171 },
{ url = "https://files.pythonhosted.org/packages/c5/b0/266a529c3eef171137b73cde8ad7aa282734354609a8b2f5564428e8f12d/tree_sitter-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c012e4c345c57a95d92ab5a890c637aaa51ab3b7ff25ed7069834b1087361c95", size = 120260 },
{ url = "https://files.pythonhosted.org/packages/c1/c3/07bfaa345e0037ff75d98b7a643cf940146e4092a1fd54eed0359836be03/tree_sitter-0.24.0-cp310-cp310-win_arm64.whl", hash = "sha256:033506c1bc2ba7bd559b23a6bdbeaf1127cee3c68a094b82396718596dfe98bc", size = 108416 },
{ url = "https://files.pythonhosted.org/packages/66/08/82aaf7cbea7286ee2a0b43e9b75cb93ac6ac132991b7d3c26ebe5e5235a3/tree_sitter-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de0fb7c18c6068cacff46250c0a0473e8fc74d673e3e86555f131c2c1346fb13", size = 140733 },
{ url = "https://files.pythonhosted.org/packages/8c/bd/1a84574911c40734d80327495e6e218e8f17ef318dd62bb66b55c1e969f5/tree_sitter-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7c9c89666dea2ce2b2bf98e75f429d2876c569fab966afefdcd71974c6d8538", size = 134243 },
{ url = "https://files.pythonhosted.org/packages/46/c1/c2037af2c44996d7bde84eb1c9e42308cc84b547dd6da7f8a8bea33007e1/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddb113e6b8b3e3b199695b1492a47d87d06c538e63050823d90ef13cac585fd", size = 562030 },
{ url = "https://files.pythonhosted.org/packages/4c/aa/2fb4d81886df958e6ec7e370895f7106d46d0bbdcc531768326124dc8972/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ea01a7003b88b92f7f875da6ba9d5d741e0c84bb1bd92c503c0eecd0ee6409", size = 575585 },
{ url = "https://files.pythonhosted.org/packages/e3/3c/5f997ce34c0d1b744e0f0c0757113bdfc173a2e3dadda92c751685cfcbd1/tree_sitter-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:464fa5b2cac63608915a9de8a6efd67a4da1929e603ea86abaeae2cb1fe89921", size = 578203 },
{ url = "https://files.pythonhosted.org/packages/d5/1f/f2bc7fa7c3081653ea4f2639e06ff0af4616c47105dbcc0746137da7620d/tree_sitter-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b1f3cbd9700e1fba0be2e7d801527e37c49fc02dc140714669144ef6ab58dce", size = 120147 },
{ url = "https://files.pythonhosted.org/packages/c0/4c/9add771772c4d72a328e656367ca948e389432548696a3819b69cdd6f41e/tree_sitter-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:f3f08a2ca9f600b3758792ba2406971665ffbad810847398d180c48cee174ee2", size = 108302 },
{ url = "https://files.pythonhosted.org/packages/e9/57/3a590f287b5aa60c07d5545953912be3d252481bf5e178f750db75572bff/tree_sitter-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14beeff5f11e223c37be7d5d119819880601a80d0399abe8c738ae2288804afc", size = 140788 },
{ url = "https://files.pythonhosted.org/packages/61/0b/fc289e0cba7dbe77c6655a4dd949cd23c663fd62a8b4d8f02f97e28d7fe5/tree_sitter-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26a5b130f70d5925d67b47db314da209063664585a2fd36fa69e0717738efaf4", size = 133945 },
{ url = "https://files.pythonhosted.org/packages/86/d7/80767238308a137e0b5b5c947aa243e3c1e3e430e6d0d5ae94b9a9ffd1a2/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fc5c3c26d83c9d0ecb4fc4304fba35f034b7761d35286b936c1db1217558b4e", size = 564819 },
{ url = "https://files.pythonhosted.org/packages/bf/b3/6c5574f4b937b836601f5fb556b24804b0a6341f2eb42f40c0e6464339f4/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:772e1bd8c0931c866b848d0369b32218ac97c24b04790ec4b0e409901945dd8e", size = 579303 },
{ url = "https://files.pythonhosted.org/packages/0a/f4/bd0ddf9abe242ea67cca18a64810f8af230fc1ea74b28bb702e838ccd874/tree_sitter-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:24a8dd03b0d6b8812425f3b84d2f4763322684e38baf74e5bb766128b5633dc7", size = 581054 },
{ url = "https://files.pythonhosted.org/packages/8c/1c/ff23fa4931b6ef1bbeac461b904ca7e49eaec7e7e5398584e3eef836ec96/tree_sitter-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9e8b1605ab60ed43803100f067eed71b0b0e6c1fb9860a262727dbfbbb74751", size = 120221 },
{ url = "https://files.pythonhosted.org/packages/b2/2a/9979c626f303177b7612a802237d0533155bf1e425ff6f73cc40f25453e2/tree_sitter-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:f733a83d8355fc95561582b66bbea92ffd365c5d7a665bc9ebd25e049c2b2abb", size = 108234 },
{ url = "https://files.pythonhosted.org/packages/61/cd/2348339c85803330ce38cee1c6cbbfa78a656b34ff58606ebaf5c9e83bd0/tree_sitter-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d4a6416ed421c4210f0ca405a4834d5ccfbb8ad6692d4d74f7773ef68f92071", size = 140781 },
{ url = "https://files.pythonhosted.org/packages/8b/a3/1ea9d8b64e8dcfcc0051028a9c84a630301290995cd6e947bf88267ef7b1/tree_sitter-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0992d483677e71d5c5d37f30dfb2e3afec2f932a9c53eec4fca13869b788c6c", size = 133928 },
{ url = "https://files.pythonhosted.org/packages/fe/ae/55c1055609c9428a4aedf4b164400ab9adb0b1bf1538b51f4b3748a6c983/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57277a12fbcefb1c8b206186068d456c600dbfbc3fd6c76968ee22614c5cd5ad", size = 564497 },
{ url = "https://files.pythonhosted.org/packages/ce/d0/f2ffcd04882c5aa28d205a787353130cbf84b2b8a977fd211bdc3b399ae3/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25fa22766d63f73716c6fec1a31ee5cf904aa429484256bd5fdf5259051ed74", size = 578917 },
{ url = "https://files.pythonhosted.org/packages/af/82/aebe78ea23a2b3a79324993d4915f3093ad1af43d7c2208ee90be9273273/tree_sitter-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d5d9537507e1c8c5fa9935b34f320bfec4114d675e028f3ad94f11cf9db37b9", size = 581148 },
{ url = "https://files.pythonhosted.org/packages/a1/b4/6b0291a590c2b0417cfdb64ccb8ea242f270a46ed429c641fbc2bfab77e0/tree_sitter-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:f58bb4956917715ec4d5a28681829a8dad5c342cafd4aea269f9132a83ca9b34", size = 120207 },
{ url = "https://files.pythonhosted.org/packages/a8/18/542fd844b75272630229c9939b03f7db232c71a9d82aadc59c596319ea6a/tree_sitter-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:23641bd25dcd4bb0b6fa91b8fb3f46cc9f1c9f475efe4d536d3f1f688d1b84c8", size = 108232 },
dependencies = [
{ name = "click" },
{ name = "typing-extensions" },
]
[[package]]
name = "tree-sitter-zig"
version = "1.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5c/97/75967b81460e0ce999de4736b9ac189dcd5ad1c85aabcc398ba529f4838e/tree_sitter_zig-1.1.2.tar.gz", hash = "sha256:da24db16df92f7fcfa34448e06a14b637b1ff985f7ce2ee19183c489e187a92e", size = 194084 }
sdist = { url = "https://files.pythonhosted.org/packages/29/67/88189eb827c491646511dc6c806e2e6e543241cae5438383aa042b1dfa40/typer_slim-0.15.2.tar.gz", hash = "sha256:4a666bb7839a88f51dd25d078d36dbc1d0f37c8c2696e184fbc1f3eaa314a91b", size = 100755 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/c6/db41d3f6c7c0174db56d9122a2a4d8b345c377ca87268e76557b2879675e/tree_sitter_zig-1.1.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e7542354a5edba377b5692b2add4f346501306d455e192974b7e76bf1a61a282", size = 61900 },
{ url = "https://files.pythonhosted.org/packages/5a/78/93d32fea98b3b031bc0fbec44e27f2b8cc1a1a8ff5a99dfb1a8f85b11d43/tree_sitter_zig-1.1.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:daa2cdd7c1a2d278f2a917c85993adb6e84d37778bfc350ee9e342872e7f8be2", size = 67837 },
{ url = "https://files.pythonhosted.org/packages/40/45/ef5afd6b79bd58731dae2cf61ff7960dd616737397db4d2e926457ff24b7/tree_sitter_zig-1.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1962e95067ac5ee784daddd573f828ef32f15e9c871967df6833d3d389113eae", size = 83391 },
{ url = "https://files.pythonhosted.org/packages/78/02/275523eb05108d83e154f52c7255763bac8b588ae14163563e19479322a7/tree_sitter_zig-1.1.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e924509dcac5a6054da357e3d6bcf37ea82984ee1d2a376569753d32f61ea8bb", size = 82323 },
{ url = "https://files.pythonhosted.org/packages/ef/e9/ff3c11097e37d4d899155c8fbdf7531063b6d15ee252b2e01ce0063f0218/tree_sitter_zig-1.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d8f463c370cdd71025b8d40f90e21e8fc25c7394eb64ebd53b1e566d712a3a68", size = 81383 },
{ url = "https://files.pythonhosted.org/packages/ab/5c/f5fb2ce355bbd381e647b04e8b2078a4043e663b6df6145d87550d3c3fe5/tree_sitter_zig-1.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:7b94f00a0e69231ac4ebf0aa763734b9b5637e0ff13634ebfe6d13fadece71e9", size = 65105 },
{ url = "https://files.pythonhosted.org/packages/34/8d/c0a481cc7bba9d39c533dd3098463854b5d3c4e6134496d9d83cd1331e51/tree_sitter_zig-1.1.2-cp39-abi3-win_arm64.whl", hash = "sha256:88152ebeaeca1431a6fc943a8b391fee6f6a8058f17435015135157735061ddf", size = 63219 },
{ url = "https://files.pythonhosted.org/packages/9e/84/9b68e98bf7417d25e38b27a0296bfcbc6719b15d7000f4c09d9716fa9d11/typer_slim-0.15.2-py3-none-any.whl", hash = "sha256:4273014a3378b24367bffed45c2ce8dd3d85bd201a6f02e51ba6b19f336009be", size = 45117 },
]

@@ -330,8 +224,8 @@

name = "zig-codeblocks"
version = "0.2.3"
version = "0.3.0"
source = { editable = "." }
dependencies = [
{ name = "more-itertools" },
{ name = "tree-sitter" },
{ name = "tree-sitter-zig" },
[package.optional-dependencies]
cli = [
{ name = "typer-slim" },
]

@@ -343,3 +237,2 @@

{ name = "pytest" },
{ name = "pytest-cov" },
{ name = "ruff" },

@@ -349,7 +242,4 @@ ]

[package.metadata]
requires-dist = [
{ name = "more-itertools", specifier = "~=10.6" },
{ name = "tree-sitter", specifier = "~=0.24.0" },
{ name = "tree-sitter-zig", specifier = ">=1.1.2,<2" },
]
requires-dist = [{ name = "typer-slim", marker = "extra == 'cli'", specifier = ">=0.15.2" }]
provides-extras = ["cli"]

@@ -360,4 +250,3 @@ [package.metadata.requires-dev]

{ name = "pytest", specifier = ">=8.3.4" },
{ name = "pytest-cov", specifier = ">=6.0.0" },
{ name = "ruff", specifier = ">=0.9.4" },
]
from zig_codeblocks.styling import Color, Style, Theme
KEYWORDS = frozenset(
{
"addrspace",
"align",
"allowzero",
"and",
"anyframe",
"anytype",
"asm",
"async",
"await",
"break",
"callconv",
"catch",
"comptime",
"const",
"continue",
"defer",
"else",
"enum",
"errdefer",
"error",
"export",
"extern",
"fn",
"for",
"if",
"inline",
"linksection",
"noalias",
"noinline",
"nosuspend",
"opaque",
"or",
"orelse",
"packed",
"pub",
"resume",
"return",
"struct",
"suspend",
"switch",
"test",
"threadlocal",
"try",
"union",
"unreachable",
"usingnamespace",
"var",
"volatile",
"while",
}
)
IDENTIFIERS = frozenset({"identifier", "c"})
NUMERIC_LITERALS = frozenset({"integer", "float"})
PRIMITIVE_VALUES = frozenset({"true", "false", "null", "undefined"})
TYPES = frozenset(
{
"anyerror",
"anyopaque",
"bool",
"builtin_type",
"c_char",
"c_int",
"c_long",
"c_longdouble",
"c_longlong",
"c_short",
"c_uint",
"c_ulong",
"c_ulonglong",
"c_ushort",
"comptime_float",
"comptime_int",
"f128",
"f16",
"f32",
"f64",
"f80",
"isize",
"noreturn",
"type",
"usize",
"void",
}
)
STRINGLIKE = frozenset(
{"string_content", "multiline_string", '"', "'", "character_content"}
)
DEFAULT_THEME = Theme(
builtin_identifiers=Style(Color.BLUE, bold=True),
calls=Style(Color.BLUE),
comments=Style(Color.GRAY),
keywords=Style(Color.MAGENTA),
numeric=Style(Color.CYAN),
primitive_values=Style(Color.CYAN),
strings=Style(Color.GREEN),
types=Style(Color.ORANGE),
)
from __future__ import annotations
import re
from typing import TYPE_CHECKING, TypeVar, cast
from more_itertools import peekable
from zig_codeblocks import consts
from zig_codeblocks.parsing import extract_codeblocks, tokenize_zig
from zig_codeblocks.styling import RESET, Style, Theme
if TYPE_CHECKING:
from collections.abc import Iterator
from zig_codeblocks.parsing import Token
Body = list[str | Style]
T = TypeVar("T")
def _get_style(kind: str, theme: Theme) -> Style | None:
for options, style in (
(consts.IDENTIFIERS, theme.get("identifiers")),
(consts.KEYWORDS, theme.get("keywords")),
(consts.NUMERIC_LITERALS, theme.get("numeric")),
(consts.PRIMITIVE_VALUES, theme.get("primitive_values")),
(consts.STRINGLIKE, theme.get("strings")),
(consts.TYPES, theme.get("types")),
):
if kind in options:
return style
if kind == "comment":
return theme.get("comments")
if kind == "builtin_identifier":
return theme.get("builtin_identifiers")
return None
def _last_applied_style(body: Body) -> Style | str | None:
return next(
(item for item in body[::-1] if not isinstance(item, str) or item == RESET),
None,
)
def _adjust_string_idents(body: Body, token: Token, theme: Theme) -> None:
# Special case for `whatever.@"something here"`,
# which is an identifier, so should have no string highlighting
identifier_style = theme.get("identifiers")
string_style = theme.get("strings")
if not ((identifier_style or string_style) and token.kind == '"'):
return
if not string_style:
if len(body) > 2 and body[-3] == "@":
body.insert(-2, cast(Style, identifier_style))
return
if _last_applied_style(body) == string_style and len(body) > 3 and body[-4] == "@":
if identifier_style:
body[-3] = identifier_style
else:
del body[-3]
def _process_zig_tokens(
source: bytes, tokens: Iterator[Token], theme: Theme = consts.DEFAULT_THEME
) -> str:
body: Body = []
pointer = 0
tokens = peekable(tokens)
token = next(tokens)
def skip_to_token() -> None:
nonlocal pointer
filler = source[pointer : token.byte_range.start]
match filler.isspace(), _last_applied_style(body):
case (_, None):
pass
case (True, Style(underline=True) | Style(bold=True)) | (False, Style()):
body.append(RESET)
body.append(filler.decode())
pointer = token.byte_range.start
while pointer < len(source):
if token.byte_range.start > pointer:
skip_to_token()
continue
style = (
theme.get("calls")
if token.kind == "identifier"
and (next_token := tokens.peek(None))
and next_token.kind == "("
else _get_style(token.kind, theme)
)
if style is None:
if _last_applied_style(body) not in (RESET, None):
body.append(RESET)
elif _last_applied_style(body) is not style:
body.append(style)
_adjust_string_idents(body, token, theme)
body.append(token.value.decode())
pointer = token.byte_range.stop
try:
token = next(tokens)
except StopIteration:
break
return "".join(map(str, body))
def highlight_zig_code(source: str | bytes, theme: Theme = consts.DEFAULT_THEME) -> str:
"""
Return an ANSI syntax-highlighted version of the given Zig source code.
Assumes UTF-8 if source is `bytes`.
"""
if isinstance(source, str):
source = source.encode()
return _process_zig_tokens(source, tokenize_zig(source), theme)
def process_markdown(
source: str | bytes, theme: Theme = consts.DEFAULT_THEME, *, only_code: bool = False
) -> str:
"""
Return a Markdown source with Zig code blocks syntax-highlighted.
If `only_code` is True, only processed Zig code blocks will be returned.
Assumes UTF-8 if source is `bytes`.
"""
if isinstance(source, bytes):
source = source.decode()
zig_codeblocks = (
block.body for block in extract_codeblocks(source) if block.lang == "zig"
)
if only_code:
return "\n".join(
f"```ansi\n{highlight_zig_code(code, theme)}\n```"
for code in zig_codeblocks
)
for codeblock in zig_codeblocks:
highlighted_source = f"```ansi\n{highlight_zig_code(codeblock, theme)}\n```"
source = re.sub(
f"```zig\n{re.escape(codeblock)}(?:\r?\n)*```",
highlighted_source.replace("\\", r"\\"),
source,
)
return source
import re
from collections.abc import Iterator
from itertools import chain
from typing import NamedTuple
import tree_sitter as ts
import tree_sitter_zig
CODE_BLOCK_PATTERN = re.compile(
r"```(?:([A-Za-z0-9\-_\+\.#]+)(?:\r?\n)+([^\r\n].*?)|(.*?))```", re.DOTALL
)
ZIG_PARSER = ts.Parser(ts.Language(tree_sitter_zig.language()))
class Token(NamedTuple):
kind: str
value: bytes
byte_range: range
class CodeBlock(NamedTuple):
"""A code block extracted from a Markdown source."""
lang: str | None
body: str
def extract_codeblocks(source: str | bytes) -> Iterator[CodeBlock]:
"""Yield CodeBlocks from a Markdown source. Assumes UTF-8 if source is `bytes`."""
if isinstance(source, bytes):
source = source.decode()
for m in CODE_BLOCK_PATTERN.finditer(source):
lang, body, no_lang_body = m.groups()
yield (
CodeBlock(lang, body=body.strip("\r\n"))
if lang
else CodeBlock(lang=None, body=no_lang_body.strip("\r\n"))
)
def tokenize_zig(src: bytes) -> Iterator[Token]:
tree = ZIG_PARSER.parse(src)
return _traverse(tree.root_node, src)
def _traverse(node: ts.Node, src: bytes) -> Iterator[Token]:
if node.child_count == 0:
yield Token(
node.type,
src[node.start_byte : node.end_byte],
range(node.start_byte, node.end_byte),
)
else:
yield from chain.from_iterable(_traverse(child, src) for child in node.children)
from __future__ import annotations
from dataclasses import KW_ONLY, dataclass
from enum import Enum
from itertools import compress
from typing import TypedDict
def _to_sgr(*args: str) -> str:
return f"\033[{';'.join(args)}m"
RESET = _to_sgr("0")
class Color(Enum):
"""
An enumeration of 3-bit ANSI colors.
Some names were adjusted to match Discord's style.
"""
GRAY = "30"
RED = "31"
GREEN = "32"
ORANGE = "33"
BLUE = "34"
MAGENTA = "35"
CYAN = "36"
WHITE = "37" # Black for light mode
@dataclass(slots=True, frozen=True)
class Style:
"""
A style for syntax highlighting.
Takes a `Color` and can optionally be bold and/or underlined.
Produces an SGR sequence when converted to a string.
"""
color: Color
_: KW_ONLY
bold: bool = False
underline: bool = False
def __str__(self) -> str:
modifiers = compress(("1", "4"), (self.bold, self.underline))
return _to_sgr(self.color.value, *modifiers)
class Theme(TypedDict, total=False):
"""A theme for syntax highlighting Zig code."""
builtin_identifiers: Style
calls: Style
comments: Style
identifiers: Style
keywords: Style
numeric: Style
strings: Style
primitive_values: Style
types: Style

Sorry, the diff of this file is not supported yet