Skip to content

Commit d145668

Browse files
committed
Format comments with topiary
1 parent 281d2e1 commit d145668

18 files changed

+258
-26
lines changed

.github/workflows/build_wheels.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ jobs:
1818
with:
1919
submodules: recursive
2020

21+
- uses: astral-sh/setup-uv@v6
22+
2123
- name: Build SDist
22-
run: python ./setup.py sdist
24+
run: uv build --sdist
2325

2426
- uses: actions/upload-artifact@v5
2527
with:
@@ -31,17 +33,16 @@ jobs:
3133
strategy:
3234
fail-fast: false
3335
matrix:
34-
python-version: ["3.10", "3.11", "3.12", "3.13"]
36+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
3537
steps:
3638
- uses: actions/checkout@v5
3739
- uses: actions/setup-python@v6
3840
with:
3941
python-version: ${{ matrix.python-version }}
42+
- uses: astral-sh/setup-uv@v6
4043
- name: Install dependencies
41-
run: pip install .[dev]
42-
- run: pytest
43-
44-
44+
run: uv sync --all-extras
45+
- run: uv run pytest
4546

4647
upload_all:
4748
needs: [build_sdist, check]

Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "zeekscript"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
[lib]
8+
name = "zeekscript"
9+
crate-type = ["cdylib"]
10+
11+
[dependencies]
12+
pyo3 = { version = "0.27.1", optional = true }
13+
thiserror = "2.0.17"
14+
topiary-core = { version = "0.7.0", default-features = false }
15+
topiary-tree-sitter-facade = { version = "0.7.0", default-features = false }
16+
tree-sitter-zeek = { git = "https://github.com/zeek/tree-sitter-zeek", version = "0.2.9" }
17+
18+
[dev-dependencies]
19+
insta = "1.43.2"
20+
21+
[features]
22+
default = ["python"]
23+
python = ["pyo3"]

pyproject.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[build-system]
2-
requires = ["setuptools"]
2+
requires = ["maturin>=1.8,<2.0"]
3+
build-backend = "maturin"
34

45
[project]
56
name = "zeekscript"
@@ -9,6 +10,9 @@ readme = "README.md"
910

1011
classifiers=[
1112
"Programming Language :: Python :: 3.7",
13+
"Programming Language :: Rust",
14+
"Programming Language :: Python :: Implementation :: CPython",
15+
"Programming Language :: Python :: Implementation :: PyPy",
1216
"License :: OSI Approved :: BSD License",
1317
"Topic :: Utilities",
1418
]
@@ -50,8 +54,8 @@ Repository = "https://github.com/zeek/zeekscript"
5054
zeek-format = "zeekscript.cli:zeek_format"
5155
zeek-script = "zeekscript.cli:zeek_script"
5256

53-
[tool.setuptools]
54-
packages = ["zeekscript"]
57+
[tool.maturin]
58+
features = ["pyo3/extension-module"]
5559

5660
[tool.ruff.lint]
5761
select = ["PL", "UP", "RUF", "N", "I", "RET"]

setup.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/lib.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use std::string::FromUtf8Error;
2+
3+
use thiserror::Error;
4+
use topiary_core::{FormatterError, TopiaryQuery};
5+
6+
#[derive(Error, Debug)]
7+
pub enum FormatError {
8+
#[error("parse error")]
9+
Parse,
10+
11+
#[error("internal query error")]
12+
Query(String),
13+
14+
#[error("idempotency violated")]
15+
Idempotency,
16+
17+
#[error("UTF8 conversion error")]
18+
UTF8(FromUtf8Error),
19+
20+
#[error("unknown error")]
21+
Unknown,
22+
}
23+
24+
const QUERY: &str = include_str!("query.scm");
25+
26+
pub fn format(
27+
input: &str,
28+
skip_idempotence: bool,
29+
tolerate_parsing_errors: bool,
30+
) -> Result<String, FormatError> {
31+
let mut output = Vec::new();
32+
33+
let grammar = topiary_tree_sitter_facade::Language::from(tree_sitter_zeek::LANGUAGE);
34+
35+
let query = TopiaryQuery::new(&grammar, QUERY).map_err(|e| match e {
36+
FormatterError::Query(m, e) => FormatError::Query(match e {
37+
None => m,
38+
Some(e) => format!("{m}: {e}"),
39+
}),
40+
_ => FormatError::Unknown,
41+
})?;
42+
43+
let language = topiary_core::Language {
44+
name: "zeek".to_string(),
45+
indent: Some("\t".into()),
46+
grammar,
47+
query,
48+
};
49+
50+
if let Err(e) = topiary_core::formatter(
51+
&mut input.as_bytes(),
52+
&mut output,
53+
&language,
54+
topiary_core::Operation::Format {
55+
skip_idempotence,
56+
tolerate_parsing_errors,
57+
},
58+
) {
59+
Err(match e {
60+
FormatterError::Query(m, e) => FormatError::Query(match e {
61+
None => m,
62+
Some(e) => format!("{m}: {e}"),
63+
}),
64+
FormatterError::Idempotence => FormatError::Idempotency,
65+
FormatterError::Parsing { .. } => FormatError::Parse,
66+
_ => FormatError::Unknown,
67+
})?;
68+
};
69+
70+
let output = String::from_utf8(output).map_err(FormatError::UTF8)?;
71+
72+
Ok(output)
73+
}
74+
75+
#[cfg(feature = "python")]
76+
#[pyo3::pymodule]
77+
mod zeekscript {
78+
use pyo3::{exceptions::PyException, pyfunction, PyResult};
79+
80+
#[pyfunction]
81+
fn format(input: &str) -> PyResult<String> {
82+
super::format(input, false, true).map_err(|e| PyException::new_err(e.to_string()))
83+
}
84+
}
85+
86+
#[cfg(test)]
87+
mod test {
88+
use insta::assert_debug_snapshot;
89+
90+
use crate::FormatError;
91+
92+
fn format(input: &str) -> Result<String, FormatError> {
93+
crate::format(input, false, false)
94+
}
95+
96+
#[test]
97+
fn comments() {
98+
assert_debug_snapshot!(format("# foo\n;1;"));
99+
assert_debug_snapshot!(format("##! foo\n;1;"));
100+
assert_debug_snapshot!(format("## foo\n1;"));
101+
assert_debug_snapshot!(format("##< foo\n1;"));
102+
103+
assert_debug_snapshot!(format("1;# foo\n;1;"));
104+
assert_debug_snapshot!(format("1;##! foo\n;1;"));
105+
assert_debug_snapshot!(format("1;## foo\n1;"));
106+
assert_debug_snapshot!(format("1;##< foo"));
107+
assert_debug_snapshot!(format("1;##< foo\n##< bar"));
108+
}
109+
}

src/query.scm

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
; Rules for formatting Spicy.
2+
;
3+
; Formatting is specified here in terms of tree-sitter nodes. We select nodes
4+
; with tree-sitter queries[^1] and then attach topiary formatting rules[^2] in
5+
; the captures.
6+
;
7+
; See the Development section in README.md for a workflow on how to modify or
8+
; extend these rules.
9+
10+
; [^1]: https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries
11+
; [^2]: https://github.com/tweag/topiary#design
12+
13+
; Comments are always followed by a linebreak.
14+
[
15+
(minor_comment)
16+
(zeekygen_head_comment)
17+
(zeekygen_prev_comment)
18+
(zeekygen_next_comment)
19+
] @append_hardline
20+
21+
; Comments are preceeded by a space.
22+
(
23+
[
24+
(_)
25+
(nl) @do_nothing
26+
]
27+
.
28+
[
29+
(minor_comment)
30+
(zeekygen_head_comment)
31+
(zeekygen_prev_comment)
32+
(zeekygen_next_comment)
33+
] @prepend_space
34+
)
35+
36+
; If we have multiple comments documenting an item with `##<` align them all.
37+
(zeekygen_prev_comment) @multi_line_indent_all
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
source: src/lib.rs
3+
expression: "format(\"##! foo\\n;1;\")"
4+
---
5+
Ok(
6+
"##! foo\n;1;\n",
7+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
source: src/lib.rs
3+
expression: "format(\"## foo\\n1;\")"
4+
---
5+
Ok(
6+
"## foo\n1;\n",
7+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
source: src/lib.rs
3+
expression: "format(\"##< foo\\n1;\")"
4+
---
5+
Ok(
6+
"##< foo\n1;\n",
7+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
source: src/lib.rs
3+
expression: "format(\"1;# foo\\n;1;\")"
4+
---
5+
Ok(
6+
"1; # foo\n;1;\n",
7+
)

0 commit comments

Comments
 (0)