diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml new file mode 100644 index 00000000..8ceddf68 --- /dev/null +++ b/.github/workflows/bench.yml @@ -0,0 +1,32 @@ +name: bench + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +jobs: + benchmarks: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - uses: actions-rust-lang/setup-rust-toolchain@2fcdc490d667999e01ddbbf0f2823181beef6b39 + with: + toolchain: stable + + - run: cargo install cargo-codspeed --locked + + - name: Build the benchmark target(s) + run: cargo codspeed build + + - name: Run the benchmarks + uses: CodSpeedHQ/action@653fdc30e6c40ffd9739e40c8a0576f4f4523ca1 + with: + mode: instrumentation + run: cargo codspeed run diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e30122f7..0f8b18c5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -76,7 +76,7 @@ jobs: components: clippy - name: Run clippy - run: cargo clippy --all-targets --all-features -- -D warnings + run: cargo clippy --all-targets --all-features --benches -- -D warnings cargo-check: runs-on: ubuntu-24.04 diff --git a/Cargo.lock b/Cargo.lock index e44cf1e1..a959c116 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,6 +88,15 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arraydeque" version = "0.5.1" @@ -132,6 +141,15 @@ 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" @@ -162,6 +180,12 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "bytes" version = "1.10.1" @@ -183,6 +207,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clap" version = "4.5.47" @@ -203,6 +233,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim", + "terminal_size", ] [[package]] @@ -223,12 +254,86 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "codspeed" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35584c5fcba8059780748866387fb97c5a203bcfc563fc3d0790af406727a117" +dependencies = [ + "anyhow", + "bincode", + "colored", + "glob", + "libc", + "nix", + "serde", + "serde_json", + "statrs", + "uuid", +] + +[[package]] +name = "codspeed-divan-compat" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf64eda57508448d59efd940bad62ede7c50b0d451a150b8d6a0eca642792a6" +dependencies = [ + "codspeed", + "codspeed-divan-compat-macros", + "codspeed-divan-compat-walltime", +] + +[[package]] +name = "codspeed-divan-compat-macros" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058167258e819b16a4ba601fdfe270349ef191154758dbce122c62a698f70ba8" +dependencies = [ + "divan-macros", + "itertools", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "codspeed-divan-compat-walltime" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f9866ee3a4ef9d2868823ea5811886763af244f2df584ca247f49281c43f1f" +dependencies = [ + "cfg-if", + "clap", + "codspeed", + "condtype", + "divan-macros", + "libc", + "regex-lite", +] + [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + +[[package]] +name = "condtype" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" + [[package]] name = "config" version = "0.15.15" @@ -423,6 +528,17 @@ dependencies = [ "syn", ] +[[package]] +name = "divan-macros" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "djls" version = "5.2.0" @@ -537,6 +653,7 @@ version = "0.0.0" dependencies = [ "anyhow", "camino", + "codspeed-divan-compat", "djls-conf", "djls-source", "djls-workspace", @@ -784,6 +901,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "hashbrown" version = "0.14.5" @@ -931,9 +1054,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown 0.15.5", @@ -997,12 +1120,31 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "json5" version = "0.4.1" @@ -1142,6 +1284,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.3", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "notify" version = "8.2.0" @@ -1181,6 +1335,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -1340,6 +1503,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -1415,6 +1587,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" + [[package]] name = "regex-syntax" version = "0.8.6" @@ -1469,6 +1647,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" @@ -1545,9 +1729,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.223" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" dependencies = [ "serde_core", "serde_derive", @@ -1566,18 +1750,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.223" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.223" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", @@ -1680,6 +1864,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "statrs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" +dependencies = [ + "approx", + "num-traits", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1727,6 +1921,16 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "terminal_size" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +dependencies = [ + "rustix", + "windows-sys 0.60.2", +] + [[package]] name = "thin-vec" version = "0.2.14" @@ -1893,18 +2097,30 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ - "serde", + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", ] [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] @@ -2096,6 +2312,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -2133,6 +2360,65 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +dependencies = [ + "unicode-ident", +] + [[package]] name = "which" version = "8.0.0" diff --git a/Cargo.toml b/Cargo.toml index 4464d65a..97d151c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ url = "2.5" which = "8.0" # testing +divan = { package = "codspeed-divan-compat", version = "3.0" } insta = { version = "1.43", features = ["yaml"] } tempfile = "3.22" diff --git a/Justfile b/Justfile index 90347660..61ca421e 100644 --- a/Justfile +++ b/Justfile @@ -27,7 +27,7 @@ clean: cargo clean clippy *ARGS: - cargo clippy --all-targets --all-features --fix {{ ARGS }} -- -D warnings + cargo clippy --all-targets --all-features --benches --fix {{ ARGS }} -- -D warnings fmt *ARGS: cargo +nightly fmt {{ ARGS }} diff --git a/crates/djls-templates/Cargo.toml b/crates/djls-templates/Cargo.toml index e3b867a2..895c9c0d 100644 --- a/crates/djls-templates/Cargo.toml +++ b/crates/djls-templates/Cargo.toml @@ -16,8 +16,13 @@ toml = { workspace = true } [dev-dependencies] camino = { workspace = true } +divan = { workspace = true } insta = { workspace = true } tempfile = { workspace = true } +[[bench]] +name = "lexer" +harness = false + [lints] workspace = true diff --git a/crates/djls-templates/benches/lexer.rs b/crates/djls-templates/benches/lexer.rs new file mode 100644 index 00000000..7c4013e9 --- /dev/null +++ b/crates/djls-templates/benches/lexer.rs @@ -0,0 +1,61 @@ +mod support; + +use divan::Bencher; +use support::db::Db; +use support::fixtures::template_fixtures; +use support::fixtures::TemplateFixture; + +fn main() { + divan::main(); +} + +#[divan::bench(args = template_fixtures())] +fn lex_template_fixture(fixture: &TemplateFixture) { + let mut db = Db::new(); + let file = db.file_with_contents(fixture.path.clone(), &fixture.source); + let tokens = djls_templates::lex_template(&db, file); + divan::black_box(tokens.stream(&db).len()); +} + +#[divan::bench] +fn lex_all_templates(bencher: Bencher) { + let fixtures = template_fixtures(); + bencher.bench_local(|| { + let mut db = Db::new(); + for fixture in fixtures { + let file = db.file_with_contents(fixture.path.clone(), &fixture.source); + let tokens = djls_templates::lex_template(&db, file); + divan::black_box(tokens.stream(&db).len()); + } + }); +} + +#[divan::bench(args = template_fixtures())] +fn lex_template_incremental(bencher: Bencher, fixture: &TemplateFixture) { + let mut db = Db::new(); + let file = db.file_with_contents(fixture.path.clone(), &fixture.source); + + // Prime caches with the baseline source so the benchmark measures the incremental path. + let _ = djls_templates::lex_template(&db, file); + + let original = fixture.source.clone(); + let modified = { + let mut text = original.clone(); + text.push(' '); + text + }; + + let mut revision = 1_u64; + let mut use_modified = true; + + bencher.bench_local(move || { + let contents = if use_modified { &modified } else { &original }; + use_modified = !use_modified; + + db.set_file_contents(file, contents, revision); + revision = revision.wrapping_add(1); + + let tokens = djls_templates::lex_template(&db, file); + divan::black_box(tokens.stream(&db).len()); + }); +} diff --git a/crates/djls-templates/benches/support/db.rs b/crates/djls-templates/benches/support/db.rs new file mode 100644 index 00000000..dfd7de52 --- /dev/null +++ b/crates/djls-templates/benches/support/db.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; +use std::io; +use std::sync::Arc; +use std::sync::Mutex; + +use camino::Utf8Path; +use camino::Utf8PathBuf; +use djls_source::Db as SourceDb; +use djls_source::File; +use djls_templates::Db as TemplateDb; +use salsa::Setter; + +#[salsa::db] +#[derive(Clone)] +pub(crate) struct Db { + sources: Arc>>, + storage: salsa::Storage, +} + +impl Db { + #[must_use] + pub(crate) fn new() -> Self { + Self { + sources: Arc::new(Mutex::new(HashMap::new())), + storage: salsa::Storage::default(), + } + } + + pub(crate) fn file_with_contents(&mut self, path: Utf8PathBuf, contents: &str) -> File { + self.sources + .lock() + .expect("sources lock poisoned") + .insert(path.clone(), contents.to_string()); + File::new(self, path, 0) + } + + pub(crate) fn set_file_contents(&mut self, file: File, contents: &str, revision: u64) { + let path = file.path(self); + self.sources + .lock() + .expect("sources lock poisoned") + .insert(path.clone(), contents.to_string()); + file.set_revision(self).to(revision); + } +} + +impl Default for Db { + fn default() -> Self { + Self::new() + } +} + +#[salsa::db] +impl salsa::Database for Db {} + +#[salsa::db] +impl SourceDb for Db { + fn read_file_source(&self, path: &Utf8Path) -> io::Result { + let sources = self.sources.lock().expect("sources lock poisoned"); + Ok(sources.get(path).cloned().unwrap_or_default()) + } +} + +#[salsa::db] +impl TemplateDb for Db {} diff --git a/crates/djls-templates/benches/support/fixtures.rs b/crates/djls-templates/benches/support/fixtures.rs new file mode 100644 index 00000000..01fd4d80 --- /dev/null +++ b/crates/djls-templates/benches/support/fixtures.rs @@ -0,0 +1,94 @@ +use std::fmt; +use std::fs; +use std::io; +use std::sync::OnceLock; + +use camino::Utf8Path; +use camino::Utf8PathBuf; + +#[derive(Clone)] +pub(crate) struct TemplateFixture { + pub label: String, + pub path: Utf8PathBuf, + pub source: String, +} + +impl fmt::Display for TemplateFixture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.label) + } +} + +pub(crate) fn template_fixtures() -> &'static [TemplateFixture] { + static FIXTURES: OnceLock> = OnceLock::new(); + FIXTURES.get_or_init(load_template_fixtures).as_slice() +} + +fn load_template_fixtures() -> Vec { + let workspace_root = option_env!("CARGO_WORKSPACE_DIR") + .and_then(|value| if value.is_empty() { None } else { Some(value) }) + .map_or_else( + || panic!("CARGO_WORKSPACE_DIR must be configured for benchmarks"), + Utf8PathBuf::from, + ); + let template_root = workspace_root.join("tests/project"); + + let mut fixtures = Vec::new(); + collect_template_files( + template_root.as_path(), + template_root.as_path(), + &mut fixtures, + ) + .unwrap_or_else(|err| panic!("failed to load template fixtures: {err}")); + + fixtures.sort_by(|a, b| a.label.cmp(&b.label)); + assert!( + !fixtures.is_empty(), + "no templates discovered under {template_root}", + ); + + fixtures +} + +fn collect_template_files( + root: &Utf8Path, + dir: &Utf8Path, + fixtures: &mut Vec, +) -> io::Result<()> { + for entry in fs::read_dir(dir.as_std_path())? { + let entry = entry?; + let file_type = entry.file_type()?; + let path = entry.path(); + let utf8_path = Utf8PathBuf::from_path_buf(path.clone()).map_err(|original| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("path {} is not valid UTF-8", original.display()), + ) + })?; + + if file_type.is_dir() { + collect_template_files(root, utf8_path.as_path(), fixtures)?; + continue; + } + + if file_type.is_file() + && matches!(utf8_path.extension(), Some("html" | "htm" | "txt" | "xml")) + { + let source = fs::read_to_string(utf8_path.as_std_path())?; + let relative = utf8_path.strip_prefix(root).map_err(|err| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("{utf8_path} is not under {root}: {err}"), + ) + })?; + + fixtures.push(TemplateFixture { + label: relative.to_string(), + path: utf8_path, + source, + }); + } + } + + Ok(()) +} diff --git a/crates/djls-templates/benches/support/mod.rs b/crates/djls-templates/benches/support/mod.rs new file mode 100644 index 00000000..89a8ebdb --- /dev/null +++ b/crates/djls-templates/benches/support/mod.rs @@ -0,0 +1,2 @@ +pub mod db; +pub mod fixtures; diff --git a/crates/djls-templates/src/lib.rs b/crates/djls-templates/src/lib.rs index a27cf4fe..eb7d99a7 100644 --- a/crates/djls-templates/src/lib.rs +++ b/crates/djls-templates/src/lib.rs @@ -68,7 +68,7 @@ use tokens::TokenStream; /// Lex a template file into tokens. #[salsa::tracked] -fn lex_template(db: &dyn Db, file: File) -> TokenStream<'_> { +pub fn lex_template(db: &dyn Db, file: File) -> TokenStream<'_> { let source = file.source(db); if *source.kind() != FileKind::Template { return TokenStream::new(db, vec![]);