Skip to content

Commit 0cb2add

Browse files
committed
add syntax highlight module
1 parent 2bc3016 commit 0cb2add

13 files changed

Lines changed: 347 additions & 16 deletions

File tree

Cargo.lock

Lines changed: 76 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ serde_yaml = "0.9"
7373
rmp-serde = "1.1"
7474
protobuf = "3.7.2"
7575
regex = "1.10.3"
76+
tree-sitter-highlight = "0.25.3"
77+
tree-sitter-yaml = "0.7.0"
78+
tree-sitter-xml = "0.7.0"
79+
tree-sitter = "0.25.3"
7680

7781
[patch.crates-io]
7882
# tokio = { path = "../tokio/tokio" }

mitmproxy-rs/mitmproxy_rs/__init__.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ from __future__ import annotations
22

33
from typing import Any, Literal
44
from typing import final, overload, TypeVar
5-
from . import certs, contentviews, dns, local, process_info, tun, udp, wireguard
5+
from . import certs, contentviews, dns, local, process_info, tun, udp, wireguard, syntax_highlight
66

77
T = TypeVar("T")
88

@@ -61,6 +61,7 @@ __all__ = [
6161
"dns",
6262
"local",
6363
"process_info",
64+
"syntax_highlight",
6465
"tun",
6566
"udp",
6667
"wireguard",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from __future__ import annotations
2+
3+
def syntax_highlight(s: str, language: str) -> list[tuple[str, str]]:
4+
pass
5+
6+
def all_tags(language: str) -> list[str]:
7+
pass
8+
9+
__all__ = ["syntax_highlight", "all_tags"]

mitmproxy-rs/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod dns_resolver;
1212
mod process_info;
1313
mod server;
1414
mod stream;
15+
mod syntax_highlight;
1516
pub mod task;
1617
mod udp_client;
1718
mod util;
@@ -126,6 +127,14 @@ mod mitmproxy_rs {
126127

127128
Ok(())
128129
}
130+
131+
#[pymodule]
132+
mod syntax_highlight {
133+
#[pymodule_export]
134+
use crate::syntax_highlight::highlight;
135+
#[pymodule_export]
136+
use crate::syntax_highlight::all_tags;
137+
}
129138
}
130139

131140
trait AddContentview {

mitmproxy-rs/src/stream.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ impl Stream {
186186
}
187187
_ => (),
188188
},
189-
TunnelInfo::None {} => (),
189+
TunnelInfo::None => (),
190190
}
191191
match default {
192192
Some(x) => Ok(x),
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#[allow(unused_imports)]
2+
use anyhow::{anyhow, Result};
3+
4+
use pyo3::{exceptions::PyValueError, prelude::*};
5+
6+
fn str_to_language(s: &str) -> PyResult<mitmproxy::syntax_highlight::Language> {
7+
match s {
8+
"xml" => Ok(mitmproxy::syntax_highlight::Language::Xml),
9+
"yaml" => Ok(mitmproxy::syntax_highlight::Language::Yaml),
10+
other => Err(PyErr::new::<PyValueError, _>(format!(
11+
"Unsupported language: {other}"
12+
))),
13+
}
14+
}
15+
16+
/// Transform a text into tagged chunks for text.
17+
#[pyfunction]
18+
pub fn highlight(s: String, language: &str) -> PyResult<Vec<(&'static str, String)>> {
19+
let language = str_to_language(language)?;
20+
language.highlight(s.as_bytes())
21+
.map_err(|e| PyValueError::new_err(e.to_string()))
22+
}
23+
24+
/// Return the list of all possible tags for a given language.
25+
#[pyfunction]
26+
pub fn all_tags(language: &str) -> PyResult<&[&str]> {
27+
let language = str_to_language(language)?;
28+
Ok(language.all_tags())
29+
}

src/contentviews/hex_dump.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
use crate::contentviews::hex_stream::is_binary;
12
use crate::contentviews::{Metadata, Prettify};
23
use pretty_hex::{HexConfig, PrettyHex};
3-
use crate::contentviews::hex_stream::is_binary;
44

55
pub struct HexDump;
66

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ pub mod network;
1010
pub mod packet_sources;
1111
pub mod processes;
1212
pub mod shutdown;
13+
pub mod syntax_highlight;
1314
#[cfg(windows)]
1415
pub mod windows;

src/syntax_highlight/common.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use super::Chunk;
2+
use anyhow::{Context, Result};
3+
use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
4+
5+
pub fn highlight(
6+
language: tree_sitter::Language,
7+
highlights_query: &str,
8+
tags: &[&'static str],
9+
input: &[u8],
10+
) -> Result<Vec<Chunk>> {
11+
let mut highlighter = Highlighter::new();
12+
let mut config = HighlightConfiguration::new(language, "", highlights_query, "", "")
13+
.context("failed to create highlight configuration")?;
14+
config.configure(tags);
15+
16+
let highlights = highlighter
17+
.highlight(&config, input, None, |_| None)
18+
.context("failed to highlight")?;
19+
20+
let mut chunks: Vec<Chunk> = Vec::new();
21+
let mut tag: Option<&'static str> = None;
22+
23+
for event in highlights {
24+
let event = event.context("highlighter failure")?;
25+
match event {
26+
HighlightEvent::Source { start, end } => {
27+
let contents = &input[start..end];
28+
let tag_str = tag.unwrap_or("");
29+
30+
match chunks.last_mut() {
31+
Some(x) if x.0 == tag_str => {
32+
x.1.push_str(&String::from_utf8_lossy(contents));
33+
}
34+
_ => chunks.push(
35+
(tag_str, String::from_utf8_lossy(contents).to_string())
36+
),
37+
}
38+
}
39+
HighlightEvent::HighlightStart(s) => {
40+
tag = Some(tags[s.0]);
41+
}
42+
HighlightEvent::HighlightEnd => {
43+
tag = None;
44+
}
45+
}
46+
}
47+
Ok(chunks)
48+
}
49+
50+
#[cfg(test)]
51+
pub(super) fn test_tags_ok(
52+
language: tree_sitter::Language,
53+
highlights_query: &str,
54+
tags: &[&'static str],
55+
) {
56+
let config = HighlightConfiguration::new(language, "", highlights_query, "", "").unwrap();
57+
for &tag in tags {
58+
assert!(
59+
config.names().iter().any(|name| name.contains(tag)),
60+
"Invalid tag: {},\nAllowed tags: {:?}",
61+
tag,
62+
config.names()
63+
);
64+
}
65+
}

0 commit comments

Comments
 (0)