diff --git a/Cargo.lock b/Cargo.lock index 11c6a8a..dfb8052 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,19 +23,19 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom", + "getrandom 0.2.16", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.4", "once_cell", "serde", "version_check", @@ -44,19 +44,13 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -118,9 +112,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -157,9 +151,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bitvec" @@ -173,29 +167,38 @@ dependencies = [ "wyz", ] +[[package]] +name = "browserslist-data" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e977366ea69a6e756ae616c0d5def0da9a3521fca5f91f447fdf613c928a15a" +dependencies = [ + "ahash 0.8.12", + "chrono", +] + [[package]] name = "browserslist-rs" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf0ca73de70c3da94e4194e4a01fe732378f55d47cf4c0588caab22a0dbfa14" +checksum = "8dd48a6ca358df4f7000e3fb5f08738b1b91a0e5d5f862e2f77b2b14647547f5" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", + "browserslist-data", "chrono", "either", - "indexmap", "itertools 0.13.0", "nom", - "once_cell", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytecheck" @@ -219,43 +222,37 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" -version = "1.7.2" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.1.21" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -289,7 +286,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.111", ] [[package]] @@ -306,15 +303,15 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "console" -version = "0.15.8" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width 0.1.14", - "windows-sys 0.52.0", + "once_cell", + "unicode-width 0.2.2", + "windows-sys 0.61.2", ] [[package]] @@ -354,9 +351,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -373,9 +370,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "cssparser" @@ -386,7 +383,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.11.2", + "phf", "smallvec", ] @@ -406,7 +403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.111", ] [[package]] @@ -424,9 +421,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "data-url" @@ -437,11 +434,27 @@ dependencies = [ "matches", ] +[[package]] +name = "dhat" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cd11d84628e233de0ce467de10b8633f4ddaecafadefc86e13b84b8739b827" +dependencies = [ + "backtrace", + "lazy_static", + "mintex", + "parking_lot", + "rustc-hash 1.1.0", + "serde", + "serde_json", + "thousands", +] + [[package]] name = "dtoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" [[package]] name = "dtoa-short" @@ -454,21 +467,21 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -477,14 +490,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "funty" @@ -493,23 +512,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] -name = "fxhash" -version = "0.2.1" +name = "getrandom" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "byteorder", + "cfg-if", + "libc", + "wasi", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "wasi", + "r-efi", + "wasip2", ] [[package]] @@ -520,15 +542,16 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "grimoire_css" version = "1.6.0" dependencies = [ "console", + "dhat", "glob", "grimoire_css_color_toolkit", "indicatif", @@ -540,7 +563,7 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -567,6 +590,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "heck" version = "0.5.0" @@ -575,14 +604,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -598,35 +628,27 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.16.1", "serde", + "serde_core", ] [[package]] name = "indicatif" -version = "0.17.8" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" dependencies = [ "console", - "instant", - "number_prefix", "portable-atomic", - "unicode-width 0.1.14", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", + "unicode-width 0.2.2", + "unit-prefix", + "web-time", ] [[package]] @@ -661,16 +683,17 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -688,11 +711,11 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "lightningcss" -version = "1.0.0-alpha.59" +version = "1.0.0-alpha.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e225b3fa0a8bd5562c8833b1a32afa88761c4e661d3177b8cdc4e13cbf078e" +checksum = "b407ca668368d1d5a86cea58ac82d9f9f9ca4bac1e9dce6f16f875f0f081a911" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "bitflags", "browserslist-rs", "const-str", @@ -700,16 +723,18 @@ dependencies = [ "cssparser-color", "dashmap", "data-encoding", - "getrandom", + "getrandom 0.3.4", + "indexmap", "itertools 0.10.5", "lazy_static", "lightningcss-derive", "parcel_selectors", "parcel_sourcemap", - "paste", + "pastey", "pathdiff", "rayon", "serde", + "serde-content", "smallvec", ] @@ -725,12 +750,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -739,19 +758,18 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "matches" @@ -761,9 +779,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" -version = "2.6.2" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miette" @@ -792,7 +810,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.111", ] [[package]] @@ -810,6 +828,12 @@ dependencies = [ "adler2", ] +[[package]] +name = "mintex" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" + [[package]] name = "nom" version = "7.1.3" @@ -829,12 +853,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - [[package]] name = "object" version = "0.37.3" @@ -846,9 +864,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" @@ -870,17 +888,17 @@ checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" [[package]] name = "parcel_selectors" -version = "0.27.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4d26c18a8377a64728c04bf3b2e48ec43b0c77e687a18e03eb837d65e08a14" +checksum = "54fd03f1ad26cb6b3ec1b7414fa78a3bd639e7dbb421b1a60513c96ce886a196" dependencies = [ "bitflags", "cssparser", - "fxhash", "log", - "phf 0.10.1", + "phf", "phf_codegen", "precomputed-hash", + "rustc-hash 2.1.1", "smallvec", ] @@ -898,125 +916,98 @@ dependencies = [ "vlq", ] +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] -name = "paste" -version = "1.0.15" +name = "pastey" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "phf" -version = "0.10.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", -] - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared 0.11.2", + "phf_shared", ] [[package]] name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_shared 0.10.0", - "rand", + "phf_generator", + "phf_shared", ] [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared 0.11.2", + "phf_shared", "rand", ] [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", + "phf_generator", + "phf_shared", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.111", ] [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "precomputed-hash" @@ -1026,9 +1017,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1055,13 +1046,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -1074,18 +1071,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", "rand_core", ] @@ -1094,15 +1079,12 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -1110,9 +1092,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -1120,18 +1102,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.6" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.0" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1141,9 +1123,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1152,9 +1134,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rend" @@ -1201,36 +1183,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] -name = "rustix" -version = "0.38.38" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.11.0", - "windows-sys 0.59.0", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] -name = "ryu" -version = "1.0.15" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "scopeguard" @@ -1246,33 +1227,54 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-content" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3753ca04f350fa92d00b6146a3555e63c55388c9ef2e11e09bce2ff1c0b509c6" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.111", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] @@ -1298,15 +1300,15 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "strsim" @@ -1348,9 +1350,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -1365,15 +1367,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.4", "once_cell", - "rustix 0.38.38", - "windows-sys 0.59.0", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -1382,7 +1384,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.1.2", + "rustix", "windows-sys 0.60.2", ] @@ -1398,29 +1400,55 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.17", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.111", ] +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -1433,9 +1461,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-linebreak" @@ -1461,6 +1489,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + [[package]] name = "utf8parse" version = "0.2.2" @@ -1469,9 +1503,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "version_check" @@ -1487,41 +1525,51 @@ checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.111", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1529,30 +1577,69 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.111", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "windows-core" -version = "0.52.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-targets 0.52.6", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -1562,12 +1649,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-targets 0.52.6", + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", ] [[package]] @@ -1588,6 +1684,15 @@ dependencies = [ "windows-targets 0.53.5", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1717,6 +1822,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "wyz" version = "0.5.1" @@ -1728,21 +1839,26 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.111", ] + +[[package]] +name = "zmij" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0095ecd462946aa3927d9297b63ef82fb9a5316d7a37d134eeb36e58228615a" diff --git a/Cargo.toml b/Cargo.toml index 06a3045..c4c1fb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,19 +27,27 @@ crate-type = ["lib"] lto = true codegen-units = 1 +[profile.profiling] +inherits = "release" +debug = 2 + [dependencies] -console = "0.15.8" -glob = "0.3.1" +console = "0.16.2" +dhat = { version = "0.3.3", optional = true } +glob = "0.3.3" grimoire_css_color_toolkit = "1.0.0" -indicatif = "0.17.8" +indicatif = "0.18.3" lazy_static = "1.5.0" -lightningcss = { version = "1.0.0-alpha.59", features = ["browserslist"] } -miette = { version = "7.2.0", features = ["fancy"] } -once_cell = "1.20" -regex = "1.11.0" +lightningcss = { version = "1.0.0-alpha.68", features = ["browserslist"] } +miette = { version = "7.6.0", features = ["fancy"] } +once_cell = "1.21" +regex = "1.12.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -thiserror = "1.0.61" +thiserror = "2.0.17" + +[features] +heap-profile = ["dhat"] [dev-dependencies] -tempfile = "3.13.0" +tempfile = "3.24.0" diff --git a/README.md b/README.md index b554fc3..713d8af 100644 --- a/README.md +++ b/README.md @@ -1437,6 +1437,21 @@ There are only 3 commands you need to know: - **`build`**: Kicks off the build process, parsing all your input files and generating the compiled CSS. If you haven’t already run `init`, the `build` command will handle that for you automatically. - **`shorten`**: Automatically converts all full-length component names in your spells (as defined in your config) to their corresponding shorthand forms. This helps keep your code concise and consistent. Run this command to refactor your files, making your spell syntax as brief as possible without losing clarity or functionality. +**Optional parallel project builds** + +If your config defines multiple independent projects (multiple output files), Grimoire CSS can build them in parallel. + +- Enable by setting the `GRIMOIRE_CSS_JOBS` environment variable to a positive integer (e.g. `4`). +- Default is `1` (fully sequential; same behavior as before). +- Values are capped to the machine’s available parallelism. +- Higher values can reduce wall-clock build time, but may increase peak memory usage due to multiple optimizations running simultaneously. + +Example: + +```bash +GRIMOIRE_CSS_JOBS=4 grimoire_css build +``` + Grimoire CSS’s CLI is built for developers who want power without bloat. It’s direct, no-nonsense, and integrates smoothly into any project or bundler. Here’s a refined version of the remaining parts, keeping the technical depth and making them more engaging and polished: diff --git a/benchmark/.python-version b/benchmark/.python-version new file mode 100644 index 0000000..95ed564 --- /dev/null +++ b/benchmark/.python-version @@ -0,0 +1 @@ +3.14.2 diff --git a/benchmark/__pycache__/main.cpython-314.pyc b/benchmark/__pycache__/main.cpython-314.pyc new file mode 100644 index 0000000..047a204 Binary files /dev/null and b/benchmark/__pycache__/main.cpython-314.pyc differ diff --git a/benchmark/core/__pycache__/benchmark_formatter.cpython-314.pyc b/benchmark/core/__pycache__/benchmark_formatter.cpython-314.pyc new file mode 100644 index 0000000..cc50380 Binary files /dev/null and b/benchmark/core/__pycache__/benchmark_formatter.cpython-314.pyc differ diff --git a/benchmark/core/__pycache__/metrics_collector.cpython-314.pyc b/benchmark/core/__pycache__/metrics_collector.cpython-314.pyc new file mode 100644 index 0000000..923bc13 Binary files /dev/null and b/benchmark/core/__pycache__/metrics_collector.cpython-314.pyc differ diff --git a/benchmark/core/__pycache__/project_creator.cpython-314.pyc b/benchmark/core/__pycache__/project_creator.cpython-314.pyc new file mode 100644 index 0000000..0756bb8 Binary files /dev/null and b/benchmark/core/__pycache__/project_creator.cpython-314.pyc differ diff --git a/benchmark/core/__pycache__/report_generator.cpython-314.pyc b/benchmark/core/__pycache__/report_generator.cpython-314.pyc new file mode 100644 index 0000000..e277879 Binary files /dev/null and b/benchmark/core/__pycache__/report_generator.cpython-314.pyc differ diff --git a/benchmark/core/benchmark_formatter.py b/benchmark/core/benchmark_formatter.py index afa5b45..627432b 100644 --- a/benchmark/core/benchmark_formatter.py +++ b/benchmark/core/benchmark_formatter.py @@ -267,6 +267,10 @@ def generate_pretty_results(input_file): } } + jobs_value = raw_results.get("system_info", {}).get("benchmark", {}).get("grimoire_css_jobs", None) + if jobs_value is not None: + pretty_results["metadata"]["system"]["grimoire_css_jobs"] = jobs_value + return pretty_results diff --git a/benchmark/core/metrics_collector.py b/benchmark/core/metrics_collector.py index 1dfcf3e..3b34e34 100644 --- a/benchmark/core/metrics_collector.py +++ b/benchmark/core/metrics_collector.py @@ -19,6 +19,7 @@ from pathlib import Path import platform import traceback +import os class ProcessMonitor: @@ -28,12 +29,29 @@ def __init__(self): """Initialize the process monitor.""" self.is_windows = platform.system() == "Windows" self.is_macos = platform.system() == "Darwin" + # Backward-compatible primary memory series. self.memory_samples = [] self.peak_memory_bytes = 0 + # Additional memory series for better cross-run comparability. + self.memory_samples_rss = [] + self.peak_memory_bytes_rss = 0 + self.memory_samples_uss = [] + self.peak_memory_bytes_uss = 0 + # Partial USS series: sums USS only for processes where psutil reports it. + # This is useful on macOS where USS may be unavailable for some children. + self.memory_samples_uss_partial = [] + self.peak_memory_bytes_uss_partial = 0 + self.uss_coverage_samples = [] # fraction in [0..1] + self.process_count_samples = [] + self.uss_available_count_samples = [] self.cpu_user_time = 0 self.cpu_system_time = 0 self.io_read_bytes = 0 self.io_write_bytes = 0 + # Which memory measurement is used for the backward-compatible primary fields. + # - macOS/Linux: prefer 'uss' if available for the whole process tree, else 'rss' + # - Windows: prefer 'private' if available, else 'rss' + self.memory_measurement = "unknown" # Tracks all processes we're monitoring self.monitored_processes = set() # Maps PIDs to their last CPU times for delta calculations @@ -49,10 +67,20 @@ def start_monitoring(self, pid): # Reset metrics for new monitoring session self.memory_samples = [] self.peak_memory_bytes = 0 + self.memory_samples_rss = [] + self.peak_memory_bytes_rss = 0 + self.memory_samples_uss = [] + self.peak_memory_bytes_uss = 0 + self.memory_samples_uss_partial = [] + self.peak_memory_bytes_uss_partial = 0 + self.uss_coverage_samples = [] + self.process_count_samples = [] + self.uss_available_count_samples = [] self.cpu_user_time = 0 self.cpu_system_time = 0 self.io_read_bytes = 0 self.io_write_bytes = 0 + self.memory_measurement = "unknown" try: # Store initial process state @@ -107,7 +135,13 @@ def _monitor_process_tree(self, pid): self._update_process_list() # Reset per-iteration counters - current_total_memory = 0 + current_total_primary = 0 + current_total_rss = 0 + current_total_uss = 0 + uss_valid_for_all = True + current_total_uss_partial = 0 + uss_available_count = 0 + process_count = 0 # Check all processes in our monitoring list for proc in list(self.monitored_processes): @@ -117,9 +151,23 @@ def _monitor_process_tree(self, pid): self.monitored_processes.remove(proc) continue - # Measure memory using the appropriate platform-specific method - memory_used = self._get_process_memory(proc) - current_total_memory += memory_used + process_count += 1 + + # Measure memory + rss_bytes, uss_bytes, private_bytes = self._get_process_memory_components(proc) + + # RSS is always available. + current_total_rss += rss_bytes + + # USS is only valid if available for *all* monitored processes. + if uss_bytes is None: + uss_valid_for_all = False + else: + current_total_uss += uss_bytes + current_total_uss_partial += uss_bytes + uss_available_count += 1 + + # Primary metric is chosen after the loop based on platform and availability. # Measure CPU time delta self._update_cpu_times(proc) @@ -130,11 +178,61 @@ def _monitor_process_tree(self, pid): # Process no longer exists or can't be accessed self.monitored_processes.discard(proc) - # Update memory metrics only if we got a valid reading - if current_total_memory > 0: - self.memory_samples.append(current_total_memory) - self.peak_memory_bytes = max( - self.peak_memory_bytes, current_total_memory) + # Choose a stable primary memory metric per-run to avoid mixing RSS/USS + # across samples (which makes peak comparisons meaningless). + # + # - macOS/Linux: primary is RSS (always available) + # - Windows: primary is private working set if available, otherwise RSS + if self.is_windows: + # Prefer summing private memory if psutil provides it. + current_total_private = 0 + private_valid_for_all = True + for proc in list(self.monitored_processes): + try: + if proc.is_running(): + _, _, p = self._get_process_memory_components(proc) + if p is None: + private_valid_for_all = False + break + current_total_private += p + except (psutil.NoSuchProcess, psutil.AccessDenied): + private_valid_for_all = False + break + + if private_valid_for_all and current_total_private > 0: + self.memory_measurement = "private" + current_total_primary = current_total_private + else: + self.memory_measurement = "rss" + current_total_primary = current_total_rss + else: + self.memory_measurement = "rss" + current_total_primary = current_total_rss + + # Update memory metrics only if we got a valid reading. + if current_total_primary > 0: + self.memory_samples.append(current_total_primary) + self.peak_memory_bytes = max(self.peak_memory_bytes, current_total_primary) + + if current_total_rss > 0: + self.memory_samples_rss.append(current_total_rss) + self.peak_memory_bytes_rss = max(self.peak_memory_bytes_rss, current_total_rss) + + if uss_valid_for_all and current_total_uss > 0: + self.memory_samples_uss.append(current_total_uss) + self.peak_memory_bytes_uss = max(self.peak_memory_bytes_uss, current_total_uss) + + # Always record partial-USS (may undercount) and coverage. + if current_total_uss_partial > 0: + self.memory_samples_uss_partial.append(current_total_uss_partial) + self.peak_memory_bytes_uss_partial = max( + self.peak_memory_bytes_uss_partial, current_total_uss_partial + ) + + if process_count > 0: + self.uss_coverage_samples.append(uss_available_count / process_count) + self.process_count_samples.append(process_count) + self.uss_available_count_samples.append(uss_available_count) time.sleep(sampling_interval) @@ -146,6 +244,32 @@ def _monitor_process_tree(self, pid): print(f"Error in monitoring thread: {e}") traceback.print_exc() + def _get_process_memory_components(self, proc): + """Return (rss_bytes, uss_bytes_or_None, private_bytes_or_None).""" + try: + memory_info = proc.memory_info() + rss = getattr(memory_info, 'rss', 0) or 0 + + uss = None + private = None + + # Windows: private working set. + if self.is_windows: + private = getattr(memory_info, 'private', None) + return rss, uss, private + + # macOS/Linux: try USS if available. + try: + memory_full = proc.memory_full_info() + if hasattr(memory_full, 'uss'): + uss = getattr(memory_full, 'uss') + except Exception: + uss = None + + return rss, uss, private + except (psutil.NoSuchProcess, psutil.AccessDenied): + return 0, None, None + def _update_process_list(self): """Update the list of processes we're monitoring to include new children.""" processes_to_check = list(self.monitored_processes) @@ -183,6 +307,7 @@ def _get_process_memory(self, proc): try: if self.is_windows: # On Windows, use private working set for exclusive memory usage + self.memory_measurement = "private" return proc.memory_info().private elif self.is_macos: # On macOS, use rss - shared memory for better accuracy @@ -190,14 +315,22 @@ def _get_process_memory(self, proc): try: # Try to get more accurate measurement on macOS if available memory_full = proc.memory_full_info() - return getattr(memory_full, 'uss', memory_info.rss) + if hasattr(memory_full, 'uss'): + self.memory_measurement = "uss" + return getattr(memory_full, 'uss') + + self.memory_measurement = "rss" + return memory_info.rss except: + self.memory_measurement = "rss" return memory_info.rss else: # On Linux, USS (Unique Set Size) is most accurate try: + self.memory_measurement = "uss" return proc.memory_full_info().uss except: + self.memory_measurement = "rss" return proc.memory_info().rss except (psutil.NoSuchProcess, psutil.AccessDenied): return 0 @@ -263,7 +396,27 @@ def get_metrics(self): "peak_bytes": self.peak_memory_bytes, "peak_mb": self.peak_memory_bytes / (1024 * 1024), "avg_bytes": statistics.mean(self.memory_samples) if self.memory_samples else 0, - "avg_mb": statistics.mean(self.memory_samples) / (1024 * 1024) if self.memory_samples else 0 + "avg_mb": statistics.mean(self.memory_samples) / (1024 * 1024) if self.memory_samples else 0, + "measurement": self.memory_measurement, + # Additional series (may be empty if not measurable). + "rss_peak_bytes": self.peak_memory_bytes_rss, + "rss_peak_mb": self.peak_memory_bytes_rss / (1024 * 1024), + "rss_avg_bytes": statistics.mean(self.memory_samples_rss) if self.memory_samples_rss else 0, + "rss_avg_mb": statistics.mean(self.memory_samples_rss) / (1024 * 1024) if self.memory_samples_rss else 0, + "uss_peak_bytes": self.peak_memory_bytes_uss, + "uss_peak_mb": self.peak_memory_bytes_uss / (1024 * 1024), + "uss_avg_bytes": statistics.mean(self.memory_samples_uss) if self.memory_samples_uss else 0, + "uss_avg_mb": statistics.mean(self.memory_samples_uss) / (1024 * 1024) if self.memory_samples_uss else 0, + "uss_is_complete": bool(self.memory_samples_uss), + "uss_partial_peak_bytes": self.peak_memory_bytes_uss_partial, + "uss_partial_peak_mb": self.peak_memory_bytes_uss_partial / (1024 * 1024), + "uss_partial_avg_bytes": statistics.mean(self.memory_samples_uss_partial) if self.memory_samples_uss_partial else 0, + "uss_partial_avg_mb": statistics.mean(self.memory_samples_uss_partial) / (1024 * 1024) if self.memory_samples_uss_partial else 0, + "uss_coverage_avg": statistics.mean(self.uss_coverage_samples) if self.uss_coverage_samples else 0, + "uss_process_count_avg": statistics.mean(self.process_count_samples) if self.process_count_samples else 0, + "uss_process_count_max": max(self.process_count_samples) if self.process_count_samples else 0, + "uss_available_count_avg": statistics.mean(self.uss_available_count_samples) if self.uss_available_count_samples else 0, + "uss_available_count_max": max(self.uss_available_count_samples) if self.uss_available_count_samples else 0, }, "cpu": { "user_time": self.cpu_user_time, @@ -487,7 +640,23 @@ class GrimoireMetricsCollector(MetricsCollector): def __init__(self, output_dir="grimoire_css_output", executable="../target/release/grimoire_css"): """Initialize the Grimoire CSS metrics collector.""" super().__init__(output_dir) - self.executable = executable + # Allow overriding the binary path for profiling / custom builds. + # By default we run the release binary to keep benchmarks comparable. + # + # Priority order: + # 1) GRIMOIRE_CSS_EXECUTABLE: explicit path wins. + # 2) GRIMOIRE_CSS_USE_PROFILING=1: prefer ../target/profiling/grimoire_css if present. + # 3) Fallback to the default release binary. + overridden = os.environ.get("GRIMOIRE_CSS_EXECUTABLE") + use_profiling = os.environ.get("GRIMOIRE_CSS_USE_PROFILING") == "1" + + if overridden: + self.executable = overridden + elif use_profiling: + profiling_candidate = Path("../target/profiling/grimoire_css") + self.executable = str(profiling_candidate) if profiling_candidate.exists() else executable + else: + self.executable = executable def run_benchmark(self): """Run the Grimoire CSS benchmark and collect metrics.""" @@ -501,6 +670,11 @@ def run_benchmark(self): process, elapsed_time, process_metrics, stdout, stderr = self.run_process( cmd) + # dhat (heap profiling) drastically slows execution and changes allocation behavior. + # If it's enabled, the reported build time is not comparable to normal runs. + if stderr and "dhat:" in stderr: + print("Warning: dhat heap profiling detected in Grimoire process output. Build time is not comparable; disable heap profiling for performance benchmarks.") + # Step 3: Analyze output files output_metrics = self.output_analyzer.analyze() self.output_files_size = output_metrics["total_size_bytes"] @@ -514,6 +688,12 @@ def run_benchmark(self): process ) + # Add run metadata for reproducibility. + result["run"] = { + "executable": str(self.executable), + "argv": cmd, + } + return result except Exception as e: print(f"Error running Grimoire CSS benchmark: {e}") diff --git a/benchmark/core/report_generator.py b/benchmark/core/report_generator.py index 6cdfe6c..0135c1b 100644 --- a/benchmark/core/report_generator.py +++ b/benchmark/core/report_generator.py @@ -64,6 +64,10 @@ def generate_report(results): lines.append( f"Memory: {system_info.get('memory', {}).get('total_gb', 'Unknown')} GB") + jobs_value = system_info.get('benchmark', {}).get('grimoire_css_jobs', None) + if jobs_value is not None: + lines.append(f"GRIMOIRE_CSS_JOBS: {jobs_value}") + # Input summary lines.append("\nINPUT SUMMARY") lines.append("-" * 80) @@ -89,6 +93,35 @@ def generate_report(results): f"Peak Memory: {metrics['process']['memory']['peak_mb']:.2f} MB") lines.append( f"Average Memory: {metrics['process']['memory']['avg_mb']:.2f} MB") + + mem = metrics.get('process', {}).get('memory', {}) + # If available, show both RSS and USS for better comparability. + if 'rss_peak_mb' in mem: + lines.append(f"Peak RSS: {mem.get('rss_peak_mb', 0.0):.2f} MB") + lines.append(f"Average RSS: {mem.get('rss_avg_mb', 0.0):.2f} MB") + + # USS may be unavailable for some processes on macOS; if so, report a partial total + coverage. + if mem.get('uss_is_complete') and 'uss_peak_mb' in mem: + lines.append(f"Peak USS: {mem.get('uss_peak_mb', 0.0):.2f} MB") + lines.append(f"Average USS: {mem.get('uss_avg_mb', 0.0):.2f} MB") + elif 'uss_coverage_avg' in mem: + coverage_pct = float(mem.get('uss_coverage_avg', 0.0)) * 100.0 + proc_avg = float(mem.get('uss_process_count_avg', 0.0)) + proc_max = int(mem.get('uss_process_count_max', 0) or 0) + uss_avg = float(mem.get('uss_available_count_avg', 0.0)) + uss_max = int(mem.get('uss_available_count_max', 0) or 0) + + if mem.get('uss_partial_peak_bytes', 0) and 'uss_partial_peak_mb' in mem: + lines.append(f"Peak USS (partial): {mem.get('uss_partial_peak_mb', 0.0):.2f} MB") + lines.append(f"Average USS (partial): {mem.get('uss_partial_avg_mb', 0.0):.2f} MB") + else: + lines.append("USS: unavailable for monitored process tree") + + lines.append( + f"USS Coverage (avg): {coverage_pct:.1f}% (avg {uss_avg:.1f}/{proc_avg:.1f} procs, max {uss_max}/{proc_max})" + ) + if mem.get('measurement'): + lines.append(f"Primary Memory Metric: {mem.get('measurement')}") if "memory_efficiency" in metrics["throughput"]: lines.append( f"Memory Efficiency: {metrics['throughput']['memory_efficiency']:.2f} classes/MB") @@ -162,6 +195,10 @@ def generate_comparison_report(results): lines.append( f"Memory: {system_info.get('memory', {}).get('total_gb', 'Unknown')} GB") + jobs_value = system_info.get('benchmark', {}).get('grimoire_css_jobs', None) + if jobs_value is not None: + lines.append(f"GRIMOIRE_CSS_JOBS: {jobs_value}") + # Performance comparison lines.append("\nPERFORMANCE COMPARISON") lines.append("-" * 80) diff --git a/benchmark/main.py b/benchmark/main.py index 2335ec0..608ed43 100644 --- a/benchmark/main.py +++ b/benchmark/main.py @@ -28,6 +28,8 @@ import psutil import time import datetime +import subprocess +import os from pathlib import Path from core.project_creator import create_benchmark_projects @@ -66,12 +68,46 @@ def parse_args(): def collect_system_info(): """Collect information about the system for benchmark context.""" + # Benchmark configuration (captured as part of system info so it is persisted + # into all output formats). + jobs_raw = os.environ.get("GRIMOIRE_CSS_JOBS") + jobs_value = None + if jobs_raw is not None: + jobs_raw = jobs_raw.strip() + if jobs_raw == "": + jobs_value = None + else: + try: + jobs_value = int(jobs_raw) + except ValueError: + jobs_value = jobs_raw + + # Best-effort git metadata to make benchmark results reproducible. + git_sha = None + git_dirty = None + try: + git_sha = subprocess.check_output( + ["git", "rev-parse", "HEAD"], + stderr=subprocess.DEVNULL, + text=True, + ).strip() + git_dirty = subprocess.call( + ["git", "diff", "--quiet"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) != 0 + except Exception: + pass + return { "os": { "name": platform.system(), "version": platform.version(), "release": platform.release() }, + "benchmark": { + "grimoire_css_jobs": jobs_value, + }, "cpu": { "name": platform.processor() or platform.machine(), "cores_logical": psutil.cpu_count(logical=True), @@ -80,6 +116,11 @@ def collect_system_info(): "memory": { "total_gb": round(psutil.virtual_memory().total / (1024**3), 1) }, + "psutil_version": getattr(psutil, "__version__", "unknown"), + "git": { + "sha": git_sha, + "dirty": git_dirty, + }, "python_version": platform.python_version(), "timestamp": time.time(), "timestamp_human": time.strftime("%Y-%m-%d %H:%M:%S") @@ -232,6 +273,8 @@ def main(): print(f"OS: {system_info['os']['name']} {system_info['os']['release']}") print(f"CPU: {system_info['cpu']['name']}") print(f"Memory: {system_info['memory']['total_gb']} GB") + jobs_value = system_info.get('benchmark', {}).get('grimoire_css_jobs', None) + print(f"GRIMOIRE_CSS_JOBS: {jobs_value if jobs_value is not None else '(unset)'}") # Run benchmarks framework_results = run_framework_benchmark(args.framework, system_info) diff --git a/benchmark/results/result_20250323_111448.json b/benchmark/results/result_20250323_111448.json deleted file mode 100644 index 2adf288..0000000 --- a/benchmark/results/result_20250323_111448.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "system_info": { - "os": { - "name": "Darwin", - "version": "Darwin Kernel Version 24.3.0: Thu Jan 2 20:24:23 PST 2025; root:xnu-11215.81.4~3/RELEASE_ARM64_T6020", - "release": "24.3.0" - }, - "cpu": { - "name": "arm", - "cores_logical": 12, - "cores_physical": 12 - }, - "memory": { - "total_gb": 32.0 - }, - "python_version": "3.13.2", - "timestamp": 1742724888.643092, - "timestamp_human": "2025-03-23 11:14:48" - }, - "grimoire": { - "input": { - "unique_class_count": 400006, - "total_input_size_bytes": 49187280, - "file_count": 100000 - }, - "output": { - "file_count": 10, - "total_size_bytes": 5296770, - "total_size_kb": 5172.626953125, - "avg_size_bytes": 529677.0, - "avg_size_kb": 517.2626953125 - }, - "process": { - "memory": { - "peak_bytes": 116604928, - "peak_mb": 111.203125, - "avg_bytes": 47982004.589147285, - "avg_mb": 45.75920542635659, - "std_dev_mb": 25.961544766980627 - }, - "cpu": { - "user_time": 0.755105886, - "system_time": 1.333121863, - "total_time": 2.0882277489999996 - }, - "io": { - "read_bytes": 49187280, - "read_mb": 46.90864562988281, - "write_bytes": 5296770, - "write_mb": 5.051393508911133 - } - }, - "throughput": { - "build_time_seconds": 2.09773588180542, - "classes_per_second": 190684.6345478603, - "memory_efficiency": 3597.0751721230854, - "bytes_processed_per_second": 23447794.56108978, - "bytes_generated_per_second": 2524993.754428859, - "cpu_utilization": 0.9954674309154511 - }, - "exit_code": 0, - "success": true - }, - "tailwind": { - "input": { - "unique_class_count": 400006, - "total_input_size_bytes": 49187280, - "file_count": 100000 - }, - "output": { - "file_count": 10, - "total_size_bytes": 5937710, - "total_size_kb": 5798.544921875, - "avg_size_bytes": 593771.0, - "avg_size_kb": 579.8544921875 - }, - "process": { - "memory": { - "peak_bytes": 361725952, - "peak_mb": 344.96875, - "avg_bytes": 191161685.33333334, - "avg_mb": 182.30598958333334, - "std_dev_mb": 74.08982503042134 - }, - "cpu": { - "user_time": 7.772634282, - "system_time": 60.89118616200001, - "total_time": 68.663820444 - }, - "io": { - "read_bytes": 49187280, - "read_mb": 46.90864562988281, - "write_bytes": 5937710, - "write_mb": 5.662641525268555 - } - }, - "throughput": { - "build_time_seconds": 10.575389862060547, - "classes_per_second": 37824.23203470074, - "memory_efficiency": 1159.5427122021922, - "bytes_processed_per_second": 4651107.9630700415, - "bytes_generated_per_second": 561464.8800137072, - "cpu_utilization": 6.492793300257707 - }, - "exit_code": 0, - "success": true - } -} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012340.json b/benchmark/results/result_20251227_012340.json new file mode 100644 index 0000000..36e19af --- /dev/null +++ b/benchmark/results/result_20251227_012340.json @@ -0,0 +1,140 @@ +{ + "system_info": { + "os": { + "name": "Darwin", + "version": "Darwin Kernel Version 25.2.0: Tue Nov 18 21:07:05 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6020", + "release": "25.2.0" + }, + "benchmark": { + "grimoire_css_jobs": 1 + }, + "cpu": { + "name": "arm", + "cores_logical": 12, + "cores_physical": 12 + }, + "memory": { + "total_gb": 32.0 + }, + "psutil_version": "7.0.0", + "git": { + "sha": "8f60e2643076e9065a21408a98d0da64b5aea71b", + "dirty": true + }, + "python_version": "3.14.2", + "timestamp": 1766795020.693991, + "timestamp_human": "2025-12-27 01:23:40" + }, + "grimoire": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5296800, + "total_size_kb": 5172.65625, + "avg_size_bytes": 529680.0, + "avg_size_kb": 517.265625 + }, + "process": { + "memory": { + "peak_bytes": 164986880, + "peak_mb": 157.34375, + "avg_bytes": 86189106.9683258, + "avg_mb": 82.19633766968326, + "measurement": "rss", + "rss_peak_bytes": 164986880, + "rss_peak_mb": 157.34375, + "rss_avg_bytes": 86189106.9683258, + "rss_avg_mb": 82.19633766968326, + "uss_peak_bytes": 138870784, + "uss_peak_mb": 132.4375, + "uss_avg_bytes": 66468405.28506787, + "uss_avg_mb": 63.38921097285068, + "uss_is_complete": true, + "uss_partial_peak_bytes": 138870784, + "uss_partial_peak_mb": 132.4375, + "uss_partial_avg_bytes": 66468405.28506787, + "uss_partial_avg_mb": 63.38921097285068, + "uss_coverage_avg": 1.0, + "uss_process_count_avg": 1, + "uss_process_count_max": 1, + "uss_available_count_avg": 1, + "uss_available_count_max": 1, + "std_dev_mb": 51.10911577288717 + }, + "cpu": { + "user_time": 0.807057117, + "system_time": 2.02749662, + "total_time": 2.8345537370000002 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5296800, + "write_mb": 5.051422119140625 + } + }, + "throughput": { + "build_time_seconds": 3.7795708179473877, + "classes_per_second": 105833.70950494203, + "memory_efficiency": 2542.2427010923534, + "bytes_processed_per_second": 13093359.638879737, + "bytes_generated_per_second": 1401428.959830045 + }, + "exit_code": 0, + "success": true, + "run": { + "executable": "../target/release/grimoire_css", + "argv": [ + "../target/release/grimoire_css", + "build" + ] + } + }, + "tailwind": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5937710, + "total_size_kb": 5798.544921875, + "avg_size_bytes": 593771.0, + "avg_size_kb": 579.8544921875 + }, + "process": { + "memory": { + "peak_bytes": 321273856, + "peak_mb": 306.390625, + "avg_bytes": 172912465.2890995, + "avg_mb": 164.9021771327014, + "std_dev_mb": 71.15805121031616 + }, + "cpu": { + "user_time": 8.919657383, + "system_time": 42.144116483, + "total_time": 51.06377386599999 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5937710, + "write_mb": 5.662641525268555 + } + }, + "throughput": { + "build_time_seconds": 9.40093445777893, + "classes_per_second": 42549.59991440102, + "memory_efficiency": 1305.5425569891377, + "bytes_processed_per_second": 5264080.950915584, + "bytes_generated_per_second": 631608.4881420231 + }, + "exit_code": 0, + "success": true + } +} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012340.txt b/benchmark/results/result_20251227_012340.txt new file mode 100644 index 0000000..6478fa0 --- /dev/null +++ b/benchmark/results/result_20251227_012340.txt @@ -0,0 +1,126 @@ + + +================================================================================================================================================================ +GRIMOIRE PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:23:40 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 1 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 3.78 s +Processing Speed: 105833.71 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 157.34 MB +Average Memory: 82.20 MB +Peak RSS: 157.34 MB +Average RSS: 82.20 MB +Peak USS: 132.44 MB +Average USS: 63.39 MB +Primary Memory Metric: rss +Memory Efficiency: 2542.24 classes/MB +Memory Stability (Std Dev): 51.11 MB +Memory per Class: 412.46 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 807.06 ms +System CPU Time: 2.03 s +Total CPU Time: 2.83 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.05 MB +Output File Count: 10 +Output Size: 5.05 MB + +================================================================================ +TAILWIND PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:23:40 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 1 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 9.40 s +Processing Speed: 42549.60 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 306.39 MB +Average Memory: 164.90 MB +Memory Efficiency: 1305.54 classes/MB +Memory Stability (Std Dev): 71.16 MB +Memory per Class: 803.17 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 8.92 s +System CPU Time: 42.14 s +Total CPU Time: 51.06 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.66 MB +Output File Count: 10 +Output Size: 5.66 MB + +================================================================================ +CSS FRAMEWORKS PERFORMANCE COMPARISON +================================================================================ +Generated: 2025-12-27 01:23:40 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 1 + +PERFORMANCE COMPARISON +-------------------------------------------------------------------------------- +Metric | Grimoire CSS | Tailwind CSS | Difference | Ratio (G/T) +------------------------------------------------------------------------------------------------- +Build Time | 3.78 s | 9.40 s | 5.62 s | 2.49x +Classes/sec | 105833.71 | 42549.60 | 63284.11 | 2.49x +Peak Memory | 157.34 MB | 306.39 MB | 149.05 MB | 1.95x +Memory Efficiency | 2542.24 classes/MB | 1305.54 classes/MB | 1236.70 classes/MB | 1.95x +Output Size | 5.05 MB | 5.66 MB | 625.89 KB | 1.12x + +Notes: +- Build Time: lower is better +- Classes/sec: higher is better +- Peak Memory: lower is better +- Memory Efficiency: higher is better +- Output Size: lower is better \ No newline at end of file diff --git a/benchmark/results/result_20251227_012340_pretty.json b/benchmark/results/result_20251227_012340_pretty.json new file mode 100644 index 0000000..b69ac99 --- /dev/null +++ b/benchmark/results/result_20251227_012340_pretty.json @@ -0,0 +1,119 @@ +{ + "charts": [ + { + "title": "Grimoire CSS vs Tailwind CSS - Build Time", + "chartTitle": "Build Time", + "chartSubtitle": "Total time taken to compile CSS (lower is better)", + "chartId": "chart_time", + "highlightText": "2.5x faster", + "grimoireHeight": 34.17357295366463, + "tailwindHeight": 85.0, + "grimoireValue": "3.78s", + "tailwindValue": "9.40s", + "grimoireRawValue": 3.78, + "tailwindRawValue": 9.4 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Peak Memory Usage", + "chartTitle": "Peak Memory Usage", + "chartSubtitle": "Maximum memory consumed during compilation (lower is better)", + "chartId": "chart_peak_memory", + "highlightText": "1.9x less", + "grimoireHeight": 43.650874598398694, + "tailwindHeight": 85.0, + "grimoireValue": "157.34 MB", + "tailwindValue": "306.39 MB", + "grimoireRawValue": 157.34, + "tailwindRawValue": 306.39 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Average Memory Usage", + "chartTitle": "Average Memory Usage", + "chartSubtitle": "Average memory consumed during compilation (lower is better)", + "chartId": "chart_avg_memory", + "highlightText": "2.0x less", + "grimoireHeight": 42.36868683850482, + "tailwindHeight": 85.0, + "grimoireValue": "82.2 MB", + "tailwindValue": "164.9 MB", + "grimoireRawValue": 82.2, + "tailwindRawValue": 164.9 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (User Time)", + "chartTitle": "CPU Usage (User Time)", + "chartSubtitle": "CPU time spent in user mode during compilation (lower is better)", + "chartId": "chart_cpu_user", + "highlightText": "11.1x less", + "grimoireHeight": 7.690862103710917, + "tailwindHeight": 85.0, + "grimoireValue": "807.06ms", + "tailwindValue": "8.92s", + "grimoireRawValue": 0.81, + "tailwindRawValue": 8.92 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (System Time)", + "chartTitle": "CPU Usage (System Time)", + "chartSubtitle": "CPU time spent in system mode during compilation (lower is better)", + "chartId": "chart_cpu_system", + "highlightText": "20.8x less", + "grimoireHeight": 4.089235392311926, + "tailwindHeight": 85.0, + "grimoireValue": "2.03s", + "tailwindValue": "42.14s", + "grimoireRawValue": 2.03, + "tailwindRawValue": 42.14 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Output Size", + "chartTitle": "Output Size", + "chartSubtitle": "Size of the generated CSS file (lower is better)", + "chartId": "chart_output", + "highlightText": "1.1x less", + "grimoireHeight": 75.82519186689818, + "tailwindHeight": 85.0, + "grimoireValue": "5.05 MB", + "tailwindValue": "5.66 MB", + "grimoireRawValue": 5172.66, + "tailwindRawValue": 5798.54 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Processing Speed", + "chartTitle": "Processing Speed", + "chartSubtitle": "Number of utility classes processed per second (higher is better)", + "chartId": "chart_classes_per_second", + "highlightText": "2.5x faster", + "grimoireHeight": 85.0, + "tailwindHeight": 34.17357295366463, + "grimoireValue": "105833.71 classes/s", + "tailwindValue": "42549.60 classes/s", + "grimoireRawValue": 105833.71, + "tailwindRawValue": 42549.6 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Memory Efficiency", + "chartTitle": "Memory Efficiency", + "chartSubtitle": "Number of utility classes processed per MB of memory (higher is better)", + "chartId": "chart_memory_efficiency", + "highlightText": "1.9x more efficient", + "grimoireHeight": 85.0, + "tailwindHeight": 43.650874598398694, + "grimoireValue": "2542.24 classes/MB", + "tailwindValue": "1305.54 classes/MB", + "grimoireRawValue": 2542.24, + "tailwindRawValue": 1305.54 + } + ], + "metadata": { + "timestamp": 1766795020.693991, + "timestamp_human": "2025-12-27 01:23:40", + "system": { + "os": "Darwin 25.2.0", + "cpu": "arm", + "cores": "12 physical, 12 logical", + "memory": "32.0 GB", + "grimoire_css_jobs": 1 + } + } +} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012409.json b/benchmark/results/result_20251227_012409.json new file mode 100644 index 0000000..a05142f --- /dev/null +++ b/benchmark/results/result_20251227_012409.json @@ -0,0 +1,140 @@ +{ + "system_info": { + "os": { + "name": "Darwin", + "version": "Darwin Kernel Version 25.2.0: Tue Nov 18 21:07:05 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6020", + "release": "25.2.0" + }, + "benchmark": { + "grimoire_css_jobs": 2 + }, + "cpu": { + "name": "arm", + "cores_logical": 12, + "cores_physical": 12 + }, + "memory": { + "total_gb": 32.0 + }, + "psutil_version": "7.0.0", + "git": { + "sha": "8f60e2643076e9065a21408a98d0da64b5aea71b", + "dirty": true + }, + "python_version": "3.14.2", + "timestamp": 1766795049.0435998, + "timestamp_human": "2025-12-27 01:24:09" + }, + "grimoire": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5296800, + "total_size_kb": 5172.65625, + "avg_size_bytes": 529680.0, + "avg_size_kb": 517.265625 + }, + "process": { + "memory": { + "peak_bytes": 156024832, + "peak_mb": 148.796875, + "avg_bytes": 96777847.82978724, + "avg_mb": 92.29454787234043, + "measurement": "rss", + "rss_peak_bytes": 156024832, + "rss_peak_mb": 148.796875, + "rss_avg_bytes": 96777847.82978724, + "rss_avg_mb": 92.29454787234043, + "uss_peak_bytes": 91914240, + "uss_peak_mb": 87.65625, + "uss_avg_bytes": 44642042.55319149, + "uss_avg_mb": 42.57396941489362, + "uss_is_complete": true, + "uss_partial_peak_bytes": 91914240, + "uss_partial_peak_mb": 87.65625, + "uss_partial_avg_bytes": 44642042.55319149, + "uss_partial_avg_mb": 42.57396941489362, + "uss_coverage_avg": 1.0, + "uss_process_count_avg": 1, + "uss_process_count_max": 1, + "uss_available_count_avg": 1, + "uss_available_count_max": 1, + "std_dev_mb": 42.757697156760315 + }, + "cpu": { + "user_time": 0.8715153340000001, + "system_time": 2.1863928049999997, + "total_time": 3.057908139 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5296800, + "write_mb": 5.051422119140625 + } + }, + "throughput": { + "build_time_seconds": 1.5986220836639404, + "classes_per_second": 250219.23823497523, + "memory_efficiency": 2688.2688228499424, + "bytes_processed_per_second": 30956209.41666106, + "bytes_generated_per_second": 3313353.452405756 + }, + "exit_code": 0, + "success": true, + "run": { + "executable": "../target/release/grimoire_css", + "argv": [ + "../target/release/grimoire_css", + "build" + ] + } + }, + "tailwind": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5937710, + "total_size_kb": 5798.544921875, + "avg_size_bytes": 593771.0, + "avg_size_kb": 579.8544921875 + }, + "process": { + "memory": { + "peak_bytes": 325632000, + "peak_mb": 310.546875, + "avg_bytes": 172060098.1642512, + "avg_mb": 164.08929649758454, + "std_dev_mb": 70.69412801340499 + }, + "cpu": { + "user_time": 8.739942736000001, + "system_time": 42.878009256999995, + "total_time": 51.617951993 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5937710, + "write_mb": 5.662641525268555 + } + }, + "throughput": { + "build_time_seconds": 9.343071699142456, + "classes_per_second": 42813.114667279515, + "memory_efficiency": 1288.0696352201257, + "bytes_processed_per_second": 5296682.032798928, + "bytes_generated_per_second": 635520.1149259066 + }, + "exit_code": 0, + "success": true + } +} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012409.txt b/benchmark/results/result_20251227_012409.txt new file mode 100644 index 0000000..b70c755 --- /dev/null +++ b/benchmark/results/result_20251227_012409.txt @@ -0,0 +1,126 @@ + + +================================================================================================================================================================ +GRIMOIRE PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:24:09 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 2 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 1.60 s +Processing Speed: 250219.24 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 148.80 MB +Average Memory: 92.29 MB +Peak RSS: 148.80 MB +Average RSS: 92.29 MB +Peak USS: 87.66 MB +Average USS: 42.57 MB +Primary Memory Metric: rss +Memory Efficiency: 2688.27 classes/MB +Memory Stability (Std Dev): 42.76 MB +Memory per Class: 390.06 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 871.52 ms +System CPU Time: 2.19 s +Total CPU Time: 3.06 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.05 MB +Output File Count: 10 +Output Size: 5.05 MB + +================================================================================ +TAILWIND PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:24:09 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 2 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 9.34 s +Processing Speed: 42813.11 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 310.55 MB +Average Memory: 164.09 MB +Memory Efficiency: 1288.07 classes/MB +Memory Stability (Std Dev): 70.69 MB +Memory per Class: 814.07 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 8.74 s +System CPU Time: 42.88 s +Total CPU Time: 51.62 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.66 MB +Output File Count: 10 +Output Size: 5.66 MB + +================================================================================ +CSS FRAMEWORKS PERFORMANCE COMPARISON +================================================================================ +Generated: 2025-12-27 01:24:09 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 2 + +PERFORMANCE COMPARISON +-------------------------------------------------------------------------------- +Metric | Grimoire CSS | Tailwind CSS | Difference | Ratio (G/T) +------------------------------------------------------------------------------------------------- +Build Time | 1.60 s | 9.34 s | 7.74 s | 5.84x +Classes/sec | 250219.24 | 42813.11 | 207406.12 | 5.84x +Peak Memory | 148.80 MB | 310.55 MB | 161.75 MB | 2.09x +Memory Efficiency | 2688.27 classes/MB | 1288.07 classes/MB | 1400.20 classes/MB | 2.09x +Output Size | 5.05 MB | 5.66 MB | 625.89 KB | 1.12x + +Notes: +- Build Time: lower is better +- Classes/sec: higher is better +- Peak Memory: lower is better +- Memory Efficiency: higher is better +- Output Size: lower is better \ No newline at end of file diff --git a/benchmark/results/result_20251227_012409_pretty.json b/benchmark/results/result_20251227_012409_pretty.json new file mode 100644 index 0000000..7ab91a7 --- /dev/null +++ b/benchmark/results/result_20251227_012409_pretty.json @@ -0,0 +1,119 @@ +{ + "charts": [ + { + "title": "Grimoire CSS vs Tailwind CSS - Build Time", + "chartTitle": "Build Time", + "chartSubtitle": "Total time taken to compile CSS (lower is better)", + "chartId": "chart_time", + "highlightText": "5.8x faster", + "grimoireHeight": 14.543704842156655, + "tailwindHeight": 85.0, + "grimoireValue": "1.60s", + "tailwindValue": "9.34s", + "grimoireRawValue": 1.6, + "tailwindRawValue": 9.34 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Peak Memory Usage", + "chartTitle": "Peak Memory Usage", + "chartSubtitle": "Maximum memory consumed during compilation (lower is better)", + "chartId": "chart_peak_memory", + "highlightText": "2.1x less", + "grimoireHeight": 40.72729559748427, + "tailwindHeight": 85.0, + "grimoireValue": "148.8 MB", + "tailwindValue": "310.55 MB", + "grimoireRawValue": 148.8, + "tailwindRawValue": 310.55 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Average Memory Usage", + "chartTitle": "Average Memory Usage", + "chartSubtitle": "Average memory consumed during compilation (lower is better)", + "chartId": "chart_avg_memory", + "highlightText": "1.8x less", + "grimoireHeight": 47.80955697049026, + "tailwindHeight": 85.0, + "grimoireValue": "92.29 MB", + "tailwindValue": "164.09 MB", + "grimoireRawValue": 92.29, + "tailwindRawValue": 164.09 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (User Time)", + "chartTitle": "CPU Usage (User Time)", + "chartSubtitle": "CPU time spent in user mode during compilation (lower is better)", + "chartId": "chart_cpu_user", + "highlightText": "10.0x less", + "grimoireHeight": 8.475891161719849, + "tailwindHeight": 85.0, + "grimoireValue": "871.52ms", + "tailwindValue": "8.74s", + "grimoireRawValue": 0.87, + "tailwindRawValue": 8.74 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (System Time)", + "chartTitle": "CPU Usage (System Time)", + "chartSubtitle": "CPU time spent in system mode during compilation (lower is better)", + "chartId": "chart_cpu_system", + "highlightText": "19.6x less", + "grimoireHeight": 4.334235465809559, + "tailwindHeight": 85.0, + "grimoireValue": "2.19s", + "tailwindValue": "42.88s", + "grimoireRawValue": 2.19, + "tailwindRawValue": 42.88 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Output Size", + "chartTitle": "Output Size", + "chartSubtitle": "Size of the generated CSS file (lower is better)", + "chartId": "chart_output", + "highlightText": "1.1x less", + "grimoireHeight": 75.82519186689818, + "tailwindHeight": 85.0, + "grimoireValue": "5.05 MB", + "tailwindValue": "5.66 MB", + "grimoireRawValue": 5172.66, + "tailwindRawValue": 5798.54 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Processing Speed", + "chartTitle": "Processing Speed", + "chartSubtitle": "Number of utility classes processed per second (higher is better)", + "chartId": "chart_classes_per_second", + "highlightText": "5.8x faster", + "grimoireHeight": 85.0, + "tailwindHeight": 14.543704842156655, + "grimoireValue": "250219.24 classes/s", + "tailwindValue": "42813.11 classes/s", + "grimoireRawValue": 250219.24, + "tailwindRawValue": 42813.11 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Memory Efficiency", + "chartTitle": "Memory Efficiency", + "chartSubtitle": "Number of utility classes processed per MB of memory (higher is better)", + "chartId": "chart_memory_efficiency", + "highlightText": "2.1x more efficient", + "grimoireHeight": 85.0, + "tailwindHeight": 40.72729559748427, + "grimoireValue": "2688.27 classes/MB", + "tailwindValue": "1288.07 classes/MB", + "grimoireRawValue": 2688.27, + "tailwindRawValue": 1288.07 + } + ], + "metadata": { + "timestamp": 1766795049.0435998, + "timestamp_human": "2025-12-27 01:24:09", + "system": { + "os": "Darwin 25.2.0", + "cpu": "arm", + "cores": "12 physical, 12 logical", + "memory": "32.0 GB", + "grimoire_css_jobs": 2 + } + } +} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012425.json b/benchmark/results/result_20251227_012425.json new file mode 100644 index 0000000..6e7736b --- /dev/null +++ b/benchmark/results/result_20251227_012425.json @@ -0,0 +1,140 @@ +{ + "system_info": { + "os": { + "name": "Darwin", + "version": "Darwin Kernel Version 25.2.0: Tue Nov 18 21:07:05 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6020", + "release": "25.2.0" + }, + "benchmark": { + "grimoire_css_jobs": 4 + }, + "cpu": { + "name": "arm", + "cores_logical": 12, + "cores_physical": 12 + }, + "memory": { + "total_gb": 32.0 + }, + "psutil_version": "7.0.0", + "git": { + "sha": "8f60e2643076e9065a21408a98d0da64b5aea71b", + "dirty": true + }, + "python_version": "3.14.2", + "timestamp": 1766795065.6670752, + "timestamp_human": "2025-12-27 01:24:25" + }, + "grimoire": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5296800, + "total_size_kb": 5172.65625, + "avg_size_bytes": 529680.0, + "avg_size_kb": 517.265625 + }, + "process": { + "memory": { + "peak_bytes": 267829248, + "peak_mb": 255.421875, + "avg_bytes": 127034514.28571428, + "avg_mb": 121.14955357142857, + "measurement": "rss", + "rss_peak_bytes": 267829248, + "rss_peak_mb": 255.421875, + "rss_avg_bytes": 127034514.28571428, + "rss_avg_mb": 121.14955357142857, + "uss_peak_bytes": 149585920, + "uss_peak_mb": 142.65625, + "uss_avg_bytes": 67461822.17142858, + "uss_avg_mb": 64.33660714285715, + "uss_is_complete": true, + "uss_partial_peak_bytes": 149585920, + "uss_partial_peak_mb": 142.65625, + "uss_partial_avg_bytes": 67461822.17142858, + "uss_partial_avg_mb": 64.33660714285715, + "uss_coverage_avg": 0.9859154929577465, + "uss_process_count_avg": 1, + "uss_process_count_max": 1, + "uss_available_count_avg": 0.9859154929577465, + "uss_available_count_max": 1, + "std_dev_mb": 85.23104615436908 + }, + "cpu": { + "user_time": 0.922854154, + "system_time": 2.790858709, + "total_time": 3.713712863 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5296800, + "write_mb": 5.051422119140625 + } + }, + "throughput": { + "build_time_seconds": 1.2039422988891602, + "classes_per_second": 332246.8197762243, + "memory_efficiency": 1566.0600721844987, + "bytes_processed_per_second": 41104361.933009885, + "bytes_generated_per_second": 4399546.394280848 + }, + "exit_code": 0, + "success": true, + "run": { + "executable": "../target/release/grimoire_css", + "argv": [ + "../target/release/grimoire_css", + "build" + ] + } + }, + "tailwind": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5937710, + "total_size_kb": 5798.544921875, + "avg_size_bytes": 593771.0, + "avg_size_kb": 579.8544921875 + }, + "process": { + "memory": { + "peak_bytes": 317636608, + "peak_mb": 302.921875, + "avg_bytes": 173636806.90647483, + "avg_mb": 165.5929631294964, + "std_dev_mb": 70.57462560423957 + }, + "cpu": { + "user_time": 8.718745665, + "system_time": 43.216031122, + "total_time": 51.934776787 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5937710, + "write_mb": 5.662641525268555 + } + }, + "throughput": { + "build_time_seconds": 9.371271133422852, + "classes_per_second": 42684.28416006122, + "memory_efficiency": 1320.4922886470315, + "bytes_processed_per_second": 5280743.593417385, + "bytes_generated_per_second": 633607.7481338708 + }, + "exit_code": 0, + "success": true + } +} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012425.txt b/benchmark/results/result_20251227_012425.txt new file mode 100644 index 0000000..f9a9dc9 --- /dev/null +++ b/benchmark/results/result_20251227_012425.txt @@ -0,0 +1,126 @@ + + +================================================================================================================================================================ +GRIMOIRE PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:24:25 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 4 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 1.20 s +Processing Speed: 332246.82 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 255.42 MB +Average Memory: 121.15 MB +Peak RSS: 255.42 MB +Average RSS: 121.15 MB +Peak USS: 142.66 MB +Average USS: 64.34 MB +Primary Memory Metric: rss +Memory Efficiency: 1566.06 classes/MB +Memory Stability (Std Dev): 85.23 MB +Memory per Class: 669.56 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 922.85 ms +System CPU Time: 2.79 s +Total CPU Time: 3.71 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.05 MB +Output File Count: 10 +Output Size: 5.05 MB + +================================================================================ +TAILWIND PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:24:25 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 4 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 9.37 s +Processing Speed: 42684.28 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 302.92 MB +Average Memory: 165.59 MB +Memory Efficiency: 1320.49 classes/MB +Memory Stability (Std Dev): 70.57 MB +Memory per Class: 794.08 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 8.72 s +System CPU Time: 43.22 s +Total CPU Time: 51.93 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.66 MB +Output File Count: 10 +Output Size: 5.66 MB + +================================================================================ +CSS FRAMEWORKS PERFORMANCE COMPARISON +================================================================================ +Generated: 2025-12-27 01:24:25 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 4 + +PERFORMANCE COMPARISON +-------------------------------------------------------------------------------- +Metric | Grimoire CSS | Tailwind CSS | Difference | Ratio (G/T) +------------------------------------------------------------------------------------------------ +Build Time | 1.20 s | 9.37 s | 8.17 s | 7.78x +Classes/sec | 332246.82 | 42684.28 | 289562.54 | 7.78x +Peak Memory | 255.42 MB | 302.92 MB | 47.50 MB | 1.19x +Memory Efficiency | 1566.06 classes/MB | 1320.49 classes/MB | 245.57 classes/MB | 1.19x +Output Size | 5.05 MB | 5.66 MB | 625.89 KB | 1.12x + +Notes: +- Build Time: lower is better +- Classes/sec: higher is better +- Peak Memory: lower is better +- Memory Efficiency: higher is better +- Output Size: lower is better \ No newline at end of file diff --git a/benchmark/results/result_20250323_111448_pretty.json b/benchmark/results/result_20251227_012425_pretty.json similarity index 58% rename from benchmark/results/result_20250323_111448_pretty.json rename to benchmark/results/result_20251227_012425_pretty.json index b67bf1a..e52b960 100644 --- a/benchmark/results/result_20250323_111448_pretty.json +++ b/benchmark/results/result_20251227_012425_pretty.json @@ -5,65 +5,65 @@ "chartTitle": "Build Time", "chartSubtitle": "Total time taken to compile CSS (lower is better)", "chartId": "chart_time", - "highlightText": "5.0x faster", - "grimoireHeight": 16.860612448260003, + "highlightText": "7.8x faster", + "grimoireHeight": 10.920086928292807, "tailwindHeight": 85.0, - "grimoireValue": "2.10s", - "tailwindValue": "10.58s", - "grimoireRawValue": 2.1, - "tailwindRawValue": 10.58 + "grimoireValue": "1.20s", + "tailwindValue": "9.37s", + "grimoireRawValue": 1.2, + "tailwindRawValue": 9.37 }, { "title": "Grimoire CSS vs Tailwind CSS - Peak Memory Usage", "chartTitle": "Peak Memory Usage", "chartSubtitle": "Maximum memory consumed during compilation (lower is better)", "chartId": "chart_peak_memory", - "highlightText": "3.1x less", - "grimoireHeight": 27.400353292870733, + "highlightText": "1.2x less", + "grimoireHeight": 71.67148088925569, "tailwindHeight": 85.0, - "grimoireValue": "111.2 MB", - "tailwindValue": "344.97 MB", - "grimoireRawValue": 111.2, - "tailwindRawValue": 344.97 + "grimoireValue": "255.42 MB", + "tailwindValue": "302.92 MB", + "grimoireRawValue": 255.42, + "tailwindRawValue": 302.92 }, { "title": "Grimoire CSS vs Tailwind CSS - Average Memory Usage", "chartTitle": "Average Memory Usage", "chartSubtitle": "Average memory consumed during compilation (lower is better)", "chartId": "chart_avg_memory", - "highlightText": "4.0x less", - "grimoireHeight": 21.33518745121853, + "highlightText": "1.4x less", + "grimoireHeight": 62.186894050071736, "tailwindHeight": 85.0, - "grimoireValue": "45.76 MB", - "tailwindValue": "182.31 MB", - "grimoireRawValue": 45.76, - "tailwindRawValue": 182.31 + "grimoireValue": "121.15 MB", + "tailwindValue": "165.59 MB", + "grimoireRawValue": 121.15, + "tailwindRawValue": 165.59 }, { "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (User Time)", "chartTitle": "CPU Usage (User Time)", "chartSubtitle": "CPU time spent in user mode during compilation (lower is better)", "chartId": "chart_cpu_user", - "highlightText": "10.3x less", - "grimoireHeight": 8.257689475836834, + "highlightText": "9.4x less", + "grimoireHeight": 8.997005544604335, "tailwindHeight": 85.0, - "grimoireValue": "755.11ms", - "tailwindValue": "7.77s", - "grimoireRawValue": 0.76, - "tailwindRawValue": 7.77 + "grimoireValue": "922.85ms", + "tailwindValue": "8.72s", + "grimoireRawValue": 0.92, + "tailwindRawValue": 8.72 }, { "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (System Time)", "chartTitle": "CPU Usage (System Time)", "chartSubtitle": "CPU time spent in system mode during compilation (lower is better)", "chartId": "chart_cpu_system", - "highlightText": "45.7x less", - "grimoireHeight": 2, + "highlightText": "15.5x less", + "grimoireHeight": 5.489235917923912, "tailwindHeight": 85.0, - "grimoireValue": "1.33s", - "tailwindValue": "60.89s", - "grimoireRawValue": 1.33, - "tailwindRawValue": 60.89 + "grimoireValue": "2.79s", + "tailwindValue": "43.22s", + "grimoireRawValue": 2.79, + "tailwindRawValue": 43.22 }, { "title": "Grimoire CSS vs Tailwind CSS - Output Size", @@ -71,11 +71,11 @@ "chartSubtitle": "Size of the generated CSS file (lower is better)", "chartId": "chart_output", "highlightText": "1.1x less", - "grimoireHeight": 75.82476240840325, + "grimoireHeight": 75.82519186689818, "tailwindHeight": 85.0, "grimoireValue": "5.05 MB", "tailwindValue": "5.66 MB", - "grimoireRawValue": 5172.63, + "grimoireRawValue": 5172.66, "tailwindRawValue": 5798.54 }, { @@ -83,36 +83,37 @@ "chartTitle": "Processing Speed", "chartSubtitle": "Number of utility classes processed per second (higher is better)", "chartId": "chart_classes_per_second", - "highlightText": "5.0x faster", + "highlightText": "7.8x faster", "grimoireHeight": 85.0, - "tailwindHeight": 16.860612448260003, - "grimoireValue": "190684.63 classes/s", - "tailwindValue": "37824.23 classes/s", - "grimoireRawValue": 190684.63, - "tailwindRawValue": 37824.23 + "tailwindHeight": 10.92008692829281, + "grimoireValue": "332246.82 classes/s", + "tailwindValue": "42684.28 classes/s", + "grimoireRawValue": 332246.82, + "tailwindRawValue": 42684.28 }, { "title": "Grimoire CSS vs Tailwind CSS - Memory Efficiency", "chartTitle": "Memory Efficiency", "chartSubtitle": "Number of utility classes processed per MB of memory (higher is better)", "chartId": "chart_memory_efficiency", - "highlightText": "3.1x more efficient", + "highlightText": "1.2x more efficient", "grimoireHeight": 85.0, - "tailwindHeight": 27.400353292870733, - "grimoireValue": "3597.08 classes/MB", - "tailwindValue": "1159.54 classes/MB", - "grimoireRawValue": 3597.08, - "tailwindRawValue": 1159.54 + "tailwindHeight": 71.67148088925569, + "grimoireValue": "1566.06 classes/MB", + "tailwindValue": "1320.49 classes/MB", + "grimoireRawValue": 1566.06, + "tailwindRawValue": 1320.49 } ], "metadata": { - "timestamp": 1742724888.643092, - "timestamp_human": "2025-03-23 11:14:48", + "timestamp": 1766795065.6670752, + "timestamp_human": "2025-12-27 01:24:25", "system": { - "os": "Darwin 24.3.0", + "os": "Darwin 25.2.0", "cpu": "arm", "cores": "12 physical, 12 logical", - "memory": "32.0 GB" + "memory": "32.0 GB", + "grimoire_css_jobs": 4 } } } \ No newline at end of file diff --git a/benchmark/results/result_20251227_012441.json b/benchmark/results/result_20251227_012441.json new file mode 100644 index 0000000..32eb8af --- /dev/null +++ b/benchmark/results/result_20251227_012441.json @@ -0,0 +1,140 @@ +{ + "system_info": { + "os": { + "name": "Darwin", + "version": "Darwin Kernel Version 25.2.0: Tue Nov 18 21:07:05 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6020", + "release": "25.2.0" + }, + "benchmark": { + "grimoire_css_jobs": 6 + }, + "cpu": { + "name": "arm", + "cores_logical": 12, + "cores_physical": 12 + }, + "memory": { + "total_gb": 32.0 + }, + "psutil_version": "7.0.0", + "git": { + "sha": "8f60e2643076e9065a21408a98d0da64b5aea71b", + "dirty": true + }, + "python_version": "3.14.2", + "timestamp": 1766795081.854123, + "timestamp_human": "2025-12-27 01:24:41" + }, + "grimoire": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5296800, + "total_size_kb": 5172.65625, + "avg_size_bytes": 529680.0, + "avg_size_kb": 517.265625 + }, + "process": { + "memory": { + "peak_bytes": 151404544, + "peak_mb": 144.390625, + "avg_bytes": 89052997.81818181, + "avg_mb": 84.92755681818181, + "measurement": "rss", + "rss_peak_bytes": 151404544, + "rss_peak_mb": 144.390625, + "rss_avg_bytes": 89052997.81818181, + "rss_avg_mb": 84.92755681818181, + "uss_peak_bytes": 94502912, + "uss_peak_mb": 90.125, + "uss_avg_bytes": 49753526.85714286, + "uss_avg_mb": 47.448660714285715, + "uss_is_complete": true, + "uss_partial_peak_bytes": 94502912, + "uss_partial_peak_mb": 90.125, + "uss_partial_avg_bytes": 49753526.85714286, + "uss_partial_avg_mb": 47.448660714285715, + "uss_coverage_avg": 1.0, + "uss_process_count_avg": 1, + "uss_process_count_max": 1, + "uss_available_count_avg": 1, + "uss_available_count_max": 1, + "std_dev_mb": 53.88139312427017 + }, + "cpu": { + "user_time": 0.922690456, + "system_time": 4.8702180749999995, + "total_time": 5.792908530999999 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5296800, + "write_mb": 5.051422119140625 + } + }, + "throughput": { + "build_time_seconds": 1.3034098148345947, + "classes_per_second": 306891.9655563293, + "memory_efficiency": 2770.3045124986475, + "bytes_processed_per_second": 37967552.05981016, + "bytes_generated_per_second": 4063802.4508601497 + }, + "exit_code": 0, + "success": true, + "run": { + "executable": "../target/release/grimoire_css", + "argv": [ + "../target/release/grimoire_css", + "build" + ] + } + }, + "tailwind": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5937710, + "total_size_kb": 5798.544921875, + "avg_size_bytes": 593771.0, + "avg_size_kb": 579.8544921875 + }, + "process": { + "memory": { + "peak_bytes": 321994752, + "peak_mb": 307.078125, + "avg_bytes": 173116153.8164251, + "avg_mb": 165.09642964975845, + "std_dev_mb": 71.08418887937042 + }, + "cpu": { + "user_time": 8.819792976, + "system_time": 41.545980609, + "total_time": 50.365773585 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5937710, + "write_mb": 5.662641525268555 + } + }, + "throughput": { + "build_time_seconds": 9.26749849319458, + "classes_per_second": 43162.240629846034, + "memory_efficiency": 1302.6196509438762, + "bytes_processed_per_second": 5339874.620572109, + "bytes_generated_per_second": 640702.558987223 + }, + "exit_code": 0, + "success": true + } +} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012441.txt b/benchmark/results/result_20251227_012441.txt new file mode 100644 index 0000000..b59c6ab --- /dev/null +++ b/benchmark/results/result_20251227_012441.txt @@ -0,0 +1,126 @@ + + +================================================================================================================================================================ +GRIMOIRE PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:24:41 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 6 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 1.30 s +Processing Speed: 306891.97 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 144.39 MB +Average Memory: 84.93 MB +Peak RSS: 144.39 MB +Average RSS: 84.93 MB +Peak USS: 90.12 MB +Average USS: 47.45 MB +Primary Memory Metric: rss +Memory Efficiency: 2770.30 classes/MB +Memory Stability (Std Dev): 53.88 MB +Memory per Class: 378.51 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 922.69 ms +System CPU Time: 4.87 s +Total CPU Time: 5.79 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.05 MB +Output File Count: 10 +Output Size: 5.05 MB + +================================================================================ +TAILWIND PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:24:41 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 6 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 9.27 s +Processing Speed: 43162.24 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 307.08 MB +Average Memory: 165.10 MB +Memory Efficiency: 1302.62 classes/MB +Memory Stability (Std Dev): 71.08 MB +Memory per Class: 804.97 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 8.82 s +System CPU Time: 41.55 s +Total CPU Time: 50.37 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.66 MB +Output File Count: 10 +Output Size: 5.66 MB + +================================================================================ +CSS FRAMEWORKS PERFORMANCE COMPARISON +================================================================================ +Generated: 2025-12-27 01:24:41 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 6 + +PERFORMANCE COMPARISON +-------------------------------------------------------------------------------- +Metric | Grimoire CSS | Tailwind CSS | Difference | Ratio (G/T) +------------------------------------------------------------------------------------------------- +Build Time | 1.30 s | 9.27 s | 7.96 s | 7.11x +Classes/sec | 306891.97 | 43162.24 | 263729.72 | 7.11x +Peak Memory | 144.39 MB | 307.08 MB | 162.69 MB | 2.13x +Memory Efficiency | 2770.30 classes/MB | 1302.62 classes/MB | 1467.68 classes/MB | 2.13x +Output Size | 5.05 MB | 5.66 MB | 625.89 KB | 1.12x + +Notes: +- Build Time: lower is better +- Classes/sec: higher is better +- Peak Memory: lower is better +- Memory Efficiency: higher is better +- Output Size: lower is better \ No newline at end of file diff --git a/benchmark/results/result_20251227_012441_pretty.json b/benchmark/results/result_20251227_012441_pretty.json new file mode 100644 index 0000000..06d0507 --- /dev/null +++ b/benchmark/results/result_20251227_012441_pretty.json @@ -0,0 +1,119 @@ +{ + "charts": [ + { + "title": "Grimoire CSS vs Tailwind CSS - Build Time", + "chartTitle": "Build Time", + "chartSubtitle": "Total time taken to compile CSS (lower is better)", + "chartId": "chart_time", + "highlightText": "7.1x faster", + "grimoireHeight": 11.954664394312775, + "tailwindHeight": 85.0, + "grimoireValue": "1.30s", + "tailwindValue": "9.27s", + "grimoireRawValue": 1.3, + "tailwindRawValue": 9.27 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Peak Memory Usage", + "chartTitle": "Peak Memory Usage", + "chartSubtitle": "Maximum memory consumed during compilation (lower is better)", + "chartId": "chart_peak_memory", + "highlightText": "2.1x less", + "grimoireHeight": 39.96768941128581, + "tailwindHeight": 85.0, + "grimoireValue": "144.39 MB", + "tailwindValue": "307.08 MB", + "grimoireRawValue": 144.39, + "tailwindRawValue": 307.08 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Average Memory Usage", + "chartTitle": "Average Memory Usage", + "chartSubtitle": "Average memory consumed during compilation (lower is better)", + "chartId": "chart_avg_memory", + "highlightText": "1.9x less", + "grimoireHeight": 43.725005712478264, + "tailwindHeight": 85.0, + "grimoireValue": "84.93 MB", + "tailwindValue": "165.1 MB", + "grimoireRawValue": 84.93, + "tailwindRawValue": 165.1 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (User Time)", + "chartTitle": "CPU Usage (User Time)", + "chartSubtitle": "CPU time spent in user mode during compilation (lower is better)", + "chartId": "chart_cpu_user", + "highlightText": "9.6x less", + "grimoireHeight": 8.892350304980672, + "tailwindHeight": 85.0, + "grimoireValue": "922.69ms", + "tailwindValue": "8.82s", + "grimoireRawValue": 0.92, + "tailwindRawValue": 8.82 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (System Time)", + "chartTitle": "CPU Usage (System Time)", + "chartSubtitle": "CPU time spent in system mode during compilation (lower is better)", + "chartId": "chart_cpu_system", + "highlightText": "8.5x less", + "grimoireHeight": 9.96410555983659, + "tailwindHeight": 85.0, + "grimoireValue": "4.87s", + "tailwindValue": "41.55s", + "grimoireRawValue": 4.87, + "tailwindRawValue": 41.55 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Output Size", + "chartTitle": "Output Size", + "chartSubtitle": "Size of the generated CSS file (lower is better)", + "chartId": "chart_output", + "highlightText": "1.1x less", + "grimoireHeight": 75.82519186689818, + "tailwindHeight": 85.0, + "grimoireValue": "5.05 MB", + "tailwindValue": "5.66 MB", + "grimoireRawValue": 5172.66, + "tailwindRawValue": 5798.54 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Processing Speed", + "chartTitle": "Processing Speed", + "chartSubtitle": "Number of utility classes processed per second (higher is better)", + "chartId": "chart_classes_per_second", + "highlightText": "7.1x faster", + "grimoireHeight": 85.0, + "tailwindHeight": 11.954664394312775, + "grimoireValue": "306891.97 classes/s", + "tailwindValue": "43162.24 classes/s", + "grimoireRawValue": 306891.97, + "tailwindRawValue": 43162.24 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Memory Efficiency", + "chartTitle": "Memory Efficiency", + "chartSubtitle": "Number of utility classes processed per MB of memory (higher is better)", + "chartId": "chart_memory_efficiency", + "highlightText": "2.1x more efficient", + "grimoireHeight": 85.0, + "tailwindHeight": 39.967689411285804, + "grimoireValue": "2770.30 classes/MB", + "tailwindValue": "1302.62 classes/MB", + "grimoireRawValue": 2770.3, + "tailwindRawValue": 1302.62 + } + ], + "metadata": { + "timestamp": 1766795081.854123, + "timestamp_human": "2025-12-27 01:24:41", + "system": { + "os": "Darwin 25.2.0", + "cpu": "arm", + "cores": "12 physical, 12 logical", + "memory": "32.0 GB", + "grimoire_css_jobs": 6 + } + } +} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012458.json b/benchmark/results/result_20251227_012458.json new file mode 100644 index 0000000..6637b36 --- /dev/null +++ b/benchmark/results/result_20251227_012458.json @@ -0,0 +1,140 @@ +{ + "system_info": { + "os": { + "name": "Darwin", + "version": "Darwin Kernel Version 25.2.0: Tue Nov 18 21:07:05 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6020", + "release": "25.2.0" + }, + "benchmark": { + "grimoire_css_jobs": 8 + }, + "cpu": { + "name": "arm", + "cores_logical": 12, + "cores_physical": 12 + }, + "memory": { + "total_gb": 32.0 + }, + "psutil_version": "7.0.0", + "git": { + "sha": "8f60e2643076e9065a21408a98d0da64b5aea71b", + "dirty": true + }, + "python_version": "3.14.2", + "timestamp": 1766795098.173505, + "timestamp_human": "2025-12-27 01:24:58" + }, + "grimoire": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5296800, + "total_size_kb": 5172.65625, + "avg_size_bytes": 529680.0, + "avg_size_kb": 517.265625 + }, + "process": { + "memory": { + "peak_bytes": 181878784, + "peak_mb": 173.453125, + "avg_bytes": 90574532.92307693, + "avg_mb": 86.37860576923077, + "measurement": "rss", + "rss_peak_bytes": 181878784, + "rss_peak_mb": 173.453125, + "rss_avg_bytes": 90574532.92307693, + "rss_avg_mb": 86.37860576923077, + "uss_peak_bytes": 108969984, + "uss_peak_mb": 103.921875, + "uss_avg_bytes": 28411326.35897436, + "uss_avg_mb": 27.095152243589745, + "uss_is_complete": true, + "uss_partial_peak_bytes": 108969984, + "uss_partial_peak_mb": 103.921875, + "uss_partial_avg_bytes": 28411326.35897436, + "uss_partial_avg_mb": 27.095152243589745, + "uss_coverage_avg": 1.0, + "uss_process_count_avg": 1, + "uss_process_count_max": 1, + "uss_available_count_avg": 1, + "uss_available_count_max": 1, + "std_dev_mb": 55.383478271518264 + }, + "cpu": { + "user_time": 0.92461585, + "system_time": 4.9346328239999995, + "total_time": 5.859248674 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5296800, + "write_mb": 5.051422119140625 + } + }, + "throughput": { + "build_time_seconds": 1.3175179958343506, + "classes_per_second": 303605.7201986728, + "memory_efficiency": 2306.1331411584542, + "bytes_processed_per_second": 37560989.797836475, + "bytes_generated_per_second": 4020286.6425711866 + }, + "exit_code": 0, + "success": true, + "run": { + "executable": "../target/release/grimoire_css", + "argv": [ + "../target/release/grimoire_css", + "build" + ] + } + }, + "tailwind": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5937710, + "total_size_kb": 5798.544921875, + "avg_size_bytes": 593771.0, + "avg_size_kb": 579.8544921875 + }, + "process": { + "memory": { + "peak_bytes": 319864832, + "peak_mb": 305.046875, + "avg_bytes": 173194673.23076922, + "avg_mb": 165.17131159855768, + "std_dev_mb": 71.40594682739257 + }, + "cpu": { + "user_time": 8.87050304, + "system_time": 41.328194268000004, + "total_time": 50.19869730800001 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5937710, + "write_mb": 5.662641525268555 + } + }, + "throughput": { + "build_time_seconds": 9.233954429626465, + "classes_per_second": 43319.035527900174, + "memory_efficiency": 1311.2935511960252, + "bytes_processed_per_second": 5359272.712157177, + "bytes_generated_per_second": 643030.0306604605 + }, + "exit_code": 0, + "success": true + } +} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012458.txt b/benchmark/results/result_20251227_012458.txt new file mode 100644 index 0000000..66271d3 --- /dev/null +++ b/benchmark/results/result_20251227_012458.txt @@ -0,0 +1,126 @@ + + +================================================================================================================================================================ +GRIMOIRE PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:24:58 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 8 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 1.32 s +Processing Speed: 303605.72 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 173.45 MB +Average Memory: 86.38 MB +Peak RSS: 173.45 MB +Average RSS: 86.38 MB +Peak USS: 103.92 MB +Average USS: 27.10 MB +Primary Memory Metric: rss +Memory Efficiency: 2306.13 classes/MB +Memory Stability (Std Dev): 55.38 MB +Memory per Class: 454.69 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 924.62 ms +System CPU Time: 4.93 s +Total CPU Time: 5.86 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.05 MB +Output File Count: 10 +Output Size: 5.05 MB + +================================================================================ +TAILWIND PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:24:58 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 8 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 9.23 s +Processing Speed: 43319.04 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 305.05 MB +Average Memory: 165.17 MB +Memory Efficiency: 1311.29 classes/MB +Memory Stability (Std Dev): 71.41 MB +Memory per Class: 799.65 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 8.87 s +System CPU Time: 41.33 s +Total CPU Time: 50.20 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.66 MB +Output File Count: 10 +Output Size: 5.66 MB + +================================================================================ +CSS FRAMEWORKS PERFORMANCE COMPARISON +================================================================================ +Generated: 2025-12-27 01:24:58 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 8 + +PERFORMANCE COMPARISON +-------------------------------------------------------------------------------- +Metric | Grimoire CSS | Tailwind CSS | Difference | Ratio (G/T) +------------------------------------------------------------------------------------------------ +Build Time | 1.32 s | 9.23 s | 7.92 s | 7.01x +Classes/sec | 303605.72 | 43319.04 | 260286.68 | 7.01x +Peak Memory | 173.45 MB | 305.05 MB | 131.59 MB | 1.76x +Memory Efficiency | 2306.13 classes/MB | 1311.29 classes/MB | 994.84 classes/MB | 1.76x +Output Size | 5.05 MB | 5.66 MB | 625.89 KB | 1.12x + +Notes: +- Build Time: lower is better +- Classes/sec: higher is better +- Peak Memory: lower is better +- Memory Efficiency: higher is better +- Output Size: lower is better \ No newline at end of file diff --git a/benchmark/results/result_20251227_012458_pretty.json b/benchmark/results/result_20251227_012458_pretty.json new file mode 100644 index 0000000..ff7e108 --- /dev/null +++ b/benchmark/results/result_20251227_012458_pretty.json @@ -0,0 +1,119 @@ +{ + "charts": [ + { + "title": "Grimoire CSS vs Tailwind CSS - Build Time", + "chartTitle": "Build Time", + "chartSubtitle": "Total time taken to compile CSS (lower is better)", + "chartId": "chart_time", + "highlightText": "7.0x faster", + "grimoireHeight": 12.12795996551718, + "tailwindHeight": 85.0, + "grimoireValue": "1.32s", + "tailwindValue": "9.23s", + "grimoireRawValue": 1.32, + "tailwindRawValue": 9.23 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Peak Memory Usage", + "chartTitle": "Peak Memory Usage", + "chartSubtitle": "Maximum memory consumed during compilation (lower is better)", + "chartId": "chart_peak_memory", + "highlightText": "1.8x less", + "grimoireHeight": 48.331967423039494, + "tailwindHeight": 85.0, + "grimoireValue": "173.45 MB", + "tailwindValue": "305.05 MB", + "grimoireRawValue": 173.45, + "tailwindRawValue": 305.05 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Average Memory Usage", + "chartTitle": "Average Memory Usage", + "chartSubtitle": "Average memory consumed during compilation (lower is better)", + "chartId": "chart_avg_memory", + "highlightText": "1.9x less", + "grimoireHeight": 44.451917341611335, + "tailwindHeight": 85.0, + "grimoireValue": "86.38 MB", + "tailwindValue": "165.17 MB", + "grimoireRawValue": 86.38, + "tailwindRawValue": 165.17 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (User Time)", + "chartTitle": "CPU Usage (User Time)", + "chartSubtitle": "CPU time spent in user mode during compilation (lower is better)", + "chartId": "chart_cpu_user", + "highlightText": "9.6x less", + "grimoireHeight": 8.859965088293349, + "tailwindHeight": 85.0, + "grimoireValue": "924.62ms", + "tailwindValue": "8.87s", + "grimoireRawValue": 0.92, + "tailwindRawValue": 8.87 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (System Time)", + "chartTitle": "CPU Usage (System Time)", + "chartSubtitle": "CPU time spent in system mode during compilation (lower is better)", + "chartId": "chart_cpu_system", + "highlightText": "8.4x less", + "grimoireHeight": 10.149095489632145, + "tailwindHeight": 85.0, + "grimoireValue": "4.93s", + "tailwindValue": "41.33s", + "grimoireRawValue": 4.93, + "tailwindRawValue": 41.33 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Output Size", + "chartTitle": "Output Size", + "chartSubtitle": "Size of the generated CSS file (lower is better)", + "chartId": "chart_output", + "highlightText": "1.1x less", + "grimoireHeight": 75.82519186689818, + "tailwindHeight": 85.0, + "grimoireValue": "5.05 MB", + "tailwindValue": "5.66 MB", + "grimoireRawValue": 5172.66, + "tailwindRawValue": 5798.54 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Processing Speed", + "chartTitle": "Processing Speed", + "chartSubtitle": "Number of utility classes processed per second (higher is better)", + "chartId": "chart_classes_per_second", + "highlightText": "7.0x faster", + "grimoireHeight": 85.0, + "tailwindHeight": 12.127959965517183, + "grimoireValue": "303605.72 classes/s", + "tailwindValue": "43319.04 classes/s", + "grimoireRawValue": 303605.72, + "tailwindRawValue": 43319.04 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Memory Efficiency", + "chartTitle": "Memory Efficiency", + "chartSubtitle": "Number of utility classes processed per MB of memory (higher is better)", + "chartId": "chart_memory_efficiency", + "highlightText": "1.8x more efficient", + "grimoireHeight": 85.0, + "tailwindHeight": 48.331967423039494, + "grimoireValue": "2306.13 classes/MB", + "tailwindValue": "1311.29 classes/MB", + "grimoireRawValue": 2306.13, + "tailwindRawValue": 1311.29 + } + ], + "metadata": { + "timestamp": 1766795098.173505, + "timestamp_human": "2025-12-27 01:24:58", + "system": { + "os": "Darwin 25.2.0", + "cpu": "arm", + "cores": "12 physical, 12 logical", + "memory": "32.0 GB", + "grimoire_css_jobs": 8 + } + } +} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012514.json b/benchmark/results/result_20251227_012514.json new file mode 100644 index 0000000..b3a8310 --- /dev/null +++ b/benchmark/results/result_20251227_012514.json @@ -0,0 +1,140 @@ +{ + "system_info": { + "os": { + "name": "Darwin", + "version": "Darwin Kernel Version 25.2.0: Tue Nov 18 21:07:05 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6020", + "release": "25.2.0" + }, + "benchmark": { + "grimoire_css_jobs": 10 + }, + "cpu": { + "name": "arm", + "cores_logical": 12, + "cores_physical": 12 + }, + "memory": { + "total_gb": 32.0 + }, + "psutil_version": "7.0.0", + "git": { + "sha": "8f60e2643076e9065a21408a98d0da64b5aea71b", + "dirty": true + }, + "python_version": "3.14.2", + "timestamp": 1766795114.513845, + "timestamp_human": "2025-12-27 01:25:14" + }, + "grimoire": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5296800, + "total_size_kb": 5172.65625, + "avg_size_bytes": 529680.0, + "avg_size_kb": 517.265625 + }, + "process": { + "memory": { + "peak_bytes": 271646720, + "peak_mb": 259.0625, + "avg_bytes": 63904206.451612905, + "avg_mb": 60.94380040322581, + "measurement": "rss", + "rss_peak_bytes": 271646720, + "rss_peak_mb": 259.0625, + "rss_avg_bytes": 63904206.451612905, + "rss_avg_mb": 60.94380040322581, + "uss_peak_bytes": 195018752, + "uss_peak_mb": 185.984375, + "uss_avg_bytes": 53297812.64516129, + "uss_avg_mb": 50.828755040322584, + "uss_is_complete": true, + "uss_partial_peak_bytes": 195018752, + "uss_partial_peak_mb": 185.984375, + "uss_partial_avg_bytes": 53297812.64516129, + "uss_partial_avg_mb": 50.828755040322584, + "uss_coverage_avg": 0.992, + "uss_process_count_avg": 1, + "uss_process_count_max": 1, + "uss_available_count_avg": 0.992, + "uss_available_count_max": 1, + "std_dev_mb": 41.60914957602498 + }, + "cpu": { + "user_time": 1.144358405, + "system_time": 21.18281145, + "total_time": 22.327169854999998 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5296800, + "write_mb": 5.051422119140625 + } + }, + "throughput": { + "build_time_seconds": 2.486582040786743, + "classes_per_second": 160865.79627730278, + "memory_efficiency": 1544.052110977081, + "bytes_processed_per_second": 19901728.231071133, + "bytes_generated_per_second": 2130152.922010213 + }, + "exit_code": 0, + "success": true, + "run": { + "executable": "../target/release/grimoire_css", + "argv": [ + "../target/release/grimoire_css", + "build" + ] + } + }, + "tailwind": { + "input": { + "unique_class_count": 400006, + "total_input_size_bytes": 49487280, + "file_count": 100000 + }, + "output": { + "file_count": 10, + "total_size_bytes": 5937710, + "total_size_kb": 5798.544921875, + "avg_size_bytes": 593771.0, + "avg_size_kb": 579.8544921875 + }, + "process": { + "memory": { + "peak_bytes": 320389120, + "peak_mb": 305.546875, + "avg_bytes": 172127066.2095238, + "avg_mb": 164.15316220238094, + "std_dev_mb": 70.51225332667009 + }, + "cpu": { + "user_time": 8.663403975, + "system_time": 42.962273485000004, + "total_time": 51.62567746 + }, + "io": { + "read_bytes": 49487280, + "read_mb": 47.19474792480469, + "write_bytes": 5937710, + "write_mb": 5.662641525268555 + } + }, + "throughput": { + "build_time_seconds": 9.339990854263306, + "classes_per_second": 42827.23679728384, + "memory_efficiency": 1309.1477371516237, + "bytes_processed_per_second": 5298429.171096155, + "bytes_generated_per_second": 635729.7445628322 + }, + "exit_code": 0, + "success": true + } +} \ No newline at end of file diff --git a/benchmark/results/result_20251227_012514.txt b/benchmark/results/result_20251227_012514.txt new file mode 100644 index 0000000..191c067 --- /dev/null +++ b/benchmark/results/result_20251227_012514.txt @@ -0,0 +1,126 @@ + + +================================================================================================================================================================ +GRIMOIRE PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:25:14 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 10 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 2.49 s +Processing Speed: 160865.80 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 259.06 MB +Average Memory: 60.94 MB +Peak RSS: 259.06 MB +Average RSS: 60.94 MB +Peak USS: 185.98 MB +Average USS: 50.83 MB +Primary Memory Metric: rss +Memory Efficiency: 1544.05 classes/MB +Memory Stability (Std Dev): 41.61 MB +Memory per Class: 679.11 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 1.14 s +System CPU Time: 21.18 s +Total CPU Time: 22.33 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.05 MB +Output File Count: 10 +Output Size: 5.05 MB + +================================================================================ +TAILWIND PERFORMANCE BENCHMARK REPORT +================================================================================ +Generated: 2025-12-27 01:25:14 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 10 + +INPUT SUMMARY +-------------------------------------------------------------------------------- +Unique Utility Classes: 400006 +Total Input Size: 47.19 MB +Input HTML Files: 100000 + +PERFORMANCE METRICS +-------------------------------------------------------------------------------- +Build Time: 9.34 s +Processing Speed: 42827.24 classes/second + +MEMORY USAGE +-------------------------------------------------------------------------------- +Peak Memory: 305.55 MB +Average Memory: 164.15 MB +Memory Efficiency: 1309.15 classes/MB +Memory Stability (Std Dev): 70.51 MB +Memory per Class: 800.96 bytes/class + +CPU USAGE +-------------------------------------------------------------------------------- +User CPU Time: 8.66 s +System CPU Time: 42.96 s +Total CPU Time: 51.63 s + +I/O & OUTPUT METRICS +-------------------------------------------------------------------------------- +Total Read: 47.19 MB +Total Written: 5.66 MB +Output File Count: 10 +Output Size: 5.66 MB + +================================================================================ +CSS FRAMEWORKS PERFORMANCE COMPARISON +================================================================================ +Generated: 2025-12-27 01:25:14 + +SYSTEM INFORMATION +-------------------------------------------------------------------------------- +OS: Darwin 25.2.0 +CPU: arm +Cores: 12 physical, 12 logical +Memory: 32.0 GB +GRIMOIRE_CSS_JOBS: 10 + +PERFORMANCE COMPARISON +-------------------------------------------------------------------------------- +Metric | Grimoire CSS | Tailwind CSS | Difference | Ratio (G/T) +------------------------------------------------------------------------------------------------ +Build Time | 2.49 s | 9.34 s | 6.85 s | 3.76x +Classes/sec | 160865.80 | 42827.24 | 118038.56 | 3.76x +Peak Memory | 259.06 MB | 305.55 MB | 46.48 MB | 1.18x +Memory Efficiency | 1544.05 classes/MB | 1309.15 classes/MB | 234.90 classes/MB | 1.18x +Output Size | 5.05 MB | 5.66 MB | 625.89 KB | 1.12x + +Notes: +- Build Time: lower is better +- Classes/sec: higher is better +- Peak Memory: lower is better +- Memory Efficiency: higher is better +- Output Size: lower is better \ No newline at end of file diff --git a/benchmark/results/result_20251227_012514_pretty.json b/benchmark/results/result_20251227_012514_pretty.json new file mode 100644 index 0000000..db5c9ea --- /dev/null +++ b/benchmark/results/result_20251227_012514_pretty.json @@ -0,0 +1,119 @@ +{ + "charts": [ + { + "title": "Grimoire CSS vs Tailwind CSS - Build Time", + "chartTitle": "Build Time", + "chartSubtitle": "Total time taken to compile CSS (lower is better)", + "chartId": "chart_time", + "highlightText": "3.8x faster", + "grimoireHeight": 22.629516106045934, + "tailwindHeight": 85.0, + "grimoireValue": "2.49s", + "tailwindValue": "9.34s", + "grimoireRawValue": 2.49, + "tailwindRawValue": 9.34 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Peak Memory Usage", + "chartTitle": "Peak Memory Usage", + "chartSubtitle": "Maximum memory consumed during compilation (lower is better)", + "chartId": "chart_peak_memory", + "highlightText": "1.2x less", + "grimoireHeight": 72.06852467399642, + "tailwindHeight": 85.0, + "grimoireValue": "259.06 MB", + "tailwindValue": "305.55 MB", + "grimoireRawValue": 259.06, + "tailwindRawValue": 305.55 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Average Memory Usage", + "chartTitle": "Average Memory Usage", + "chartSubtitle": "Average memory consumed during compilation (lower is better)", + "chartId": "chart_avg_memory", + "highlightText": "2.7x less", + "grimoireHeight": 31.557253998479826, + "tailwindHeight": 85.0, + "grimoireValue": "60.94 MB", + "tailwindValue": "164.15 MB", + "grimoireRawValue": 60.94, + "tailwindRawValue": 164.15 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (User Time)", + "chartTitle": "CPU Usage (User Time)", + "chartSubtitle": "CPU time spent in user mode during compilation (lower is better)", + "chartId": "chart_cpu_user", + "highlightText": "7.6x less", + "grimoireHeight": 11.227741971365244, + "tailwindHeight": 85.0, + "grimoireValue": "1.14s", + "tailwindValue": "8.66s", + "grimoireRawValue": 1.14, + "tailwindRawValue": 8.66 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - CPU Usage (System Time)", + "chartTitle": "CPU Usage (System Time)", + "chartSubtitle": "CPU time spent in system mode during compilation (lower is better)", + "chartId": "chart_cpu_system", + "highlightText": "2.0x less", + "grimoireHeight": 41.90976936727164, + "tailwindHeight": 85.0, + "grimoireValue": "21.18s", + "tailwindValue": "42.96s", + "grimoireRawValue": 21.18, + "tailwindRawValue": 42.96 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Output Size", + "chartTitle": "Output Size", + "chartSubtitle": "Size of the generated CSS file (lower is better)", + "chartId": "chart_output", + "highlightText": "1.1x less", + "grimoireHeight": 75.82519186689818, + "tailwindHeight": 85.0, + "grimoireValue": "5.05 MB", + "tailwindValue": "5.66 MB", + "grimoireRawValue": 5172.66, + "tailwindRawValue": 5798.54 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Processing Speed", + "chartTitle": "Processing Speed", + "chartSubtitle": "Number of utility classes processed per second (higher is better)", + "chartId": "chart_classes_per_second", + "highlightText": "3.8x faster", + "grimoireHeight": 85.0, + "tailwindHeight": 22.629516106045926, + "grimoireValue": "160865.80 classes/s", + "tailwindValue": "42827.24 classes/s", + "grimoireRawValue": 160865.8, + "tailwindRawValue": 42827.24 + }, + { + "title": "Grimoire CSS vs Tailwind CSS - Memory Efficiency", + "chartTitle": "Memory Efficiency", + "chartSubtitle": "Number of utility classes processed per MB of memory (higher is better)", + "chartId": "chart_memory_efficiency", + "highlightText": "1.2x more efficient", + "grimoireHeight": 85.0, + "tailwindHeight": 72.06852467399642, + "grimoireValue": "1544.05 classes/MB", + "tailwindValue": "1309.15 classes/MB", + "grimoireRawValue": 1544.05, + "tailwindRawValue": 1309.15 + } + ], + "metadata": { + "timestamp": 1766795114.513845, + "timestamp_human": "2025-12-27 01:25:14", + "system": { + "os": "Darwin 25.2.0", + "cpu": "arm", + "cores": "12 physical, 12 logical", + "memory": "32.0 GB", + "grimoire_css_jobs": 10 + } + } +} \ No newline at end of file diff --git a/src/commands/shorten.rs b/src/commands/shorten.rs index 85020f2..af0f1f4 100644 --- a/src/commands/shorten.rs +++ b/src/commands/shorten.rs @@ -43,16 +43,11 @@ pub fn shorten(current_dir: &Path) -> Result<(), GrimoireCssError> { let mut new_parts = Vec::with_capacity(parts.len()); let mut changed = false; for part in parts { - if let Ok(Some(spell)) = Spell::new( - part, - &config.shared_spells, - &config.scrolls, - (0, 0), - None, - None, - ) { - if let Some(short) = get_shorten_component(&spell.component) { - let short_part = part.replacen(&spell.component, short, 1); + if let Ok(Some(spell)) = + Spell::new(part, &config.shared_spells, &config.scrolls, (0, 0), None) + { + if let Some(short) = get_shorten_component(spell.component()) { + let short_part = part.replacen(spell.component(), short, 1); if short_part != part { changed = true; } @@ -79,10 +74,9 @@ pub fn shorten(current_dir: &Path) -> Result<(), GrimoireCssError> { &config.scrolls, (0, 0), None, - None, - ) && let Some(short) = get_shorten_component(&spell.component) + ) && let Some(short) = get_shorten_component(spell.component()) { - let short_spell = raw_spell.replacen(&spell.component, short, 1); + let short_spell = raw_spell.replacen(spell.component(), short, 1); if raw_spell != &short_spell && new_content.contains(raw_spell) { let count = new_content.matches(raw_spell).count(); new_content = new_content.replace(raw_spell, &short_spell); diff --git a/src/core/css_builder/css_builder_base.rs b/src/core/css_builder/css_builder_base.rs index 36dc0f7..2bb6333 100644 --- a/src/core/css_builder/css_builder_base.rs +++ b/src/core/css_builder/css_builder_base.rs @@ -5,6 +5,21 @@ use crate::core::{CssOptimizer, GrimoireCssError, css_generator::CssGenerator, spell::Spell}; use std::collections::HashMap; +#[derive(Debug, Clone, Copy)] +struct PieceRange { + start: usize, + end: usize, + spell_index: usize, +} + +#[derive(Debug, Clone)] +struct MediaEntry { + min_width: Option, + start: usize, + end: usize, + spell_index: usize, +} + /// Core CSS builder that handles spell compilation and optimization pub struct CssBuilder<'a> { css_generator: CssGenerator<'a>, @@ -49,22 +64,105 @@ impl<'a> CssBuilder<'a> { /// # Errors /// /// Returns a `GrimoireCSSError` if CSS generation fails. + #[allow(dead_code)] pub fn combine_spells_to_css(&self, spells: &[Spell]) -> Result, GrimoireCssError> { - let mut base_rules: Vec<(String, usize)> = Vec::new(); - let mut media_queries: Vec<(String, usize)> = Vec::new(); + let (raw_css, pieces) = self.build_joined_css_and_pieces(spells)?; + self.validate_or_isolate(spells, &raw_css, &pieces)?; + Ok(pieces + .iter() + .map(|p| raw_css[p.start..p.end].to_string()) + .collect()) + } + + /// Memory-efficient variant that returns a single joined CSS string. + pub fn combine_spells_to_css_string( + &self, + spells: &[Spell], + ) -> Result { + let (raw_css, pieces) = self.build_joined_css_and_pieces(spells)?; + self.validate_or_isolate(spells, &raw_css, &pieces)?; + Ok(raw_css) + } + + /// Builds and returns optimized CSS in one step. + /// + /// This avoids the common `validate()` then `optimize()` double-parse on the success path. + /// On failure, it still performs rule isolation to produce a precise, spell-linked error. + pub fn combine_spells_to_optimized_css_string( + &self, + spells: &[Spell], + ) -> Result { + let (raw_css, pieces) = self.build_joined_css_and_pieces(spells)?; + + match self.optimizer.optimize(&raw_css) { + Ok(css) => Ok(css), + Err(optimize_err) => match self.validate_or_isolate(spells, &raw_css, &pieces) { + // Optimization may fail even if parsing succeeds (e.g. minify stage). + Ok(()) => Err(optimize_err), + Err(isolated_err) => Err(isolated_err), + }, + } + } + + /// Optimizes and minifies CSS. + /// + /// # Arguments + /// + /// * `raw_css` - Raw CSS string to optimize. + /// + /// # Returns + /// + /// Optimized and minified CSS string. + pub fn optimize_css(&self, raw_css: &str) -> Result { + self.optimizer.optimize(raw_css) + } + + fn create_compile_error(&self, spell: &Spell, error: GrimoireCssError) -> GrimoireCssError { + GrimoireCssError::CompileError { + message: format!("Invalid CSS generated: {}", error), + span: spell.span, + label: "This spell generated invalid CSS".to_string(), + help: Some( + "This usually means the spell value is not valid CSS after Grimoire transformations.\n\ +If you intended spaces inside a value, encode them as '_' (underscores)." + .to_string(), + ), + source_file: spell.source.clone(), + } + } + + fn build_joined_css_and_pieces( + &self, + spells: &[Spell], + ) -> Result<(String, Vec), GrimoireCssError> { + use once_cell::sync::Lazy; + + static MIN_WIDTH_RE: Lazy = + Lazy::new(|| regex::Regex::new(r"min-width:\s*(\\d+)").unwrap()); + + fn extract_min_width(re: ®ex::Regex, s: &str) -> Option { + re.captures(s) + .and_then(|cap| cap.get(1)) + .and_then(|m| m.as_str().parse::().ok()) + } + + let mut base_css = String::new(); + let mut base_pieces: Vec = Vec::new(); + + let mut media_css = String::new(); + let mut media_entries: Vec = Vec::new(); for (spell_index, spell) in spells.iter().enumerate() { match &spell.scroll_spells { Some(ss) if !ss.is_empty() => { - let mut local_scroll_css_vec = Vec::new(); - let mut local_scroll_additional_css_vec = Vec::new(); + let mut combined_scroll_css = String::new(); for s in ss { if let Some(css) = self.css_generator.generate_css(s)? { let class_name = self.css_generator.generate_css_class_name( &spell.raw_spell, - &spell.effects, - &spell.focus, + spell.effects(), + spell.focus(), spell.with_template, )?; @@ -74,154 +172,184 @@ impl<'a> CssBuilder<'a> { &css.0, ); - local_scroll_css_vec.push(updated_css); + combined_scroll_css.push_str(&updated_css); if let Some(additional_css) = css.2 { - local_scroll_additional_css_vec.push(additional_css); + let start = base_css.len(); + base_css.push_str(&additional_css); + let end = base_css.len(); + base_pieces.push(PieceRange { + start, + end, + spell_index, + }); } } } - let combined_css = local_scroll_css_vec.join(""); - let wrapped_css = if spell.area.is_empty() { - combined_css + let wrapped_css = if spell.area().is_empty() { + combined_scroll_css } else { self.css_generator - .wrap_base_css_with_media_query(&spell.area, &combined_css) + .wrap_base_css_with_media_query(spell.area(), &combined_scroll_css) }; + if wrapped_css.trim_start().starts_with("@media") { - media_queries.push((wrapped_css, spell_index)); + let start = media_css.len(); + media_css.push_str(&wrapped_css); + let end = media_css.len(); + media_entries.push(MediaEntry { + min_width: extract_min_width(&MIN_WIDTH_RE, &wrapped_css), + start, + end, + spell_index, + }); } else { - base_rules.push((wrapped_css, spell_index)); - } - - for add_css in local_scroll_additional_css_vec { - base_rules.push((add_css, spell_index)); + let start = base_css.len(); + base_css.push_str(&wrapped_css); + let end = base_css.len(); + base_pieces.push(PieceRange { + start, + end, + spell_index, + }); } } _ => { if let Some(css) = self.css_generator.generate_css(spell)? { if css.0.trim_start().starts_with("@media") { - media_queries.push((css.0, spell_index)); + let start = media_css.len(); + media_css.push_str(&css.0); + let end = media_css.len(); + media_entries.push(MediaEntry { + min_width: extract_min_width(&MIN_WIDTH_RE, &css.0), + start, + end, + spell_index, + }); } else { - base_rules.push((css.0, spell_index)); + let start = base_css.len(); + base_css.push_str(&css.0); + let end = base_css.len(); + base_pieces.push(PieceRange { + start, + end, + spell_index, + }); } if let Some(additional_css) = css.2 { - base_rules.push((additional_css, spell_index)); + let start = base_css.len(); + base_css.push_str(&additional_css); + let end = base_css.len(); + base_pieces.push(PieceRange { + start, + end, + spell_index, + }); } } } } } - media_queries.sort_by(|a, b| { - fn extract_min_width(s: &str) -> Option { - let re = regex::Regex::new(r"min-width:\s*(\\d+)").unwrap(); - re.captures(s) - .and_then(|cap| cap.get(1)) - .and_then(|m| m.as_str().parse::().ok()) - } - match (extract_min_width(&a.0), extract_min_width(&b.0)) { - (Some(aw), Some(bw)) => aw.cmp(&bw), - (Some(_), None) => std::cmp::Ordering::Less, - (None, Some(_)) => std::cmp::Ordering::Greater, - (None, None) => a.0.cmp(&b.0), + // Sort media queries by min-width, then by the text itself (stable deterministic output). + media_entries.sort_by(|a, b| match (a.min_width, b.min_width) { + (Some(aw), Some(bw)) => aw.cmp(&bw), + (Some(_), None) => std::cmp::Ordering::Less, + (None, Some(_)) => std::cmp::Ordering::Greater, + (None, None) => { + let aslice = &media_css[a.start..a.end]; + let bslice = &media_css[b.start..b.end]; + aslice.cmp(bslice) } }); - base_rules.extend(media_queries); - // Validate the combined output once (fast path). - if let Err(e) = self.validate_joined_css(&base_rules) { - if let Some((spell_index, rule_error)) = self.find_first_invalid_rule(&base_rules) { - return Err(self.create_compile_error(&spells[spell_index], rule_error)); - } + // Final output: base rules, then sorted media queries. + let mut raw_css = base_css; + let mut pieces = base_pieces; - // Fallback: no rule isolated (should be rare), attach to first spell if available. - if let Some(first) = spells.first() { - return Err(self.create_compile_error(first, e)); - } - return Err(e); - } - - Ok(base_rules.into_iter().map(|(css, _)| css).collect()) - } + raw_css.reserve(media_css.len()); - /// Optimizes and minifies CSS. - /// - /// # Arguments - /// - /// * `raw_css` - Raw CSS string to optimize. - /// - /// # Returns - /// - /// Optimized and minified CSS string. - pub fn optimize_css(&self, raw_css: &str) -> Result { - self.optimizer.optimize(raw_css) - } - - fn create_compile_error(&self, spell: &Spell, error: GrimoireCssError) -> GrimoireCssError { - GrimoireCssError::CompileError { - message: format!("Invalid CSS generated: {}", error), - span: spell.span, - label: "This spell generated invalid CSS".to_string(), - help: Some( - "This usually means the spell value is not valid CSS after Grimoire transformations.\n\ -If you intended spaces inside a value, encode them as '_' (underscores)." - .to_string(), - ), - source_file: spell.source.clone(), + for m in media_entries { + let start = raw_css.len(); + raw_css.push_str(&media_css[m.start..m.end]); + let end = raw_css.len(); + pieces.push(PieceRange { + start, + end, + spell_index: m.spell_index, + }); } - } - fn validate_joined_css(&self, rules: &[(String, usize)]) -> Result<(), GrimoireCssError> { - if rules.is_empty() { - return Ok(()); - } - let mut joined = String::new(); - for (css, _) in rules { - joined.push_str(css); - } - self.optimizer.validate(&joined) + Ok((raw_css, pieces)) } - fn validate_rules_slice(&self, rules: &[(String, usize)]) -> Result<(), GrimoireCssError> { - if rules.is_empty() { + fn validate_or_isolate( + &self, + spells: &[Spell], + raw_css: &str, + pieces: &[PieceRange], + ) -> Result<(), GrimoireCssError> { + if pieces.is_empty() { return Ok(()); } - let mut joined = String::new(); - for (css, _) in rules { - joined.push_str(css); + + if let Err(e) = self.optimizer.validate(raw_css) { + if let Some((spell_index, rule_error)) = self.find_first_invalid_piece(raw_css, pieces) + { + return Err(self.create_compile_error(&spells[spell_index], rule_error)); + } + + if let Some(first) = spells.first() { + return Err(self.create_compile_error(first, e)); + } + return Err(e); } - self.optimizer.validate(&joined) + + Ok(()) } - /// Returns the first invalid rule in source order (by spell index), if any. - fn find_first_invalid_rule( + /// Returns the first invalid piece in source order (by spell index), if any. + fn find_first_invalid_piece( &self, - rules: &[(String, usize)], + raw_css: &str, + pieces: &[PieceRange], ) -> Option<(usize, GrimoireCssError)> { - if rules.is_empty() { + if pieces.is_empty() { return None; } - // If the entire set validates, nothing to isolate. - if self.validate_rules_slice(rules).is_ok() { + // If the entire slice validates, nothing to isolate. + let full_start = pieces.first()?.start; + let full_end = pieces.last()?.end; + if self + .optimizer + .validate(&raw_css[full_start..full_end]) + .is_ok() + { return None; } - if rules.len() == 1 { - let rule_error = self.optimizer.validate(&rules[0].0).err()?; - return Some((rules[0].1, rule_error)); + if pieces.len() == 1 { + let p = pieces[0]; + let rule_error = self.optimizer.validate(&raw_css[p.start..p.end]).err()?; + return Some((p.spell_index, rule_error)); } - let mid = rules.len() / 2; - let (left, right) = rules.split_at(mid); + let mid = pieces.len() / 2; + let (left, right) = pieces.split_at(mid); - if self.validate_rules_slice(left).is_err() { - return self.find_first_invalid_rule(left); + let left_start = left.first()?.start; + let left_end = left.last()?.end; + if self + .optimizer + .validate(&raw_css[left_start..left_end]) + .is_err() + { + return self.find_first_invalid_piece(raw_css, left); } - self.find_first_invalid_rule(right) + self.find_first_invalid_piece(raw_css, right) } } diff --git a/src/core/css_builder/css_builder_fs.rs b/src/core/css_builder/css_builder_fs.rs index b9b2467..5b28dd1 100644 --- a/src/core/css_builder/css_builder_fs.rs +++ b/src/core/css_builder/css_builder_fs.rs @@ -20,19 +20,22 @@ use crate::{ use regex::Regex; use std::{ collections::HashSet, - fs, + env, fs, path::{Path, PathBuf}, sync::Arc, + thread, }; use super::CssBuilder; +type CriticalCssEntries = Vec<(PathBuf, Arc)>; +type CriticalCssResult = Option; + /// Manages the process of compiling and building CSS files with filesystem persistence. pub struct CssBuilderFs<'a> { css_builder: CssBuilder<'a>, config: &'a ConfigFs, current_dir: &'a Path, - parser: ParserFs, inline_css_regex: Regex, } @@ -54,14 +57,12 @@ impl<'a> CssBuilderFs<'a> { optimizer: &'a O, ) -> Result { let css_builder = CssBuilder::new(optimizer, &config.variables, &config.custom_animations)?; - let parser = ParserFs::new(current_dir); let inline_css_regex = Regex::new(r#"(?s)"#)?; Ok(Self { css_builder, config, current_dir, - parser, inline_css_regex, }) } @@ -74,95 +75,80 @@ impl<'a> CssBuilderFs<'a> { /// /// Returns a `GrimoireCSSError` if any step in the build process fails. pub fn build(&mut self) -> Result<(), GrimoireCssError> { - let mut project_build_info = Vec::new(); - - for project in &self.config.projects { - let project_output_dir_path = project - .output_dir_path - .as_deref() - .map(|d| self.current_dir.join(d)) - .unwrap_or_else(|| self.current_dir.join("grimoire/dist")); - - if let Some(single_output_file_name) = &project.single_output_file_name { - let parsing_results = self - .parser - .collect_classes_single_output(&project.input_paths)?; - let bundle_output_full_path = project_output_dir_path.join(single_output_file_name); - - let mut all_spells = Vec::new(); - let mut seen_spells = std::collections::HashSet::new(); - - for (file_path, content, classes) in parsing_results { - let source = Arc::new(SourceFile::new( - Some(file_path.clone()), - file_path.to_string_lossy().to_string(), - content, - )); - let spells = Spell::generate_spells_from_classes( - classes, - &self.config.shared_spells, - &self.config.scrolls, - Some(file_path), - Some(source), - )?; + let lock_enabled = self.config.lock.unwrap_or(false); + + let jobs = Self::jobs_from_env()?; + + // Only collect output paths when we actually need them for file tracking. + let mut compiled_project_paths: Option> = lock_enabled.then(Vec::new); + + if jobs <= 1 || self.config.projects.len() <= 1 { + for project in &self.config.projects { + let outputs = self.build_project(project)?; + if let Some(paths) = &mut compiled_project_paths { + paths.extend(outputs); + } + } + } else { + let mut all_outputs: Vec = Vec::new(); + let this: &CssBuilderFs<'a> = &*self; + + // NOTE: Parallelism is intentionally limited to project-level isolation. Each project + // builds its own parser/builder instances to avoid shared mutable state. + thread::scope(|scope| { + let projects = &self.config.projects; + let chunk_size = projects.len().div_ceil(jobs); + let mut handles = Vec::new(); + + for chunk in projects.chunks(chunk_size) { + handles.push(scope.spawn(move || { + let mut outputs = Vec::new(); + for project in chunk { + outputs.extend(this.build_project(project)?); + } + Ok::<_, GrimoireCssError>(outputs) + })); + } - for spell in spells { - if seen_spells.insert(spell.clone()) { - all_spells.push(spell); + for h in handles { + match h.join() { + Ok(Ok(outputs)) => all_outputs.extend(outputs), + Ok(Err(e)) => return Err(e), + Err(_) => { + return Err(GrimoireCssError::InvalidInput( + "Project build thread panicked".to_string(), + )); } } } - project_build_info.push(BuildInfo { - file_path: bundle_output_full_path, - spells: all_spells, - }); - } else { - let parsing_results = self.parser.collect_classes_multiple_output( - &project.input_paths, - &project_output_dir_path, - )?; - - for (output_file_path, source_path, content, classes) in parsing_results { - let source = Arc::new(SourceFile::new( - Some(source_path.clone()), - source_path.to_string_lossy().to_string(), - content, - )); - let spells = Spell::generate_spells_from_classes( - classes, - &self.config.shared_spells, - &self.config.scrolls, - Some(source_path), - Some(source), - )?; + Ok(()) + })?; - project_build_info.push(BuildInfo { - file_path: output_file_path, - spells, - }); - } + if let Some(paths) = &mut compiled_project_paths { + paths.extend(all_outputs); } } - - let compiled_css: Vec<(PathBuf, String)> = self.compile_css(&project_build_info)?; let compiled_shared_css: Option> = self.compile_shared_css()?; - let compiled_critical_css: Option> = self.compile_critical_css()?; - - Self::write_compiled_css(&compiled_css)?; + let compiled_critical_css: CriticalCssResult = self.compile_critical_css()?; if let Some(compiled_shared_css) = &compiled_shared_css { Self::write_compiled_css(compiled_shared_css)?; } // Track file changes if locking is enabled - if self.config.lock.unwrap_or(false) { - let all_compiled_paths = compiled_css.iter().map(|(path, _)| path.as_path()).chain( - compiled_shared_css - .as_ref() - .into_iter() - .flat_map(|css| css.iter().map(|(path, _)| path.as_path())), - ); + if lock_enabled { + let all_compiled_paths = compiled_project_paths + .as_ref() + .expect("compiled_project_paths must be collected when lock is enabled") + .iter() + .map(|p| p.as_path()) + .chain( + compiled_shared_css + .as_ref() + .into_iter() + .flat_map(|css| css.iter().map(|(path, _)| path.as_path())), + ); FileTracker::track(self.current_dir, all_compiled_paths)?; } @@ -187,6 +173,7 @@ impl<'a> CssBuilderFs<'a> { /// # Errors /// /// Returns a `GrimoireCSSError` if spell assembly or CSS optimization fails. + #[allow(dead_code)] fn compile_css( &self, project_build_info: &[BuildInfo], @@ -194,14 +181,9 @@ impl<'a> CssBuilderFs<'a> { let compiled_css: Result, GrimoireCssError> = project_build_info .iter() .map(|build_info| { - let assembled_spells = - self.css_builder.combine_spells_to_css(&build_info.spells)?; - let raw_css = if assembled_spells.len() == 1 { - assembled_spells[0].clone() - } else { - assembled_spells.concat() - }; - let css = self.css_builder.optimize_css(&raw_css)?; + let css = self + .css_builder + .combine_spells_to_optimized_css_string(&build_info.spells)?; Ok((build_info.file_path.clone(), css)) }) .collect(); @@ -293,9 +275,9 @@ impl<'a> CssBuilderFs<'a> { /// # Errors /// /// Returns a `GrimoireCSSError` if CSS composition or optimization fails. - fn compile_critical_css(&self) -> Result>, GrimoireCssError> { + fn compile_critical_css(&self) -> Result { self.config.critical.as_ref().map_or(Ok(None), |critical| { - let mut compiled_critical_css = Vec::new(); + let mut compiled_critical_css: CriticalCssEntries = Vec::new(); for critical_item in critical { if critical_item.file_to_inline_paths.is_empty() { @@ -317,11 +299,12 @@ impl<'a> CssBuilderFs<'a> { } if !composed_css.is_empty() { - let optimized_css = self.css_builder.optimize_css(&composed_css)?; + let optimized_css: Arc = + Arc::from(self.css_builder.optimize_css(&composed_css)?); for path_to_inline in &critical_item.file_to_inline_paths { compiled_critical_css - .push((PathBuf::from(&path_to_inline), optimized_css.clone())); + .push((PathBuf::from(&path_to_inline), Arc::clone(&optimized_css))); } } } @@ -377,7 +360,7 @@ impl<'a> CssBuilderFs<'a> { ) } - /// Composes additional CSS from shared styles. + /// Composes additional (raw, unoptimized) CSS from shared styles. /// /// # Arguments /// @@ -385,7 +368,7 @@ impl<'a> CssBuilderFs<'a> { /// /// # Returns /// - /// Composed and optimized CSS string. + /// Composed (raw) CSS string. /// /// # Errors /// @@ -415,20 +398,21 @@ impl<'a> CssBuilderFs<'a> { &self.config.scrolls, (0, 0), None, - None, )? { spells.push(spell); } } - let assembled_spells = self.css_builder.combine_spells_to_css(&spells)?; - let mut raw_css = assembled_spells.join(""); + let mut raw_css = self.css_builder.combine_spells_to_css_string(&spells)?; if !files_content.is_empty() { - raw_css.push_str(&files_content.join("")); + for contents in files_content { + raw_css.push_str(&contents); + } } - self.css_builder.optimize_css(&raw_css) + // Important: callers are responsible for running optimization exactly once. + Ok(raw_css) } /// Injects critical CSS into HTML files. @@ -442,11 +426,11 @@ impl<'a> CssBuilderFs<'a> { /// Returns a `GrimoireCSSError` if reading or writing HTML files fails. fn inject_critical_css_into_html( &self, - inline_shared_css: &[(PathBuf, String)], + inline_shared_css: &[(PathBuf, Arc)], ) -> Result<(), GrimoireCssError> { for (file_path, css) in inline_shared_css { let path = self.current_dir.join(file_path); - self.embed_critical_css(&path, css)?; + self.embed_critical_css(&path, css.as_ref())?; } Ok(()) @@ -485,11 +469,120 @@ impl<'a> CssBuilderFs<'a> { fs::write(html_file_path, updated_html_content)?; Ok(()) } + + fn build_project( + &self, + project: &'a crate::core::ConfigFsProject, + ) -> Result, GrimoireCssError> { + let project_output_dir_path = project + .output_dir_path + .as_deref() + .map(|d| self.current_dir.join(d)) + .unwrap_or_else(|| self.current_dir.join("grimoire/dist")); + + let parser = ParserFs::new(self.current_dir); + + let mut outputs = Vec::new(); + + if let Some(single_output_file_name) = &project.single_output_file_name { + let parsing_results = parser.collect_classes_single_output(&project.input_paths)?; + let bundle_output_full_path = project_output_dir_path.join(single_output_file_name); + + let mut all_spells = Vec::new(); + for (file_path, classes) in parsing_results { + let source = Arc::new(SourceFile::new_path_only( + Some(file_path.clone()), + file_path.to_string_lossy().to_string(), + )); + let spells = Spell::generate_spells_from_classes( + classes, + &self.config.shared_spells, + &self.config.scrolls, + Some(source), + )?; + + // `ParserFs::collect_classes_single_output` already deduplicates class tokens. + all_spells.extend(spells); + } + + let css = self + .css_builder + .combine_spells_to_optimized_css_string(&all_spells)?; + + Self::create_output_directory_if_needed(&bundle_output_full_path)?; + fs::write(&bundle_output_full_path, css)?; + outputs.push(bundle_output_full_path); + } else { + let mut out_paths = Vec::new(); + parser.for_each_classes_multiple_output( + &project.input_paths, + &project_output_dir_path, + |output_file_path, source_path, classes| { + let source = Arc::new(SourceFile::new_path_only( + Some(source_path.clone()), + source_path.to_string_lossy().to_string(), + )); + let spells = Spell::generate_spells_from_classes( + classes, + &self.config.shared_spells, + &self.config.scrolls, + Some(source), + )?; + + let css = self + .css_builder + .combine_spells_to_optimized_css_string(&spells)?; + Self::create_output_directory_if_needed(&output_file_path)?; + fs::write(&output_file_path, css)?; + out_paths.push(output_file_path); + Ok(()) + }, + )?; + outputs.extend(out_paths); + } + + Ok(outputs) + } + + fn jobs_from_env() -> Result { + match env::var("GRIMOIRE_CSS_JOBS") { + Ok(v) => Self::parse_jobs(&v).map(Self::cap_jobs_to_machine), + Err(env::VarError::NotPresent) => Ok(1), + Err(e) => Err(GrimoireCssError::InvalidInput(format!( + "Failed to read GRIMOIRE_CSS_JOBS: {e}" + ))), + } + } + + fn parse_jobs(raw: &str) -> Result { + let trimmed = raw.trim(); + let jobs: usize = trimmed.parse().map_err(|_| { + GrimoireCssError::InvalidInput(format!( + "Invalid GRIMOIRE_CSS_JOBS value '{trimmed}': expected a positive integer" + )) + })?; + + if jobs == 0 { + return Err(GrimoireCssError::InvalidInput( + "GRIMOIRE_CSS_JOBS must be >= 1".to_string(), + )); + } + + Ok(jobs) + } + + fn cap_jobs_to_machine(requested: usize) -> usize { + let max = thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(1); + requested.clamp(1, max) + } } #[cfg(test)] mod tests { use super::*; + use crate::core::ConfigFsCritical; use std::path::Path; struct MockOptimizer; @@ -525,28 +618,29 @@ mod tests { let optimizer = MockOptimizer; let builder = CssBuilderFs::new(&config, current_dir, &optimizer).unwrap(); + let spell = Spell::new( + "display=grid", + &config.shared_spells, + &config.scrolls, + (0, 0), + None, + ) + .unwrap() + .unwrap(); + let build_info = BuildInfo { file_path: PathBuf::from("test_output.css"), - spells: vec![Spell { - file_path: None, - span: (0, 0), - source: None, - raw_spell: "d=grid".to_string(), - component: "display".to_string(), - component_target: "grid".to_string(), - effects: String::new(), - area: String::new(), - focus: String::new(), - with_template: false, - scroll_spells: None, - }], + spells: vec![spell], }; let result = builder.compile_css(&[build_info]); assert!(result.is_ok()); let compiled_css = result.unwrap(); - assert_eq!(compiled_css[0].1, ".d\\=grid{display:grid;}_optimized"); + assert_eq!( + compiled_css[0].1, + ".display\\=grid{display:grid;}_optimized" + ); } #[test] @@ -556,37 +650,38 @@ mod tests { let optimizer = MockOptimizer; let builder = CssBuilderFs::new(&config, current_dir, &optimizer).unwrap(); - let spells = vec![Spell { - file_path: None, - span: (0, 0), - source: None, - raw_spell: "d=grid".to_string(), - component: "display".to_string(), - component_target: "grid".to_string(), - effects: String::new(), - area: String::new(), - focus: String::new(), - with_template: false, - scroll_spells: None, - }]; - - let result = builder.css_builder.combine_spells_to_css(&spells); + let spell = Spell::new( + "display=grid", + &config.shared_spells, + &config.scrolls, + (0, 0), + None, + ) + .unwrap() + .unwrap(); + + let spells = vec![spell]; + + let result = builder.css_builder.combine_spells_to_css_string(&spells); assert!(result.is_ok()); let assembled_css = result.unwrap(); - assert_eq!(assembled_css[0], ".d\\=grid{display:grid;}"); + assert_eq!(assembled_css, ".display\\=grid{display:grid;}"); } #[test] fn test_cssbuilder_write_compiled_css() { let file_path = PathBuf::from("test_output.css"); - let css = vec![(file_path.clone(), ".d\\=grid{display:grid;}".to_string())]; + let css = vec![( + file_path.clone(), + ".display\\=grid{display:grid;}".to_string(), + )]; let result = CssBuilderFs::write_compiled_css(&css); assert!(result.is_ok()); let written_content = std::fs::read_to_string(&file_path).unwrap(); - assert_eq!(written_content, ".d\\=grid{display:grid;}"); + assert_eq!(written_content, ".display\\=grid{display:grid;}"); std::fs::remove_file(file_path).unwrap(); } @@ -598,11 +693,56 @@ mod tests { let optimizer = MockOptimizer; let builder = CssBuilderFs::new(&config, current_dir, &optimizer).unwrap(); - let raw_css = ".d\\=grid{display:grid;}"; + let raw_css = ".display\\=grid{display:grid;}"; let result = builder.css_builder.optimize_css(raw_css); assert!(result.is_ok()); let optimized_css = result.unwrap(); - assert_eq!(optimized_css, ".d\\=grid{display:grid;}_optimized"); + assert_eq!(optimized_css, ".display\\=grid{display:grid;}_optimized"); + } + + #[test] + fn test_compose_extra_css_is_raw_not_optimized() { + let config = create_test_config(); + let current_dir = Path::new("."); + let optimizer = MockOptimizer; + let builder = CssBuilderFs::new(&config, current_dir, &optimizer).unwrap(); + + let raw = builder + .compose_extra_css(&["display=grid".to_string()]) + .unwrap(); + // compose_extra_css returns raw CSS; optimization is the caller's responsibility. + assert_eq!(raw, ".display\\=grid{display:grid;}"); + } + + #[test] + fn test_compile_critical_css_shares_payload_across_files() { + let mut config = create_test_config(); + config.critical = Some(vec![ConfigFsCritical { + file_to_inline_paths: vec!["a.html".to_string(), "b.html".to_string()], + styles: Some(vec!["display=grid".to_string()]), + css_custom_properties: None, + }]); + + let current_dir = Path::new("."); + let optimizer = MockOptimizer; + let builder = CssBuilderFs::new(&config, current_dir, &optimizer).unwrap(); + + let compiled = builder.compile_critical_css().unwrap().unwrap(); + assert_eq!(compiled.len(), 2); + assert!(Arc::ptr_eq(&compiled[0].1, &compiled[1].1)); + assert_eq!( + compiled[0].1.as_ref(), + ".display\\=grid{display:grid;}_optimized" + ); + } + + #[test] + fn test_parse_jobs_defaults_and_validation() { + assert_eq!(CssBuilderFs::parse_jobs("1").unwrap(), 1); + assert_eq!(CssBuilderFs::parse_jobs(" 4 ").unwrap(), 4); + assert!(CssBuilderFs::parse_jobs("0").is_err()); + assert!(CssBuilderFs::parse_jobs("-1").is_err()); + assert!(CssBuilderFs::parse_jobs("abc").is_err()); } } diff --git a/src/core/css_builder/css_builder_in_memory.rs b/src/core/css_builder/css_builder_in_memory.rs index e488fb3..0ce73c1 100644 --- a/src/core/css_builder/css_builder_in_memory.rs +++ b/src/core/css_builder/css_builder_in_memory.rs @@ -72,14 +72,14 @@ impl<'a> CssBuilderInMemory<'a> { class_names, &HashSet::new(), &self.config.scrolls, - None, Some(source), )?; // Combine spells into CSS - let assembled_spells = self.css_builder.combine_spells_to_css(&spells)?; - let raw_css = assembled_spells.join(""); - let css = self.css_builder.optimize_css(&raw_css)?; + // Avoid validate() + optimize() double-parsing for the common success path. + let css = self + .css_builder + .combine_spells_to_optimized_css_string(&spells)?; results.push(CompiledCssInMemory { name: project.name.clone(), diff --git a/src/core/css_generator/css_generator_base.rs b/src/core/css_generator/css_generator_base.rs index cdc4c40..9baf817 100644 --- a/src/core/css_generator/css_generator_base.rs +++ b/src/core/css_generator/css_generator_base.rs @@ -153,13 +153,13 @@ Fix options:\n\ let css_class_name = self .generate_css_class_name( &spell.raw_spell, - &spell.effects, - &spell.focus, + spell.effects(), + spell.focus(), spell.with_template, ) .map_err(|e| self.create_compile_error(spell, e))?; - let component_str = spell.component.as_str(); + let component_str = spell.component(); // match component and get css property let css_property: Option<&str> = if component_str.starts_with("--") { @@ -173,7 +173,7 @@ Fix options:\n\ Some(css_property) => { // adapt target let adapted_target = self - .adapt_targets(&spell.component_target, self.variables) + .adapt_targets(spell.component_target(), self.variables) .map_err(|e| self.create_compile_error(spell, e))?; // generate base css without any media queries (except for the mrs function) let (base_css, additional_css) = self @@ -184,9 +184,9 @@ Fix options:\n\ ) .map_err(|e| self.create_compile_error(spell, e))?; - if !spell.area.is_empty() { + if !spell.area().is_empty() { return Ok(Some(( - self.wrap_base_css_with_media_query(&spell.area, &base_css), + self.wrap_base_css_with_media_query(spell.area(), &base_css), css_class_name, additional_css, ))); @@ -1194,19 +1194,12 @@ mod tests { let config = ConfigFs::default(); let generator = CssGenerator::new(&config.variables, &config.custom_animations).unwrap(); - let spell = Spell { - file_path: None, - span: (0, 0), - source: None, - raw_spell: "bg-c=pink".to_string(), - component: "bg-c".to_string(), - component_target: "pink".to_string(), - effects: "".to_string(), - area: "".to_string(), - focus: "".to_string(), - with_template: false, - scroll_spells: None, - }; + let shared_spells = std::collections::HashSet::new(); + let scrolls: Option>> = None; + + let spell = Spell::new("bg-c=pink", &shared_spells, &scrolls, (0, 0), None) + .unwrap() + .unwrap(); let result = generator.generate_css(&spell); @@ -1222,19 +1215,15 @@ mod tests { // --- COMPLEX --- - let spell_complex = Spell { - file_path: None, - span: (0, 0), - source: None, - raw_spell: "{[data-theme='light']_p}font-sz=mrs(14px_16px_380px_800px)".to_string(), - component: "font-sz".to_string(), - component_target: "mrs(14px_16px_380px_800px)".to_string(), - effects: "".to_string(), - area: "".to_string(), - focus: "[data-theme='light']_p".to_string(), - with_template: true, - scroll_spells: None, - }; + let spell_complex = Spell::new( + "{[data-theme='light']_p}font-sz=mrs(14px_16px_380px_800px)", + &shared_spells, + &scrolls, + (0, 0), + None, + ) + .unwrap() + .unwrap(); let result = generator.generate_css(&spell_complex); @@ -1248,7 +1237,7 @@ mod tests { assert_eq!( css, - r".g\!\{\[data-theme\=\'light\'\]\_p\}font-sz\=mrs\(14px\_16px\_380px\_800px\)\;[data-theme='light'] p{font-size:14px;}@media screen and (min-width: 380px) {.g\!\{\[data-theme\=\'light\'\]\_p\}font-sz\=mrs\(14px\_16px\_380px\_800px\)\;[data-theme='light'] p{font-size: calc(14px + 2 * ((100vw - 380px) / 420));}}@media screen and (min-width: 800px) {.g\!\{\[data-theme\=\'light\'\]\_p\}font-sz\=mrs\(14px\_16px\_380px\_800px\)\;[data-theme='light'] p{font-size: 16px;}}" + r".\{\[data-theme\=\'light\'\]\_p\}font-sz\=mrs\(14px\_16px\_380px\_800px\)[data-theme='light'] p{font-size:14px;}@media screen and (min-width: 380px) {.\{\[data-theme\=\'light\'\]\_p\}font-sz\=mrs\(14px\_16px\_380px\_800px\)[data-theme='light'] p{font-size: calc(14px + 2 * ((100vw - 380px) / 420));}}@media screen and (min-width: 800px) {.\{\[data-theme\=\'light\'\]\_p\}font-sz\=mrs\(14px\_16px\_380px\_800px\)[data-theme='light'] p{font-size: 16px;}}" ); } diff --git a/src/core/parser/parser_base.rs b/src/core/parser/parser_base.rs index ad14ccb..2ed611c 100644 --- a/src/core/parser/parser_base.rs +++ b/src/core/parser/parser_base.rs @@ -141,6 +141,15 @@ impl Parser { let start = base_offset + part_start; let length = part.len(); + // For regular `class` / `className` tokens we can check the HashSet + // by `&str` first to avoid allocating `String` for duplicates. + if !matches!(collection_type, CollectionType::CurlyClass) + && !part.is_empty() + && seen_class_names.contains(part) + { + continue; + } + let mut class_string = part.to_string(); if matches!(collection_type, CollectionType::CurlyClass) { diff --git a/src/core/parser/parser_fs.rs b/src/core/parser/parser_fs.rs index 639f736..7537198 100644 --- a/src/core/parser/parser_fs.rs +++ b/src/core/parser/parser_fs.rs @@ -13,9 +13,10 @@ use std::{ type Span = (usize, usize); type ClassWithSpan = (String, Span); +type ClassesWithSpans = Vec; -type SingleOutputFileResult = (PathBuf, String, Vec); -type MultiOutputFileResult = (PathBuf, PathBuf, String, Vec); +type SingleOutputFileClasses = (PathBuf, ClassesWithSpans); +type MultipleOutputFileClasses = (PathBuf, PathBuf, ClassesWithSpans); /// `ParserFs` extends the base `Parser` with filesystem-specific functionality. /// It handles file reading, directory traversal, and path resolution. @@ -45,7 +46,7 @@ impl ParserFs { /// /// # Returns /// - /// A vector of tuples containing file path, file content, and found classes with spans. + /// A vector of tuples containing file path and found classes with spans. /// /// # Errors /// @@ -53,7 +54,7 @@ impl ParserFs { pub fn collect_classes_single_output( &self, input_paths: &Vec, - ) -> Result, GrimoireCssError> { + ) -> Result, GrimoireCssError> { let mut results = Vec::new(); let mut seen_class_names: HashSet = HashSet::new(); @@ -74,16 +75,17 @@ impl ParserFs { /// /// # Returns /// - /// A vector of tuples: (OutputCssPath, InputSourcePath, InputSourceContent, ClassesWithSpans). + /// A vector of tuples: (OutputCssPath, InputSourcePath, ClassesWithSpans). /// /// # Errors /// /// Returns a `GrimoireCSSError` if any file or directory cannot be processed. + #[allow(dead_code)] pub fn collect_classes_multiple_output( &self, input_paths: &Vec, output_dir_path: &Path, - ) -> Result, GrimoireCssError> { + ) -> Result, GrimoireCssError> { let mut res = Vec::new(); for input_path_string in input_paths { @@ -113,7 +115,7 @@ impl ParserFs { return Err(e.with_source(src)); } - res.push((bundle_output_full_path, path, file_content, class_names)); + res.push((bundle_output_full_path, path, class_names)); } else if path.is_dir() { let entries = &self.get_sorted_directory_entries(&path)?; @@ -138,6 +140,75 @@ impl ParserFs { Ok(res) } + /// Streaming variant of `collect_classes_multiple_output`. + /// + /// Calls `visitor` once per input file instead of building a large in-memory vector. + pub fn for_each_classes_multiple_output( + &self, + input_paths: &Vec, + output_dir_path: &Path, + mut visitor: F, + ) -> Result<(), GrimoireCssError> + where + F: FnMut(PathBuf, PathBuf, ClassesWithSpans) -> Result<(), GrimoireCssError>, + { + for input_path_string in input_paths { + let path = self.current_dir.join(input_path_string); + self.visit_classes_multiple_output_path(&path, output_dir_path, &mut visitor)?; + } + + Ok(()) + } + + fn visit_classes_multiple_output_path( + &self, + path: &Path, + output_dir_path: &Path, + visitor: &mut F, + ) -> Result<(), GrimoireCssError> + where + F: FnMut(PathBuf, PathBuf, ClassesWithSpans) -> Result<(), GrimoireCssError>, + { + if path.is_file() { + let mut class_names = Vec::new(); + let mut seen_class_names: HashSet = HashSet::new(); + + let output_file_path = path.with_extension("css"); + let bundle_output_full_path = + output_dir_path.join(output_file_path.file_name().ok_or_else(|| { + GrimoireCssError::InvalidPath(output_file_path.to_string_lossy().into()) + })?); + + let file_content = fs::read_to_string(path)?; + if let Err(e) = self.base_parser.collect_candidates( + &file_content, + &mut class_names, + &mut seen_class_names, + ) { + let src = Arc::new(SourceFile::new( + Some(path.to_path_buf()), + path.to_string_lossy().to_string(), + file_content.clone(), + )); + return Err(e.with_source(src)); + } + + visitor(bundle_output_full_path, path.to_path_buf(), class_names)?; + return Ok(()); + } + + if path.is_dir() { + let entries = self.get_sorted_directory_entries(path)?; + for entry in entries { + self.visit_classes_multiple_output_path(&entry, output_dir_path, visitor)?; + } + return Ok(()); + } + + add_message(format!("Invalid path: {}", path.display())); + Ok(()) + } + /// Recursively collects CSS class names or templated spells from a given file or directory path. /// /// # Arguments @@ -152,7 +223,7 @@ impl ParserFs { fn collect_spells_from_path( &self, path: &Path, - results: &mut Vec, + results: &mut Vec, seen_class_names: &mut HashSet, ) -> Result<(), GrimoireCssError> { if path.is_file() { @@ -173,7 +244,7 @@ impl ParserFs { } if !class_names.is_empty() { - results.push((path.to_path_buf(), file_content, class_names)); + results.push((path.to_path_buf(), class_names)); } } else if path.is_dir() { let entries = &self.get_sorted_directory_entries(path)?; @@ -187,7 +258,6 @@ impl ParserFs { Ok(()) } - /// Retrieves and sorts all entries in a given directory. /// /// # Arguments @@ -246,7 +316,7 @@ mod tests { assert!(result.is_ok()); let results = result.unwrap(); assert_eq!(results.len(), 1); - let (path, _, classes) = &results[0]; + let (path, classes) = &results[0]; assert_eq!(path, &test_file); assert_eq!(classes.len(), 3); @@ -282,12 +352,12 @@ mod tests { assert_eq!(outputs.len(), 2); // Check first file output - let (_, _, _, classes1) = &outputs[0]; + let (_, _, classes1) = &outputs[0]; assert_eq!(classes1.len(), 1); assert_eq!(classes1[0].0, "file1-class"); // Check second file output - let (_, _, _, classes2) = &outputs[1]; + let (_, _, classes2) = &outputs[1]; assert_eq!(classes2.len(), 1); assert_eq!(classes2[0].0, "file2-class"); } @@ -311,7 +381,7 @@ mod tests { assert!(result.is_ok()); let results = result.unwrap(); assert_eq!(results.len(), 1); - let (_, _, classes) = &results[0]; + let (_, classes) = &results[0]; assert_eq!(classes.len(), 1); assert_eq!(classes[0].0, "nested-class"); } @@ -334,4 +404,26 @@ mod tests { assert!(result.is_ok()); assert_eq!(result.unwrap().len(), 0); } + + #[test] + fn test_collect_classes_single_output_dedups_across_files() { + let temp_dir = tempdir().unwrap(); + let a = temp_dir.path().join("a.html"); + let b = temp_dir.path().join("b.html"); + + // Same token appears in both files. + fs::write(&a, r#"
"#).unwrap(); + fs::write(&b, r#"
"#).unwrap(); + + let parser = ParserFs::new(temp_dir.path()); + let input_paths = vec!["a.html".to_string(), "b.html".to_string()]; + + let results = parser.collect_classes_single_output(&input_paths).unwrap(); + + // Dedup is global across all inputs in single-output mode. + assert_eq!(results.len(), 1); + assert_eq!(results[0].0, a); + assert_eq!(results[0].1.len(), 1); + assert_eq!(results[0].1[0].0, "h=10px".to_string()); + } } diff --git a/src/core/source_file.rs b/src/core/source_file.rs index 473f35b..69b6182 100644 --- a/src/core/source_file.rs +++ b/src/core/source_file.rs @@ -4,7 +4,7 @@ use std::{path::PathBuf, sync::Arc}; pub struct SourceFile { pub name: String, pub path: Option, - pub content: Arc, + pub content: Option>, } impl SourceFile { @@ -12,7 +12,15 @@ impl SourceFile { Self { name, path, - content: Arc::new(content), + content: Some(Arc::new(content)), + } + } + + pub fn new_path_only(path: Option, name: String) -> Self { + Self { + name, + path, + content: None, } } } diff --git a/src/core/spell.rs b/src/core/spell.rs index 32180f4..70c7dc8 100644 --- a/src/core/spell.rs +++ b/src/core/spell.rs @@ -27,36 +27,34 @@ use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; -use std::path::PathBuf; use std::sync::Arc; use super::{ GrimoireCssError, component::get_css_property, source_file::SourceFile, spell_value_validator, }; +#[derive(Debug, Clone)] +struct SpellParts { + area: std::ops::Range, + focus: std::ops::Range, + effects: std::ops::Range, + component: std::ops::Range, + component_target: std::ops::Range, +} + #[derive(Debug, Clone)] pub struct Spell { pub raw_spell: String, - pub component: String, - pub component_target: String, - pub effects: String, - pub area: String, - pub focus: String, pub with_template: bool, pub scroll_spells: Option>, pub span: (usize, usize), - pub file_path: Option, pub source: Option>, + parts: Option, } impl PartialEq for Spell { fn eq(&self, other: &Self) -> bool { self.raw_spell == other.raw_spell - && self.component == other.component - && self.component_target == other.component_target - && self.effects == other.effects - && self.area == other.area - && self.focus == other.focus && self.with_template == other.with_template && self.scroll_spells == other.scroll_spells } @@ -67,24 +65,53 @@ impl Eq for Spell {} impl Hash for Spell { fn hash(&self, state: &mut H) { self.raw_spell.hash(state); - self.component.hash(state); - self.component_target.hash(state); - self.effects.hash(state); - self.area.hash(state); - self.focus.hash(state); self.with_template.hash(state); self.scroll_spells.hash(state); } } impl Spell { + pub fn area(&self) -> &str { + self.parts + .as_ref() + .map(|p| &self.raw_spell[p.area.clone()]) + .unwrap_or("") + } + + pub fn focus(&self) -> &str { + self.parts + .as_ref() + .map(|p| &self.raw_spell[p.focus.clone()]) + .unwrap_or("") + } + + pub fn effects(&self) -> &str { + self.parts + .as_ref() + .map(|p| &self.raw_spell[p.effects.clone()]) + .unwrap_or("") + } + + pub fn component(&self) -> &str { + self.parts + .as_ref() + .map(|p| &self.raw_spell[p.component.clone()]) + .unwrap_or("") + } + + pub fn component_target(&self) -> &str { + self.parts + .as_ref() + .map(|p| &self.raw_spell[p.component_target.clone()]) + .unwrap_or("") + } + /// Example input: "md__{_>_p}hover:display=none" pub fn new( raw_spell: &str, shared_spells: &HashSet, scrolls: &Option>>, span: (usize, usize), - file_path: Option, source: Option>, ) -> Result, GrimoireCssError> { let with_template = Self::check_for_template(raw_spell); @@ -102,51 +129,67 @@ impl Spell { .filter(|s| !s.is_empty()) .collect(); + // Template spell: keep outer spell and parse inner spells. if with_template && !raw_spell_split.is_empty() { let mut scroll_spells: Vec = Vec::new(); for rs in raw_spell_split { - if let Some(spell) = Spell::new( - rs, - shared_spells, - scrolls, - span, - file_path.clone(), - source.clone(), - )? { + if let Some(spell) = Spell::new(rs, shared_spells, scrolls, span, source.clone())? { scroll_spells.push(spell); } } return Ok(Some(Spell { raw_spell: raw_spell_cleaned.to_string(), - component: String::new(), - component_target: String::new(), - effects: String::new(), - area: String::new(), - focus: String::new(), with_template, scroll_spells: Some(scroll_spells), span, - file_path, source, + parts: None, })); } - // Split the input string by "__" to separate the area (screen size) and the rest - let (area, rest) = raw_spell_cleaned - .split_once("__") - .unwrap_or(("", raw_spell_cleaned)); + let raw = raw_spell_cleaned.to_string(); - // Split the raw spell by "}" to get the focus and the rest - let (focus, rest) = rest - .split_once('}') - .map_or(("", rest), |(f, r)| (f.strip_prefix('{').unwrap_or(f), r)); + // Parse into byte ranges within `raw`. + let mut area_range = 0..0; + let mut focus_range = 0..0; + let mut effects_range = 0..0; - // Split the rest by ":" to get the effects (pseudo-class) and the rest - let (effects, rest) = rest.split_once(':').unwrap_or(("", rest)); + let mut rest_start = 0usize; + if let Some(pos) = raw.find("__") { + area_range = 0..pos; + rest_start = pos + 2; + } - // Split the rest by "=" to separate the component (property) and component_target (value) - if let Some((component, component_target)) = rest.split_once("=") { + let mut after_focus_start = rest_start; + if rest_start < raw.len() + && let Some(close_rel) = raw[rest_start..].find('}') + { + let focus_part_start = if raw.as_bytes().get(rest_start) == Some(&b'{') { + rest_start + 1 + } else { + rest_start + }; + focus_range = focus_part_start..(rest_start + close_rel); + after_focus_start = rest_start + close_rel + 1; + } + + let mut after_effects_start = after_focus_start; + if after_focus_start < raw.len() + && let Some(colon_rel) = raw[after_focus_start..].find(':') + { + effects_range = after_focus_start..(after_focus_start + colon_rel); + after_effects_start = after_focus_start + colon_rel + 1; + } + + // component=target + if after_effects_start <= raw.len() + && let Some(eq_rel) = raw[after_effects_start..].find('=') + { + let component_range = after_effects_start..(after_effects_start + eq_rel); + let component_target_range = (after_effects_start + eq_rel + 1)..raw.len(); + + let component_target = &raw[component_target_range.clone()]; if let Some(err) = spell_value_validator::validate_component_target(component_target) { let message = match err { spell_value_validator::SpellValueValidationError::UnexpectedClosingParen => { @@ -182,99 +225,105 @@ Use '_' inside spell values to represent spaces." return Err(GrimoireCssError::InvalidInput(message)); } + let parts = SpellParts { + area: area_range, + focus: focus_range, + effects: effects_range, + component: component_range.clone(), + component_target: component_target_range.clone(), + }; + let mut spell = Spell { - raw_spell: raw_spell_cleaned.to_string(), - component: component.to_string(), - component_target: component_target.to_string(), - effects: effects.to_string(), - area: area.to_string(), - focus: focus.to_string(), + raw_spell: raw, with_template, scroll_spells: None, span, - file_path: file_path.clone(), source: source.clone(), + parts: Some(parts), }; - if let Some(raw_scroll_spells) = - Self::check_raw_scroll_spells(&spell.component, scrolls) - { + let component = spell.component(); + + if let Some(raw_scroll_spells) = Self::check_raw_scroll_spells(component, scrolls) { spell.scroll_spells = Self::parse_scroll( component, raw_scroll_spells, - &spell.component_target, + spell.component_target(), shared_spells, scrolls, span, - &file_path, - source.clone(), + source, )?; - } else if !spell.component.starts_with("--") - && get_css_property(&spell.component).is_none() - { - let message = format!("Unknown component or scroll: '{}'", spell.component); + } else if !component.starts_with("--") && get_css_property(component).is_none() { + let message = format!("Unknown component or scroll: '{component}'"); if let Some(src) = &source { return Err(GrimoireCssError::InvalidSpellFormat { - message, - span, - label: "Error in this spell".to_string(), - help: Some( - "Check that the component name exists (built-in CSS property alias) or that the scroll is defined in config.scrolls." - .to_string(), - ), - source_file: Some(src.clone()), - }); + message, + span, + label: "Error in this spell".to_string(), + help: Some( + "Check that the component name exists (built-in CSS property alias) or that the scroll is defined in config.scrolls." + .to_string(), + ), + source_file: Some(src.clone()), + }); } else { return Err(GrimoireCssError::InvalidInput(message)); } } return Ok(Some(spell)); - } else if let Some(raw_scroll_spells) = Self::check_raw_scroll_spells(rest, scrolls) { - return Ok(Some(Spell { - raw_spell: raw_spell_cleaned.to_string(), - component: rest.to_string(), - component_target: String::new(), - effects: effects.to_string(), - area: area.to_string(), - focus: focus.to_string(), + } + + // scroll (no '=') + if after_effects_start <= raw.len() + && let Some(raw_scroll_spells) = + Self::check_raw_scroll_spells(&raw[after_effects_start..], scrolls) + { + let component_range = after_effects_start..raw.len(); + let parts = SpellParts { + area: area_range, + focus: focus_range, + effects: effects_range, + component: component_range.clone(), + component_target: 0..0, + }; + + let mut spell = Spell { + raw_spell: raw, with_template, - scroll_spells: Self::parse_scroll( - rest, - raw_scroll_spells, - "", - shared_spells, - scrolls, - span, - &file_path, - source.clone(), - )?, + scroll_spells: None, + span, + source: source.clone(), + parts: Some(parts), + }; + + let component = spell.component(); + spell.scroll_spells = Self::parse_scroll( + component, + raw_scroll_spells, + "", + shared_spells, + scrolls, span, - file_path, source, - })); + )?; + + return Ok(Some(spell)); } Ok(None) // Return None if format is invalid } - fn check_for_template(class_name: &str) -> bool { - class_name.starts_with("g!") && class_name.ends_with(";") + fn check_for_template(raw_spell: &str) -> bool { + raw_spell.starts_with("g!") && raw_spell.ends_with(';') } fn check_raw_scroll_spells<'a>( - spell_component: &'a str, + scroll_name: &str, scrolls: &'a Option>>, ) -> Option<&'a Vec> { - if get_css_property(spell_component).is_some() { - return None; - } - - if let Some(scrolls) = scrolls { - return scrolls.get(spell_component); - }; - - None + scrolls.as_ref()?.get(scroll_name) } #[allow(clippy::too_many_arguments)] @@ -285,7 +334,6 @@ Use '_' inside spell values to represent spaces." shared_spells: &HashSet, scrolls: &Option>>, span: (usize, usize), - file_path: &Option, source: Option>, ) -> Result>, GrimoireCssError> { if raw_scroll_spells.is_empty() { @@ -304,7 +352,7 @@ Use '_' inside spell values to represent spaces." for raw_spell in raw_scroll_spells.iter() { if raw_spell.contains("=$") { - if count_of_used_variables > scroll_variables.len() - 1 { + if count_of_used_variables > scroll_variables.len().saturating_sub(1) { break; } @@ -318,21 +366,15 @@ Use '_' inside spell values to represent spaces." shared_spells, scrolls, span, - file_path.clone(), source.clone(), ) { spells.push(spell); } count_of_used_variables += 1; - } else if let Ok(Some(spell)) = Spell::new( - raw_spell, - shared_spells, - scrolls, - span, - file_path.clone(), - source.clone(), - ) { + } else if let Ok(Some(spell)) = + Spell::new(raw_spell, shared_spells, scrolls, span, source.clone()) + { spells.push(spell); } } @@ -370,21 +412,13 @@ Example: complex-card=arg1_arg2_arg3" css_classes: Vec<(String, (usize, usize))>, shared_spells: &HashSet, scrolls: &Option>>, - file_path: Option, source: Option>, ) -> Result, GrimoireCssError> { let mut spells = Vec::with_capacity(css_classes.len()); for (cs, span) in css_classes { if !shared_spells.contains(&cs) - && let Some(spell) = Spell::new( - &cs, - shared_spells, - scrolls, - span, - file_path.clone(), - source.clone(), - )? + && let Some(spell) = Spell::new(&cs, shared_spells, scrolls, span, source.clone())? { spells.push(spell); } @@ -406,17 +440,17 @@ mod tests { let shared_spells = HashSet::new(); let scrolls: Option>> = None; let raw = "g!color=red--display=flex;"; - let spell = Spell::new(raw, &shared_spells, &scrolls, (0, 0), None, None) + let spell = Spell::new(raw, &shared_spells, &scrolls, (0, 0), None) .expect("parse ok") .expect("not None"); assert!(spell.with_template); assert!(spell.scroll_spells.is_some()); let spells = spell.scroll_spells.as_ref().unwrap(); assert_eq!(spells.len(), 2); - assert_eq!(spells[0].component, "color"); - assert_eq!(spells[0].component_target, "red"); - assert_eq!(spells[1].component, "display"); - assert_eq!(spells[1].component_target, "flex"); + assert_eq!(spells[0].component(), "color"); + assert_eq!(spells[0].component_target(), "red"); + assert_eq!(spells[1].component(), "display"); + assert_eq!(spells[1].component_target(), "flex"); } #[test] @@ -430,7 +464,6 @@ mod tests { &shared_spells, &scrolls, (12, 3), - None, Some(Arc::new(SourceFile::new( None, "test".to_string(), diff --git a/src/core/spell_value_validator.rs b/src/core/spell_value_validator.rs index 8ed0027..27c48eb 100644 --- a/src/core/spell_value_validator.rs +++ b/src/core/spell_value_validator.rs @@ -5,12 +5,6 @@ pub enum SpellValueValidationError { } pub fn validate_component_target(component_target: &str) -> Option { - // Intentionally minimal and cheap validation to surface common HTML class tokenization mistakes - // (e.g. h=calc(100vh - 50px) becomes h=calc(100vh). - // - // We only treat parentheses as syntax when NOT inside CSS string literals. - // In CSS, string literals are only single/double quotes. - let mut depth: i32 = 0; let mut in_single_quote = false; let mut in_double_quote = false; diff --git a/src/infrastructure/diagnostics.rs b/src/infrastructure/diagnostics.rs index 0620441..119a46f 100644 --- a/src/infrastructure/diagnostics.rs +++ b/src/infrastructure/diagnostics.rs @@ -4,7 +4,15 @@ use std::sync::Arc; use thiserror::Error; fn named_source_from(source: &Arc) -> miette::NamedSource { - miette::NamedSource::new(source.name.clone(), (*source.content).clone()) + let content = if let Some(content) = &source.content { + (**content).clone() + } else if let Some(path) = &source.path { + std::fs::read_to_string(path).unwrap_or_default() + } else { + String::new() + }; + + miette::NamedSource::new(source.name.clone(), content) } #[derive(Debug, Error)] diff --git a/src/infrastructure/lightning_css_optimizer.rs b/src/infrastructure/lightning_css_optimizer.rs index 775dc46..7b1b5b5 100644 --- a/src/infrastructure/lightning_css_optimizer.rs +++ b/src/infrastructure/lightning_css_optimizer.rs @@ -7,7 +7,7 @@ use lightningcss::{ stylesheet::{MinifyOptions, ParserOptions, StyleSheet}, targets::{Browsers, Targets}, }; -use std::{env, fs, path::Path}; +use std::{fs, path::Path}; use crate::{ buffer::add_message, @@ -47,12 +47,6 @@ impl LightningCssOptimizer { add_message("Created missing '.browserslistrc' file with 'defaults'".to_string()); } - // SAFETY: We're setting an environment variable in a controlled manner. - // This is safe as long as no other threads are concurrently reading this variable. - unsafe { - env::set_var("BROWSERSLIST_CONFIG", &browserslist_config_path); - } - let content = fs::read_to_string(&browserslist_config_path) .expect("Failed to read '.browserslistrc' file"); diff --git a/src/lib.rs b/src/lib.rs index de7ceef..ed8339a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,10 +193,7 @@ pub fn start_as_cli(args: Vec) -> Result<(), GrimoireCssError> { print!("\r\x1b[2K{GRIMM_CURSED}\n"); println!(); - println!( - "{} Something went wrong...", - style(" Cursed! ").white().on_red().bright() - ); + println!("{}", style(" Cursed! ").white().on_red().bright()); println!(); let diagnostic: GrimoireCssDiagnostic = (&e).into(); diff --git a/src/main.rs b/src/main.rs index ffe6611..59b33a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,13 @@ use grimoire_css_lib::start_as_cli; use std::env; +#[cfg(feature = "heap-profile")] +use dhat::Alloc; + +#[cfg(feature = "heap-profile")] +#[global_allocator] +static ALLOC: Alloc = Alloc; + /// The entry point for the Grimoire CSS system (CLI). /// /// This function: @@ -15,6 +22,9 @@ use std::env; /// logging, error styling, spinners, and time measurements. /// - If an error is encountered, it exits with a non-zero status code. fn main() { + #[cfg(feature = "heap-profile")] + let _profiler = dhat::Profiler::new_heap(); + let args: Vec = env::args().collect(); // By calling `start_as_cli`, we rely on the library's built-in logging,