🗝
summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock423
-rw-r--r--Cargo.toml7
-rw-r--r--src/main.rs213
4 files changed, 644 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..2209fa6
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,423 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cc"
+version = "1.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cgit-syntect"
+version = "0.1.0"
+dependencies = [
+ "syntect",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "indexmap"
+version = "2.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "libc"
+version = "0.2.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "line-wrap"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "onig"
+version = "6.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f"
+dependencies = [
+ "bitflags",
+ "libc",
+ "once_cell",
+ "onig_sys",
+]
+
+[[package]]
+name = "onig_sys"
+version = "69.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "plist"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9"
+dependencies = [
+ "base64",
+ "indexmap",
+ "line-wrap",
+ "quick-xml",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
+
+[[package]]
+name = "ryu"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.197"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.197"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syntect"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
+dependencies = [
+ "bincode",
+ "bitflags",
+ "flate2",
+ "fnv",
+ "once_cell",
+ "onig",
+ "plist",
+ "regex-syntax",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "thiserror",
+ "walkdir",
+ "yaml-rust",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..928f8ba
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "cgit-syntect"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+syntect = { version = "5.2.0", default-features = false, features = ["parsing", "yaml-load", "regex-onig", "plist-load"] }
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..74db5cd
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,213 @@
+use std::{path::PathBuf, str::FromStr};
+
+use syntect::{
+    dumps::{dump_to_uncompressed_file, from_uncompressed_dump_file},
+    highlighting::{Color, FontStyle, ScopeSelectors, ThemeSet},
+    parsing::{ParseState, ScopeStack, SyntaxSet, SyntaxSetBuilder},
+};
+
+fn main() {
+    match std::env::var("CGIT_SYNTECT_MODE").as_deref().ok() {
+        Some("compile") => compile(),
+        Some("theme") => theme(),
+        None | Some("") | Some("highlight") => highlight(),
+        _ => panic!("unknown mode"),
+    }
+}
+
+fn compile() {
+    let mut args = std::env::args();
+    args.next(); // arg0
+    let input: PathBuf = args.next().unwrap().into();
+    let output: PathBuf = args.next().unwrap().into();
+    let mut builder = SyntaxSetBuilder::new();
+    builder.add_plain_text_syntax();
+    builder.add_from_folder(input, true).unwrap();
+    let set = builder.build();
+    dump_to_uncompressed_file(&set, output).unwrap();
+}
+
+fn theme() {
+    let mut args = std::env::args();
+    args.next(); // arg0
+    let input: PathBuf = args.next().unwrap().into();
+    let css_path: PathBuf = args.next().unwrap().into();
+    let scopes_path: PathBuf = args.next().unwrap().into();
+
+    let theme = ThemeSet::get_theme(input).unwrap();
+    let mut css_gen = Vec::new();
+    let mut scopes_gen = Vec::new();
+
+    let mut global_css = String::new();
+    global_css.push_str(".highlight {\n");
+    if let Some(bg) = theme.settings.background {
+        global_css.push_str("  background-color: #");
+        global_css.extend(hex_color(bg));
+        global_css.push_str(";\n");
+    }
+    global_css.push_str("}\n");
+    css_gen.push(global_css);
+
+    for (idx, item) in theme.scopes.into_iter().enumerate() {
+        let selectors_str = selectors_to_string(item.scope);
+
+        let mut css = String::new();
+
+        css.push_str(&format!("/* {selectors_str} */\n"));
+        css.push_str(&format!(".hl-style{idx} {{\n"));
+
+        if let Some(fg) = item.style.foreground {
+            css.push_str("  color: #");
+            css.extend(hex_color(fg));
+            css.push_str(";\n");
+        }
+
+        if let Some(bg) = item.style.background {
+            css.push_str("  background-color: #");
+            css.extend(hex_color(bg));
+            css.push_str(";\n");
+        }
+
+        if let Some(font) = item.style.font_style {
+            if font.contains(FontStyle::BOLD) {
+                css.push_str("  font-weight: bold;\n");
+            }
+            if font.contains(FontStyle::UNDERLINE) {
+                css.push_str("  text-decoration: underline;\n");
+            }
+            if font.contains(FontStyle::ITALIC) {
+                css.push_str("  font-style: italic;\n");
+            }
+        }
+
+        css.push_str("}\n");
+        css_gen.push(css);
+
+        scopes_gen.push(selectors_str);
+    }
+
+    std::fs::write(css_path, css_gen.join("\n")).unwrap();
+    std::fs::write(scopes_path, scopes_gen.join("\n")).unwrap();
+}
+
+fn selectors_to_string(selectors: ScopeSelectors) -> String {
+    let mut scopes = Vec::new();
+    for scope in selectors.selectors {
+        let mut ser = String::new();
+        ser.push_str(scope.path.to_string().trim());
+        for exc in scope.excludes {
+            ser.push_str(" -");
+            ser.push_str(exc.to_string().trim());
+        }
+        scopes.push(ser);
+    }
+    scopes.join(", ")
+}
+
+fn hex_color(color: Color) -> [char; 8] {
+    const HEX_DIGIT_LOOKUP: &[u8; 16] = b"0123456789abcdef";
+    [
+        HEX_DIGIT_LOOKUP[(color.r >> 4) as usize] as char,
+        HEX_DIGIT_LOOKUP[(color.r & 0xf) as usize] as char,
+        HEX_DIGIT_LOOKUP[(color.g >> 4) as usize] as char,
+        HEX_DIGIT_LOOKUP[(color.g & 0xf) as usize] as char,
+        HEX_DIGIT_LOOKUP[(color.b >> 4) as usize] as char,
+        HEX_DIGIT_LOOKUP[(color.b & 0xf) as usize] as char,
+        HEX_DIGIT_LOOKUP[(color.a >> 4) as usize] as char,
+        HEX_DIGIT_LOOKUP[(color.a & 0xf) as usize] as char,
+    ]
+}
+
+fn highlight() {
+    let mut args = std::env::args();
+    args.next(); // arg0
+    let highlight_file: PathBuf = args.next().unwrap().into();
+
+    let syntax_dump = std::env::var("CGIT_SYNTECT_SYNTAXES")
+        .map(PathBuf::from)
+        .unwrap_or_else(|_| PathBuf::from("/usr/share/cgit-syntect/syntax.packdump"));
+    let set: SyntaxSet = from_uncompressed_dump_file(syntax_dump).unwrap();
+
+    let scope_dump = std::env::var("CGIT_SYNTECT_SCOPES")
+        .map(PathBuf::from)
+        .unwrap_or_else(|_| PathBuf::from("/usr/share/cgit-syntect/scopes"));
+    let scopes = std::fs::read_to_string(scope_dump)
+        .unwrap()
+        .lines()
+        .map(ScopeSelectors::from_str)
+        .map(|sels| sels.unwrap())
+        .collect::<Vec<_>>();
+
+    let mut line = String::new();
+    std::io::stdin().read_line(&mut line).unwrap();
+    if !line.ends_with('\n') {
+        line.push('\n');
+    }
+
+    let file_name = highlight_file
+        .file_name()
+        .and_then(|n| n.to_str())
+        .unwrap_or("");
+    let extension = highlight_file
+        .extension()
+        .and_then(|x| x.to_str())
+        .unwrap_or("");
+    let syntax = set
+        .find_syntax_by_extension(file_name)
+        .or_else(|| set.find_syntax_by_extension(extension))
+        .or_else(|| set.find_syntax_by_first_line(&line))
+        .unwrap_or_else(|| set.find_syntax_plain_text());
+
+    print!(r#"<pre class="highlight"><code>"#);
+
+    let mut parse = ParseState::new(syntax);
+    let mut stack = ScopeStack::new();
+    loop {
+        let mut cursor = 0;
+        let ops = parse.parse_line(&line, &set).unwrap();
+        let mut spans = Vec::new();
+
+        for (at, op) in ops {
+            if at != cursor {
+                let mut max_power = f64::NEG_INFINITY;
+                let mut selected = None;
+                for (idx, scope) in scopes.iter().enumerate() {
+                    if let Some(power) = scope.does_match(stack.as_slice()) {
+                        if power.0 > max_power {
+                            max_power = power.0;
+                            selected = Some(idx);
+                        }
+                    }
+                }
+                spans.push((&line[cursor..at], selected));
+            }
+            stack.apply(&op).unwrap();
+            cursor = at;
+        }
+
+        if line.len() != cursor {
+            spans.push((&line[cursor..], None));
+        }
+
+        for (section, class) in spans {
+            if let Some(idx) = class {
+                print!(r#"<span class="hl-style{idx}">"#)
+            }
+            print!("{section}");
+            if class.is_some() {
+                print!("</span>");
+            }
+        }
+
+        line.clear();
+        if std::io::stdin().read_line(&mut line).unwrap() == 0 {
+            println!();
+            break;
+        }
+        if !line.ends_with('\n') {
+            line.push('\n');
+        }
+    }
+
+    print!("</code></pre>");
+}