├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets └── logo │ └── 128.png ├── rcss.pest ├── rcss └── example.rcss ├── src ├── compile.rs ├── error.rs ├── main.rs └── process_x │ ├── functions.rs │ ├── imports.rs │ ├── keyframes.rs │ ├── media_queries.rs │ ├── rule_normal.rs │ └── variables.rs └── styles ├── css └── components │ └── Atoms │ └── icon.module.css └── rcss ├── common.rcss └── components └── Atoms └── icon.module.rcss /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | rcss-css -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "android-tzdata" 16 | version = "0.1.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 19 | 20 | [[package]] 21 | name = "android_system_properties" 22 | version = "0.1.5" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 25 | dependencies = [ 26 | "libc", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys 0.59.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 73 | dependencies = [ 74 | "anstyle", 75 | "once_cell", 76 | "windows-sys 0.59.0", 77 | ] 78 | 79 | [[package]] 80 | name = "autocfg" 81 | version = "1.4.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 84 | 85 | [[package]] 86 | name = "bitflags" 87 | version = "1.3.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 90 | 91 | [[package]] 92 | name = "bitflags" 93 | version = "2.9.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 96 | 97 | [[package]] 98 | name = "block-buffer" 99 | version = "0.10.4" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 102 | dependencies = [ 103 | "generic-array", 104 | ] 105 | 106 | [[package]] 107 | name = "bumpalo" 108 | version = "3.17.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 111 | 112 | [[package]] 113 | name = "cc" 114 | version = "1.2.16" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" 117 | dependencies = [ 118 | "shlex", 119 | ] 120 | 121 | [[package]] 122 | name = "cfg-if" 123 | version = "1.0.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 126 | 127 | [[package]] 128 | name = "chrono" 129 | version = "0.4.40" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" 132 | dependencies = [ 133 | "android-tzdata", 134 | "iana-time-zone", 135 | "js-sys", 136 | "num-traits", 137 | "wasm-bindgen", 138 | "windows-link", 139 | ] 140 | 141 | [[package]] 142 | name = "clap" 143 | version = "4.5.32" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" 146 | dependencies = [ 147 | "clap_builder", 148 | "clap_derive", 149 | ] 150 | 151 | [[package]] 152 | name = "clap_builder" 153 | version = "4.5.32" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" 156 | dependencies = [ 157 | "anstream", 158 | "anstyle", 159 | "clap_lex", 160 | "strsim", 161 | ] 162 | 163 | [[package]] 164 | name = "clap_derive" 165 | version = "4.5.32" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 168 | dependencies = [ 169 | "heck", 170 | "proc-macro2", 171 | "quote", 172 | "syn", 173 | ] 174 | 175 | [[package]] 176 | name = "clap_lex" 177 | version = "0.7.4" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 180 | 181 | [[package]] 182 | name = "colorchoice" 183 | version = "1.0.3" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 186 | 187 | [[package]] 188 | name = "colored" 189 | version = "3.0.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" 192 | dependencies = [ 193 | "windows-sys 0.59.0", 194 | ] 195 | 196 | [[package]] 197 | name = "core-foundation-sys" 198 | version = "0.8.7" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 201 | 202 | [[package]] 203 | name = "cpufeatures" 204 | version = "0.2.17" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 207 | dependencies = [ 208 | "libc", 209 | ] 210 | 211 | [[package]] 212 | name = "crypto-common" 213 | version = "0.1.6" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 216 | dependencies = [ 217 | "generic-array", 218 | "typenum", 219 | ] 220 | 221 | [[package]] 222 | name = "digest" 223 | version = "0.10.7" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 226 | dependencies = [ 227 | "block-buffer", 228 | "crypto-common", 229 | ] 230 | 231 | [[package]] 232 | name = "filetime" 233 | version = "0.2.25" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" 236 | dependencies = [ 237 | "cfg-if", 238 | "libc", 239 | "libredox", 240 | "windows-sys 0.59.0", 241 | ] 242 | 243 | [[package]] 244 | name = "fsevent-sys" 245 | version = "4.1.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" 248 | dependencies = [ 249 | "libc", 250 | ] 251 | 252 | [[package]] 253 | name = "generic-array" 254 | version = "0.14.7" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 257 | dependencies = [ 258 | "typenum", 259 | "version_check", 260 | ] 261 | 262 | [[package]] 263 | name = "heck" 264 | version = "0.5.0" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 267 | 268 | [[package]] 269 | name = "iana-time-zone" 270 | version = "0.1.61" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 273 | dependencies = [ 274 | "android_system_properties", 275 | "core-foundation-sys", 276 | "iana-time-zone-haiku", 277 | "js-sys", 278 | "wasm-bindgen", 279 | "windows-core", 280 | ] 281 | 282 | [[package]] 283 | name = "iana-time-zone-haiku" 284 | version = "0.1.2" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 287 | dependencies = [ 288 | "cc", 289 | ] 290 | 291 | [[package]] 292 | name = "inotify" 293 | version = "0.11.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" 296 | dependencies = [ 297 | "bitflags 2.9.0", 298 | "inotify-sys", 299 | "libc", 300 | ] 301 | 302 | [[package]] 303 | name = "inotify-sys" 304 | version = "0.1.5" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 307 | dependencies = [ 308 | "libc", 309 | ] 310 | 311 | [[package]] 312 | name = "is_terminal_polyfill" 313 | version = "1.70.1" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 316 | 317 | [[package]] 318 | name = "js-sys" 319 | version = "0.3.77" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 322 | dependencies = [ 323 | "once_cell", 324 | "wasm-bindgen", 325 | ] 326 | 327 | [[package]] 328 | name = "kqueue" 329 | version = "1.0.8" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" 332 | dependencies = [ 333 | "kqueue-sys", 334 | "libc", 335 | ] 336 | 337 | [[package]] 338 | name = "kqueue-sys" 339 | version = "1.0.4" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" 342 | dependencies = [ 343 | "bitflags 1.3.2", 344 | "libc", 345 | ] 346 | 347 | [[package]] 348 | name = "libc" 349 | version = "0.2.171" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 352 | 353 | [[package]] 354 | name = "libredox" 355 | version = "0.1.3" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 358 | dependencies = [ 359 | "bitflags 2.9.0", 360 | "libc", 361 | "redox_syscall", 362 | ] 363 | 364 | [[package]] 365 | name = "log" 366 | version = "0.4.26" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 369 | 370 | [[package]] 371 | name = "memchr" 372 | version = "2.7.4" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 375 | 376 | [[package]] 377 | name = "mio" 378 | version = "1.0.3" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 381 | dependencies = [ 382 | "libc", 383 | "log", 384 | "wasi", 385 | "windows-sys 0.52.0", 386 | ] 387 | 388 | [[package]] 389 | name = "notify" 390 | version = "8.0.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" 393 | dependencies = [ 394 | "bitflags 2.9.0", 395 | "filetime", 396 | "fsevent-sys", 397 | "inotify", 398 | "kqueue", 399 | "libc", 400 | "log", 401 | "mio", 402 | "notify-types", 403 | "walkdir", 404 | "windows-sys 0.59.0", 405 | ] 406 | 407 | [[package]] 408 | name = "notify-types" 409 | version = "2.0.0" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" 412 | 413 | [[package]] 414 | name = "num-traits" 415 | version = "0.2.19" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 418 | dependencies = [ 419 | "autocfg", 420 | ] 421 | 422 | [[package]] 423 | name = "once_cell" 424 | version = "1.21.1" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" 427 | 428 | [[package]] 429 | name = "pathdiff" 430 | version = "0.2.3" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" 433 | 434 | [[package]] 435 | name = "pest" 436 | version = "2.7.15" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" 439 | dependencies = [ 440 | "memchr", 441 | "thiserror", 442 | "ucd-trie", 443 | ] 444 | 445 | [[package]] 446 | name = "pest_derive" 447 | version = "2.7.15" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" 450 | dependencies = [ 451 | "pest", 452 | "pest_generator", 453 | ] 454 | 455 | [[package]] 456 | name = "pest_generator" 457 | version = "2.7.15" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" 460 | dependencies = [ 461 | "pest", 462 | "pest_meta", 463 | "proc-macro2", 464 | "quote", 465 | "syn", 466 | ] 467 | 468 | [[package]] 469 | name = "pest_meta" 470 | version = "2.7.15" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" 473 | dependencies = [ 474 | "once_cell", 475 | "pest", 476 | "sha2", 477 | ] 478 | 479 | [[package]] 480 | name = "proc-macro2" 481 | version = "1.0.94" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 484 | dependencies = [ 485 | "unicode-ident", 486 | ] 487 | 488 | [[package]] 489 | name = "quote" 490 | version = "1.0.40" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 493 | dependencies = [ 494 | "proc-macro2", 495 | ] 496 | 497 | [[package]] 498 | name = "rcss-css" 499 | version = "0.2.2" 500 | dependencies = [ 501 | "chrono", 502 | "clap", 503 | "colored", 504 | "notify", 505 | "pathdiff", 506 | "pest", 507 | "pest_derive", 508 | "regex", 509 | "walkdir", 510 | ] 511 | 512 | [[package]] 513 | name = "redox_syscall" 514 | version = "0.5.10" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" 517 | dependencies = [ 518 | "bitflags 2.9.0", 519 | ] 520 | 521 | [[package]] 522 | name = "regex" 523 | version = "1.11.1" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 526 | dependencies = [ 527 | "aho-corasick", 528 | "memchr", 529 | "regex-automata", 530 | "regex-syntax", 531 | ] 532 | 533 | [[package]] 534 | name = "regex-automata" 535 | version = "0.4.9" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 538 | dependencies = [ 539 | "aho-corasick", 540 | "memchr", 541 | "regex-syntax", 542 | ] 543 | 544 | [[package]] 545 | name = "regex-syntax" 546 | version = "0.8.5" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 549 | 550 | [[package]] 551 | name = "rustversion" 552 | version = "1.0.20" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 555 | 556 | [[package]] 557 | name = "same-file" 558 | version = "1.0.6" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 561 | dependencies = [ 562 | "winapi-util", 563 | ] 564 | 565 | [[package]] 566 | name = "sha2" 567 | version = "0.10.8" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 570 | dependencies = [ 571 | "cfg-if", 572 | "cpufeatures", 573 | "digest", 574 | ] 575 | 576 | [[package]] 577 | name = "shlex" 578 | version = "1.3.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 581 | 582 | [[package]] 583 | name = "strsim" 584 | version = "0.11.1" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 587 | 588 | [[package]] 589 | name = "syn" 590 | version = "2.0.100" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 593 | dependencies = [ 594 | "proc-macro2", 595 | "quote", 596 | "unicode-ident", 597 | ] 598 | 599 | [[package]] 600 | name = "thiserror" 601 | version = "2.0.12" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 604 | dependencies = [ 605 | "thiserror-impl", 606 | ] 607 | 608 | [[package]] 609 | name = "thiserror-impl" 610 | version = "2.0.12" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 613 | dependencies = [ 614 | "proc-macro2", 615 | "quote", 616 | "syn", 617 | ] 618 | 619 | [[package]] 620 | name = "typenum" 621 | version = "1.18.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 624 | 625 | [[package]] 626 | name = "ucd-trie" 627 | version = "0.1.7" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" 630 | 631 | [[package]] 632 | name = "unicode-ident" 633 | version = "1.0.18" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 636 | 637 | [[package]] 638 | name = "utf8parse" 639 | version = "0.2.2" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 642 | 643 | [[package]] 644 | name = "version_check" 645 | version = "0.9.5" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 648 | 649 | [[package]] 650 | name = "walkdir" 651 | version = "2.5.0" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 654 | dependencies = [ 655 | "same-file", 656 | "winapi-util", 657 | ] 658 | 659 | [[package]] 660 | name = "wasi" 661 | version = "0.11.0+wasi-snapshot-preview1" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 664 | 665 | [[package]] 666 | name = "wasm-bindgen" 667 | version = "0.2.100" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 670 | dependencies = [ 671 | "cfg-if", 672 | "once_cell", 673 | "rustversion", 674 | "wasm-bindgen-macro", 675 | ] 676 | 677 | [[package]] 678 | name = "wasm-bindgen-backend" 679 | version = "0.2.100" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 682 | dependencies = [ 683 | "bumpalo", 684 | "log", 685 | "proc-macro2", 686 | "quote", 687 | "syn", 688 | "wasm-bindgen-shared", 689 | ] 690 | 691 | [[package]] 692 | name = "wasm-bindgen-macro" 693 | version = "0.2.100" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 696 | dependencies = [ 697 | "quote", 698 | "wasm-bindgen-macro-support", 699 | ] 700 | 701 | [[package]] 702 | name = "wasm-bindgen-macro-support" 703 | version = "0.2.100" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 706 | dependencies = [ 707 | "proc-macro2", 708 | "quote", 709 | "syn", 710 | "wasm-bindgen-backend", 711 | "wasm-bindgen-shared", 712 | ] 713 | 714 | [[package]] 715 | name = "wasm-bindgen-shared" 716 | version = "0.2.100" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 719 | dependencies = [ 720 | "unicode-ident", 721 | ] 722 | 723 | [[package]] 724 | name = "winapi-util" 725 | version = "0.1.9" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 728 | dependencies = [ 729 | "windows-sys 0.59.0", 730 | ] 731 | 732 | [[package]] 733 | name = "windows-core" 734 | version = "0.52.0" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 737 | dependencies = [ 738 | "windows-targets", 739 | ] 740 | 741 | [[package]] 742 | name = "windows-link" 743 | version = "0.1.1" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 746 | 747 | [[package]] 748 | name = "windows-sys" 749 | version = "0.52.0" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 752 | dependencies = [ 753 | "windows-targets", 754 | ] 755 | 756 | [[package]] 757 | name = "windows-sys" 758 | version = "0.59.0" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 761 | dependencies = [ 762 | "windows-targets", 763 | ] 764 | 765 | [[package]] 766 | name = "windows-targets" 767 | version = "0.52.6" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 770 | dependencies = [ 771 | "windows_aarch64_gnullvm", 772 | "windows_aarch64_msvc", 773 | "windows_i686_gnu", 774 | "windows_i686_gnullvm", 775 | "windows_i686_msvc", 776 | "windows_x86_64_gnu", 777 | "windows_x86_64_gnullvm", 778 | "windows_x86_64_msvc", 779 | ] 780 | 781 | [[package]] 782 | name = "windows_aarch64_gnullvm" 783 | version = "0.52.6" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 786 | 787 | [[package]] 788 | name = "windows_aarch64_msvc" 789 | version = "0.52.6" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 792 | 793 | [[package]] 794 | name = "windows_i686_gnu" 795 | version = "0.52.6" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 798 | 799 | [[package]] 800 | name = "windows_i686_gnullvm" 801 | version = "0.52.6" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 804 | 805 | [[package]] 806 | name = "windows_i686_msvc" 807 | version = "0.52.6" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 810 | 811 | [[package]] 812 | name = "windows_x86_64_gnu" 813 | version = "0.52.6" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 816 | 817 | [[package]] 818 | name = "windows_x86_64_gnullvm" 819 | version = "0.52.6" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 822 | 823 | [[package]] 824 | name = "windows_x86_64_msvc" 825 | version = "0.52.6" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 828 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rcss-css" 3 | description = "Rusty Cascading Style Sheets (RCSS) is a styling language that brings Rust-inspired syntax to CSS. It combines the robustness of Rust with SASS-like features such as nesting and variables for cleaner, more maintainable styles." 4 | version = "0.2.2" 5 | edition = "2024" 6 | 7 | repository = "https://github.com/ved-patel226/RCSS" 8 | homepage = "https://github.com/ved-patel226/RCSS" 9 | readme = "README.md" 10 | license-file = "LICENSE" 11 | 12 | 13 | keywords = [ 14 | "css", 15 | "sass", 16 | "css-preprocessor", 17 | "css-alternative", 18 | "web-development", 19 | ] 20 | categories = ["web-programming", "command-line-utilities"] 21 | 22 | 23 | [dependencies] 24 | chrono = "0.4.40" 25 | clap = { version = "4.4", features = ["derive"] } 26 | colored = "3.0.0" 27 | notify = "8.0.0" 28 | pathdiff = "0.2.3" 29 | pest = "2.7.15" 30 | pest_derive = "2.7.15" 31 | regex = "1.11.1" 32 | walkdir = "2.5.0" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ved Patel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 9 |
10 | 11 |
12 |
13 | 18 |
19 |
20 | 21 | --- 22 | 23 |

24 | 25 | 26 | 27 |

28 | 29 | > [!TIP] 30 | > Download the **[VSCode extension](https://marketplace.visualstudio.com/items?itemName=rcss-syntax-highlighting.rcss)** for syntax highlighting! 31 | 32 | **Rusty Cascading Style Sheets (RCSS)** is a styling language that brings Rust-inspired syntax to CSS. It combines the robustness of Rust with SASS-like features such as nesting and variables for cleaner, more maintainable styles. 33 | 34 | ```rcss 35 | /* common/variables.rcss: 36 | let primary_color: "#FFFFFF"; 37 | let secondary_color: "black"; 38 | 39 | fn padding() { 40 | padding: 10px; 41 | } 42 | */ 43 | 44 | use common::variables::*; 45 | 46 | @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&family=Rubik:ital,wght@0,300..900;1,300..900&display=swap'); 47 | 48 | .container { 49 | width: 50%; 50 | padding(); 51 | 52 | &:hover { 53 | padding: 40px; 54 | } 55 | 56 | h2 { 57 | color: &primary_color; 58 | } 59 | } 60 | 61 | h4 { 62 | width: 50%; 63 | } 64 | 65 | 66 | /* MOBILE STYLES */ 67 | 68 | @media screen and (max-width: 480px) { 69 | .container { 70 | width: 100%; 71 | } 72 | 73 | h4 { 74 | width: 100%; 75 | color: &secondary_color; 76 | } 77 | } 78 | ``` 79 | 80 | > [!NOTE] 81 | > The above RCSS code compiles to CSS in around **500µs**! 82 | 83 | --- 84 | 85 |
86 | 91 |
92 | 93 | First, if you don't have Cargo (Rust's package manager) installed, you can install it by following the instructions on the [official Rust website](https://www.rust-lang.org/tools/install). 94 | 95 | Then, install: 96 | 97 | ```bash 98 | cargo install rcss-css 99 | ``` 100 | 101 | > [!WARNING] 102 | > If you encounter the following warning: 103 | > 104 | > ```bash 105 | > warning: be sure to add `/home//.cargo/bin` to your PATH to be able to run the installed binaries 106 | > ``` 107 | > 108 | > ### **For Linux Users** 109 | > 110 | > Add the following line to your shell configuration file (e.g., `.bashrc`, `.zshrc`, etc.): 111 | > 112 | > ```bash 113 | > export PATH="$HOME/.cargo/bin:$PATH" 114 | > ``` 115 | > 116 | > Reload your shell configuration to apply the changes: 117 | > 118 | > ```bash 119 | > source ~/.bashrc 120 | > ``` 121 | > 122 | > ### **For Windows Users** 123 | > 124 | > 1. Open the Start Menu and search for "Environment Variables." 125 | > 2. Click on "Edit the system environment variables." 126 | > 3. In the **System Properties** window, click the **Environment Variables** button. 127 | > 4. Under **System variables**, locate the `Path` variable and click **Edit**. 128 | > 5. Add the following path to the list: 129 | > 130 | > ``` 131 | > C:\Users\\.cargo\bin 132 | > ``` 133 | > 134 | > 6. Click **OK** to save your changes. 135 | > 136 | > Restart your terminal or command prompt to ensure the updated PATH is recognized. 137 | 138 | --- 139 | 140 |
141 |
    142 | 143 |

    Usage

    144 |
    145 |
146 |
147 | 148 | RCSS expects a directory argument to watch. On file save, RCSS will compile automatically to `../css` 149 | 150 | ```bash 151 | rcss-css styles/rcss 152 | ``` 153 | 154 | This command will compile `.rcss` files in `styles/rcss` into standard CSS files at `styles/css`. 155 | 156 | --- 157 | 158 |
159 |
    160 | 161 |

    Roadmap

    162 |
    163 |
164 |
165 | 166 | ### ✅ Phase 1: Core Features (Current) 167 | 168 | - Implement Rust-like syntax parsing. 169 | - Support variables and nesting. 170 | - Support functions with no arguments 171 | - Develop a VS Code extension with syntax highlighting. 172 | - Implement importing 173 | 174 | ### 🚧 Phase 2: Enhancements (Upcoming) 175 | 176 | - Support functions with arguments 177 | - Add RCSS formatter 178 | - Improve output css format 179 | 180 | --- 181 | 182 | **Base logo** by [Dzuk](https://github.com/dzuk-mutant), licensed under [CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/). [Download the emoji set](https://rustacean.net/fan-art.html#fanart) 183 | 184 | **RCSS** is licensed under the [MIT License](https://opensource.org/licenses/MIT). 185 | -------------------------------------------------------------------------------- /assets/logo/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-patel226/RCSS/727b68ce186a23e3893db23806b79d7d6fe78cb8/assets/logo/128.png -------------------------------------------------------------------------------- /rcss.pest: -------------------------------------------------------------------------------- 1 | // Add this in the CSS section, near the beginning 2 | rcss = _{ SOI ~ (WHITE_SPACE* ~ (at_methods_oneliner | import_statement | function_definition | variable_declaration | rule) ~ WHITE_SPACE*)* ~ EOI } 3 | 4 | at_methods_oneliner = { 5 | WHITE_SPACE* ~ "@" ~ ( 6 | "import" ~ WHITE_SPACE+ ~ "url" ~ "(" ~ string_literal ~ ")" ~ WHITE_SPACE* ~ ";" | 7 | "charset" ~ WHITE_SPACE+ ~ string_literal ~ WHITE_SPACE* ~ ";" | 8 | "namespace" ~ WHITE_SPACE+ ~ (string_literal ~ WHITE_SPACE+)? ~ identifier ~ WHITE_SPACE* ~ ";" | 9 | ANY ~ WHITE_SPACE ~ ANY ~ WHITE_SPACE ~ ";" 10 | ) 11 | } 12 | 13 | // 14 | // IMPORTS 15 | // 16 | import_statement = { WHITE_SPACE* ~ "use" ~ WHITE_SPACE+ ~ import_path ~ end_seperater ~ WHITE_SPACE* } 17 | import_path = _{ (identifier ~ "::" )* ~ ( identifier | "*" ) } 18 | 19 | // 20 | // RULES 21 | // 22 | rule = _{ rule_comment | media_query | keyframes_rule | rule_normal } 23 | rule_comment = { WHITE_SPACE* ~ comment ~ WHITE_SPACE* } 24 | rule_normal = { r_base } 25 | 26 | // 27 | // MEDIA QUERIES 28 | // 29 | media_query = { WHITE_SPACE* ~ "@media" ~ WHITE_SPACE+ ~ media_condition ~ WHITE_SPACE* ~ left_curly_brace ~ rule* ~ right_curly_brace ~ WHITE_SPACE* } 30 | media_condition = { (!(left_curly_brace) ~ ANY)+ } 31 | 32 | // 33 | // KEYFRAMES 34 | // 35 | keyframes_rule = { 36 | WHITE_SPACE* ~ ("@keyframes" | "@-webkit-keyframes") ~ WHITE_SPACE+ ~ keyframes_name ~ WHITE_SPACE* ~ left_curly_brace ~ keyframe_selector_block* ~ right_curly_brace ~ WHITE_SPACE* 37 | } 38 | keyframes_name = @{ ASCII_ALPHA ~ text_chars* } 39 | keyframe_selector_block = { WHITE_SPACE* ~ keyframe_selector ~ WHITE_SPACE* ~ left_curly_brace ~ declaration* ~ right_curly_brace ~ WHITE_SPACE* } 40 | keyframe_selector = { percentage | from_keyword | to_keyword | (percentage ~ (WHITE_SPACE* ~ "," ~ WHITE_SPACE* ~ percentage)*) } 41 | percentage = @{ ASCII_DIGIT+ ~ "%" } 42 | from_keyword = { "from" } 43 | to_keyword = { "to" } 44 | 45 | // 46 | // COMMENTS 47 | // 48 | comment = _{ comment_start_tag ~ comment_body ~ comment_end_tag } 49 | comment_body = { (!comment_end_tag ~ ANY)* } 50 | comment_start_tag = _{ "/*" ~ WHITE_SPACE* } 51 | comment_end_tag = _{ WHITE_SPACE* ~ "*/" } 52 | 53 | // 54 | // SELECTORS 55 | // 56 | sel_id = _{ prefix_id ~ sel_id_body } 57 | sel_id_body = { ASCII_ALPHA ~ text_chars* } 58 | 59 | sel_class = _{ prefix_class ~ sel_class_body } 60 | sel_class_body = { ASCII_ALPHA ~ text_chars* } 61 | 62 | sel_type = _{ ASCII_ALPHA ~ text_chars* } 63 | 64 | sel_uni = _{ "*" } 65 | 66 | // A single selector element (e.g., "div", ".class", "#id", "::before") 67 | sel_element = _{ sel_id | sel_class | sel_uni | sel_type | pseudo_element | pseudo_element_reference } 68 | 69 | // A compound selector with no spaces (e.g., "div.class#id") 70 | sel_compound = _{ sel_element+ } 71 | 72 | // The full selector with support for nesting through whitespace 73 | selector = { WHITE_SPACE* ~ sel_compound ~ (WHITE_SPACE+ ~ sel_compound)* ~ WHITE_SPACE* } 74 | 75 | // Pseudo-elements (e.g., "::before", "::after") 76 | pseudo_element = _{ ":" ~ ":"? ~ ASCII_ALPHA+ } 77 | pseudo_element_reference = _{ "&:" ~ ":"? ~ ASCII_ALPHA+ } 78 | 79 | // 80 | // DECLARATION 81 | // 82 | del_property = @{ ANY ~ text_chars* } 83 | 84 | del_val_keyword = @{ ASCII_ALPHA ~ text_chars* } 85 | del_val_color = { prefix_id ~ (ASCII_ALPHA | ASCII_DIGIT)* } 86 | 87 | del_val_length = { "-"? ~ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? ~ length_type } 88 | 89 | // 90 | // VARIABLES 91 | // 92 | variable_declaration = { WHITE_SPACE* ~ "let" ~ WHITE_SPACE+ ~ variable_name ~ property_separater ~ WHITE_SPACE* ~ string_literal ~ end_seperater ~ WHITE_SPACE* } 93 | string_literal = { ("\"" ~ ( !"\"" ~ ANY )* ~ "\"") | ("'" ~ ( !"'" ~ ANY )* ~ "'") } 94 | variable_name = @{ ASCII_ALPHA ~ text_chars* } 95 | variable_reference = { "&" ~ ASCII_ALPHA ~ text_chars* } 96 | 97 | // 98 | // USER CREATED FUNCTIONS 99 | // 100 | function_definition = { "fn" ~ WHITE_SPACE+ ~ function_name ~ WHITE_SPACE* ~ parameter_list ~ WHITE_SPACE* ~ function_block } 101 | function_name = @{ ASCII_ALPHA ~ text_chars* } 102 | parameter_list = { "(" ~ ")" } 103 | parameter = { WHITE_SPACE* ~ identifier ~ WHITE_SPACE* } 104 | identifier = @{ ASCII_ALPHA ~ text_chars* } 105 | function_block = { left_curly_brace ~ declaration* ~ right_curly_brace } 106 | user_created_function_call = { WHITE_SPACE* ~ function_name ~ WHITE_SPACE* ~ parameter_list ~ WHITE_SPACE* ~ ";" } 107 | 108 | // Function calls (ex: blur(10px)) 109 | function_call = { 110 | (ASCII_ALPHA | ASCII_DIGIT | "-")+ ~ "(" ~ 111 | ( 112 | WHITE_SPACE* ~ 113 | ( 114 | function_call | 115 | del_val_keyword | 116 | del_val_color | 117 | del_val_length | 118 | variable_reference | 119 | string_literal | 120 | "-" ~ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? ~ length_type | // For negative values 121 | arithmetic_operator // For calc expressions 122 | ) ~ 123 | WHITE_SPACE* ~ ("," ~ WHITE_SPACE*)? 124 | )* ~ 125 | ")" 126 | } 127 | 128 | // Add this new rule for arithmetic operators in calc() functions 129 | arithmetic_operator = { "+" | "-" | "*" | "/" } 130 | 131 | important = {"!important"} 132 | 133 | del_value = _{ 134 | ( 135 | (function_call | del_val_keyword | del_val_color | del_val_length | variable_reference | string_literal | css_operator | important) ~ 136 | WHITE_SPACE* 137 | )+ ~ 138 | ( 139 | "," ~ WHITE_SPACE* ~ 140 | ( 141 | (function_call | del_val_keyword | del_val_color | del_val_length | variable_reference | string_literal | css_operator) ~ 142 | WHITE_SPACE* 143 | )+ 144 | )* 145 | } 146 | 147 | css_operator = { "/" | "," } 148 | 149 | declaration = { WHITE_SPACE* ~ del_property ~ property_separater ~ WHITE_SPACE* ~ del_value ~ end_seperater ~ WHITE_SPACE* } 150 | 151 | // 152 | // RULE BASE 153 | // 154 | 155 | // Ex: h1 { color: red } 156 | 157 | r_base = _{ selector ~ left_curly_brace ~ r_content* ~ right_curly_brace ~ WHITE_SPACE* } 158 | nested_rule = _{ r_base } 159 | 160 | r_content = _{ 161 | (comment ~ WHITE_SPACE*) | 162 | (user_created_function_call ~ WHITE_SPACE*) | 163 | (declaration ~ WHITE_SPACE*) | 164 | (nested_rule ~ WHITE_SPACE*) 165 | } 166 | 167 | // 168 | // SYMBOLS / CHARACTERS 169 | // 170 | text_chars = _{ ASCII_ALPHA | ASCII_DIGIT | "_" | "-" } 171 | 172 | left_curly_brace = _{ "{" } 173 | right_curly_brace = { "}" } 174 | 175 | // multiples_separater = _{ "," } 176 | property_separater = _{ ":" } 177 | end_seperater = _{ ";" } 178 | 179 | prefix_id = _{ "#" } 180 | prefix_class = _{ "." } 181 | 182 | length_type = { "cm" | "mm" | "in" | "px" | "pt" | "pc" | "em" | "ex" | "ch" | "rem" | "vw" | "vh" | "vmin" | "vmax" | "%" | "s" | "ms" | "" } -------------------------------------------------------------------------------- /rcss/example.rcss: -------------------------------------------------------------------------------- 1 | use rcss::common::variables::*; 2 | 3 | let var: "5"; 4 | 5 | fn padding() { 6 | padding: 20px; 7 | color: red; 8 | } 9 | 10 | .container { 11 | color: blue; 12 | filter: blur(10px); 13 | 14 | 15 | h2 { 16 | color: red; 17 | padding(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/compile.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use pest::Parser; 3 | use pest_derive::Parser; 4 | use std::collections::HashMap; 5 | use std::time::Instant; 6 | use colored::*; 7 | use chrono::Local; 8 | 9 | use crate::{ error::{ RCSSError, display_error }, Result }; 10 | 11 | use crate::{ rule_normal, variables, functions, keyframes, imports, media_queries, MetaData }; 12 | 13 | #[derive(Parser)] 14 | #[grammar = "rcss.pest"] 15 | pub struct RCSSParser; 16 | 17 | #[allow(dead_code)] 18 | pub fn print_rule(pair: pest::iterators::Pair) { 19 | println!("{:?} -> {}", pair.as_rule(), pair.as_str()); 20 | } 21 | 22 | #[allow(unused)] 23 | pub fn compile( 24 | input_path: &str, 25 | output_path: &str, 26 | relative_path: &str, 27 | project_meta_data: &mut HashMap>, 28 | verbose: bool, 29 | initial_compile: bool 30 | ) -> Result>> { 31 | let start_time = Instant::now(); 32 | 33 | let raw_rcss = fs::read_to_string(input_path)?; 34 | 35 | let pairs = match RCSSParser::parse(Rule::rcss, &raw_rcss) { 36 | Ok(p) => p, 37 | Err(e) => { 38 | // Extract location information from pest error 39 | let (line, column) = match e.line_col { 40 | pest::error::LineColLocation::Pos((line, col)) => (line, col), 41 | pest::error::LineColLocation::Span((line, col), _) => (line, col), 42 | }; 43 | 44 | // Get a few lines around the error for context 45 | let lines: Vec<&str> = raw_rcss.lines().collect(); 46 | let start = line.saturating_sub(2); 47 | let end = (line + 1).min(lines.len()); 48 | let context = lines[start..end].join("\n"); 49 | 50 | let err = RCSSError::ParseError { 51 | line, 52 | column, 53 | message: format!("{}", e), 54 | file_path: input_path.into(), 55 | context: context, 56 | }; 57 | 58 | display_error(&err); 59 | 60 | return Err(err); 61 | } 62 | }; 63 | 64 | let mut css_output = String::new(); 65 | let mut meta_data: Vec = Vec::new(); 66 | let mut declarations: HashMap> = HashMap::new(); 67 | let mut keyframes: HashMap>> = HashMap::new(); 68 | let mut one_liners: Vec = Vec::new(); 69 | let mut media_queries: HashMap>> = HashMap::new(); 70 | 71 | for pair in pairs { 72 | match pair.as_rule() { 73 | Rule::import_statement => { 74 | // we don't want to import anything on initial check 75 | // we just want to fill project_meta_data 76 | if initial_compile { 77 | continue; 78 | } 79 | 80 | meta_data = imports::process_import_statement( 81 | &mut meta_data, 82 | project_meta_data, 83 | &raw_rcss, 84 | input_path, 85 | relative_path, 86 | pair 87 | )?; 88 | } 89 | 90 | Rule::variable_declaration => { 91 | meta_data = variables::process_variable_declaration(meta_data, pair); 92 | } 93 | 94 | Rule::function_definition => { 95 | meta_data = functions::process_function_definition( 96 | meta_data, 97 | pair, 98 | &raw_rcss, 99 | &input_path, 100 | initial_compile 101 | )?; 102 | } 103 | 104 | Rule::rule_normal => { 105 | // we don't want to import anything on initial check 106 | // we just want to fill project_meta_data 107 | if initial_compile { 108 | continue; 109 | } 110 | 111 | declarations = rule_normal::process_rule_normal( 112 | meta_data.clone(), 113 | declarations, 114 | pair, 115 | &raw_rcss, 116 | &input_path 117 | )?; 118 | } 119 | 120 | Rule::keyframes_rule => { 121 | if initial_compile { 122 | continue; 123 | } 124 | 125 | keyframes = keyframes::process_keyframes_definition( 126 | keyframes, 127 | pair, 128 | &meta_data, 129 | &raw_rcss, 130 | input_path 131 | )?; 132 | } 133 | 134 | Rule::rule_comment => {} 135 | 136 | Rule::EOI => {} 137 | 138 | Rule::at_methods_oneliner => { 139 | one_liners.push(pair.as_str().trim().to_string()); 140 | } 141 | 142 | Rule::media_query => { 143 | if initial_compile { 144 | continue; 145 | } 146 | 147 | media_queries = media_queries::process_media_query( 148 | media_queries, 149 | pair, 150 | &meta_data, 151 | &raw_rcss, 152 | input_path 153 | )?; 154 | } 155 | 156 | _ => { 157 | // println!("{:?} -> {}", pair.as_rule(), pair.as_str()); 158 | } 159 | } 160 | } 161 | 162 | project_meta_data.insert(input_path.to_string(), meta_data.clone()); 163 | 164 | let now = Local::now(); 165 | let formatted_time = now.format("%I:%M:%S %p"); 166 | 167 | let elapsed_time = start_time.elapsed(); 168 | 169 | if initial_compile { 170 | return Ok(project_meta_data.clone()); 171 | } 172 | 173 | let css_output = css_map_to_string(&declarations, &keyframes, &one_liners, &media_queries); 174 | 175 | // Create folders 176 | if let Some(parent) = std::path::Path::new(output_path).parent() { 177 | fs::create_dir_all(parent)?; 178 | } 179 | fs::write(output_path, css_output)?; 180 | 181 | println!( 182 | "{} {} {}", 183 | format!("CSS written to {}", output_path).green(), 184 | format!("in {:.2?}", elapsed_time).truecolor(128, 128, 128), 185 | format!("@ {}", formatted_time).truecolor(128, 128, 128) 186 | ); 187 | 188 | Ok(project_meta_data.clone()) 189 | } 190 | 191 | fn css_map_to_string( 192 | css_map: &HashMap>, 193 | keyframes: &HashMap>>, 194 | one_liners: &Vec, 195 | media_queries: &HashMap>> 196 | ) -> String { 197 | let mut css_string = String::new(); 198 | 199 | // Process regular CSS rules 200 | let mut sorted_css_map: Vec<_> = css_map.iter().collect(); 201 | sorted_css_map.sort_by_key(|(selector, _)| *selector); 202 | 203 | for one_liner in one_liners { 204 | css_string.push_str(one_liner); 205 | css_string.push_str("\n"); 206 | } 207 | 208 | if !one_liners.is_empty() { 209 | css_string.push_str("\n"); 210 | } 211 | 212 | for (selector, properties) in sorted_css_map { 213 | css_string.push_str(selector); 214 | css_string.push_str(" {\n"); 215 | 216 | let mut sorted_properties = properties.clone(); 217 | sorted_properties.sort(); 218 | 219 | for property in sorted_properties { 220 | css_string.push_str(" "); 221 | css_string.push_str(&property); 222 | css_string.push('\n'); 223 | } 224 | 225 | css_string.push_str("}\n\n"); 226 | } 227 | 228 | // Process at-methods like @keyframes 229 | let mut keyframes: Vec<_> = keyframes.iter().collect(); 230 | keyframes.sort_by_key(|(at_rule, _)| *at_rule); 231 | 232 | for (at_rule, keyframes) in keyframes { 233 | css_string.push_str(at_rule); 234 | css_string.push_str(" {\n"); 235 | 236 | let mut sorted_keyframes: Vec<_> = keyframes.iter().collect(); 237 | sorted_keyframes.sort_by_key(|(keyframe_selector, _)| *keyframe_selector); 238 | 239 | for (keyframe_selector, properties) in sorted_keyframes { 240 | css_string.push_str(" "); 241 | css_string.push_str(keyframe_selector); 242 | css_string.push_str(" {\n"); 243 | 244 | let mut sorted_properties = properties.clone(); 245 | sorted_properties.sort(); 246 | 247 | for property in sorted_properties { 248 | css_string.push_str(" "); 249 | css_string.push_str(&property); 250 | css_string.push('\n'); 251 | } 252 | 253 | css_string.push_str(" }\n\n"); 254 | } 255 | 256 | css_string.push_str("}\n\n"); 257 | } 258 | 259 | for (condition, property_value) in media_queries { 260 | css_string.push_str(&format!("{} {{\n", condition.trim())); 261 | 262 | for (property, values) in property_value { 263 | css_string.push_str(&format!(" {} {{\n", property.trim())); 264 | 265 | for value in values { 266 | css_string.push_str(" "); 267 | css_string.push_str(&value); 268 | css_string.push_str("\n"); 269 | } 270 | 271 | css_string.push_str(" }\n"); 272 | } 273 | 274 | css_string.push_str("}\n"); 275 | } 276 | 277 | css_string 278 | } 279 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::path::PathBuf; 3 | use colored::Colorize; 4 | 5 | /// The different types of errors that can occur in RCSS 6 | #[derive(Debug)] 7 | #[allow(unused)] 8 | pub enum RCSSError { 9 | IoError(std::io::Error), 10 | ParseError { 11 | file_path: PathBuf, 12 | line: usize, 13 | column: usize, 14 | message: String, 15 | context: String, 16 | }, 17 | CompilationError { 18 | file_path: PathBuf, 19 | message: String, 20 | }, 21 | ConfigError(String), 22 | ImportError { 23 | file_path: PathBuf, 24 | line: usize, 25 | column: usize, 26 | message: String, 27 | context: String, 28 | }, 29 | VariableError { 30 | file_path: PathBuf, 31 | line: usize, 32 | column: usize, 33 | variable_name: String, 34 | message: String, 35 | context: String, 36 | }, 37 | FunctionError { 38 | file_path: PathBuf, 39 | line: usize, 40 | column: usize, 41 | function_name: String, 42 | message: String, 43 | context: String, 44 | }, 45 | } 46 | 47 | impl fmt::Display for RCSSError { 48 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 49 | match self { 50 | RCSSError::IoError(err) => write!(f, "IO Error: {}", err), 51 | RCSSError::ParseError { file_path, line, column, message, context } => { 52 | write!( 53 | f, 54 | "Parse Error at {}:{}:{} - {} (Context: {})", 55 | file_path.display(), 56 | line, 57 | column, 58 | message, 59 | context 60 | ) 61 | } 62 | RCSSError::CompilationError { file_path, message } => { 63 | write!(f, "Compilation Error in {} - {}", file_path.display(), message) 64 | } 65 | RCSSError::ConfigError(message) => { write!(f, "Configuration Error: {}", message) } 66 | RCSSError::ImportError { file_path, message, line: _, column: _, context: _ } => { 67 | write!(f, "Import Error in {}. {}", file_path.display(), message) 68 | } 69 | RCSSError::VariableError { 70 | file_path, 71 | variable_name, 72 | message, 73 | line, 74 | column, 75 | context, 76 | } => { 77 | write!( 78 | f, 79 | "Variable Error for var: {} at {}:{}:{} - {} (Context: {})", 80 | variable_name, 81 | file_path.display(), 82 | line, 83 | column, 84 | message, 85 | context 86 | ) 87 | } 88 | RCSSError::FunctionError { 89 | file_path, 90 | line, 91 | column, 92 | function_name, 93 | message, 94 | context, 95 | } => { 96 | write!( 97 | f, 98 | "Function Error for func: {} at {}:{}:{} - {} (Context: {})", 99 | function_name, 100 | file_path.display(), 101 | line, 102 | column, 103 | message, 104 | context 105 | ) 106 | } 107 | } 108 | } 109 | } 110 | 111 | impl std::error::Error for RCSSError {} 112 | 113 | impl From for RCSSError { 114 | fn from(error: std::io::Error) -> Self { 115 | RCSSError::IoError(error) 116 | } 117 | } 118 | 119 | /// Displays a stylized parse error message with code context 120 | fn display_error_with_context( 121 | file_path: &std::path::Path, 122 | line: usize, 123 | column: usize, 124 | message: &str, 125 | context: &str 126 | ) { 127 | let location = format!("{} --> {}:{}", file_path.display(), line, column); 128 | let length = std::cmp::min(message.len() + 10, 110); 129 | 130 | println!("{}", location); 131 | 132 | println!("{}{}", "╭".bright_red(), "─".repeat(length).bright_red()); 133 | println!("{}", "│".bright_red()); 134 | println!("{} {}", "│".bright_red(), message.white().bold()); 135 | println!("{}", "│".bright_red()); 136 | 137 | // Display code snippet with highlighting 138 | let lines: Vec<&str> = context.lines().collect(); 139 | 140 | for (i, line_content) in lines.iter().enumerate() { 141 | let line_num = (line - 1 + i).to_string(); 142 | println!("{} {: >3} │ {}", "│".bright_red(), line_num.white(), line_content); 143 | 144 | if i == 1 { 145 | // Highlight the error position with an arrow 146 | let mut pointer = " ".repeat(column); 147 | pointer.push('↑'); 148 | println!( 149 | "{} {: >3} │ {}", 150 | "│".bright_red(), 151 | " ".bright_yellow(), 152 | pointer.bright_red().bold() 153 | ); 154 | } 155 | } 156 | 157 | println!("{}", "│".bright_red()); 158 | println!("{}{}", "╰".bright_red(), "─".repeat(length).bright_red()); 159 | } 160 | 161 | /// Displays a stylized error message to the console 162 | pub fn display_error(error: &RCSSError) { 163 | let error_title = match error { 164 | RCSSError::IoError(_) => "I/O ERROR", 165 | RCSSError::ParseError { .. } => "SYNTAX ERROR", 166 | RCSSError::CompilationError { .. } => "COMPILATION ERROR", 167 | RCSSError::ConfigError(_) => "CONFIG ERROR", 168 | RCSSError::ImportError { .. } => "IMPORT ERROR", 169 | RCSSError::VariableError { .. } => "VARIABLE ERROR", 170 | RCSSError::FunctionError { .. } => "FUNCTION ERROR", 171 | }; 172 | 173 | // Create the header 174 | let header = format!(" {} ", error_title).black().on_red().bold(); 175 | println!("\n{}", header); 176 | 177 | // Display the error message 178 | match error { 179 | RCSSError::IoError(err) => { 180 | println!("{}", "╭─────────────────────────────────────────────────────".bright_red()); 181 | println!("{}", "│".bright_red()); 182 | println!("{} {}", "│".bright_red(), " File System Error ".red().bold()); 183 | println!("{} {}", "│".bright_red(), err); 184 | println!("{}", "│".bright_red()); 185 | println!("{}", "╰─────────────────────────────────────────────────────".bright_red()); 186 | } 187 | 188 | RCSSError::ParseError { file_path, line, column, message, context } => { 189 | let trimmed = message 190 | .split("expected") 191 | .nth(1) 192 | .map(|s| format!("expected:{}", s)) 193 | .unwrap_or_else(|| message.to_string()); 194 | 195 | display_error_with_context(file_path, *line, *column, &trimmed, context); 196 | } 197 | 198 | RCSSError::CompilationError { file_path, message } => { 199 | println!("{}", "╭─────────────────────────────────────────────────────".bright_red()); 200 | println!("{}", "│".bright_red()); 201 | println!("{} {}", "│".bright_red(), " File ".red().bold()); 202 | println!("{} {}", "│".bright_red(), file_path.display().to_string().blue()); 203 | println!("{}", "│".bright_red()); 204 | println!("{} {}", "│".bright_red(), " Message ".red().bold()); 205 | println!("{} {}", "│".bright_red(), message); 206 | println!("{}", "│".bright_red()); 207 | println!("{}", "╰─────────────────────────────────────────────────────".bright_red()); 208 | } 209 | 210 | RCSSError::ConfigError(message) => { 211 | println!("{}", "╭─────────────────────────────────────────────────────".bright_red()); 212 | println!("{}", "│".bright_red()); 213 | println!("{} {}", "│".bright_red(), " Configuration Issue ".red().bold()); 214 | println!("{} {}", "│".bright_red(), message); 215 | println!("{}", "│".bright_red()); 216 | println!("{}", "╰─────────────────────────────────────────────────────".bright_red()); 217 | } 218 | 219 | RCSSError::ImportError { file_path, message, line, column, context } => { 220 | display_error_with_context(file_path, *line, *column, &message, context); 221 | } 222 | 223 | RCSSError::VariableError { 224 | file_path, 225 | variable_name: _, 226 | message, 227 | line, 228 | column, 229 | context, 230 | } => { 231 | display_error_with_context(file_path, *line, *column, message, context); 232 | } 233 | 234 | RCSSError::FunctionError { 235 | file_path, 236 | function_name: _, 237 | message, 238 | line, 239 | column, 240 | context, 241 | } => { 242 | display_error_with_context(file_path, *line, *column, message, context); 243 | } 244 | } 245 | 246 | println!("\n{}\n", "For help, open an issue on GitHub.".dimmed()); 247 | } 248 | 249 | pub fn get_error_context(file_content: &str, error_line: usize, context_lines: usize) -> String { 250 | let lines: Vec<&str> = file_content.lines().collect(); 251 | 252 | // Calculate start and end lines for context, ensuring bounds 253 | let start_line = error_line.saturating_sub(context_lines); 254 | let end_line = std::cmp::min(error_line + context_lines, lines.len()); 255 | 256 | // Build context string with line numbers 257 | let mut context = String::new(); 258 | for i in start_line..end_line { 259 | if i < lines.len() { 260 | context.push_str(&format!("{}\n", lines[i])); 261 | } 262 | } 263 | 264 | context 265 | } 266 | 267 | /// For displaying warnings that aren't critical errors 268 | #[allow(unused)] 269 | pub fn display_warning(message: &str) { 270 | let header = " WARNING ".black().on_yellow().bold(); 271 | let length = std::cmp::min(message.len(), 100); 272 | 273 | println!("\n{}", header); 274 | 275 | println!("{}{}", "╭".yellow(), "─".repeat(length).yellow()); 276 | println!("{}", "│".yellow()); 277 | println!("{} {}", "│".yellow(), message); 278 | println!("{}", "│".yellow()); 279 | println!("{}{}", "╰".yellow(), "─".repeat(length).yellow()); 280 | 281 | println!(); 282 | } 283 | 284 | /// A Result type using RCSSError 285 | pub type Result = std::result::Result; 286 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // RCSS Project File Imports 2 | mod compile; 3 | mod error; 4 | 5 | pub mod process_x { 6 | pub mod variables; 7 | pub mod rule_normal; 8 | pub mod functions; 9 | pub mod keyframes; 10 | pub mod imports; 11 | pub mod media_queries; 12 | } 13 | 14 | use process_x::{ variables, rule_normal, functions, keyframes, imports, media_queries }; 15 | 16 | use error::Result; 17 | 18 | use clap::{ Arg, Command }; 19 | use compile::compile; 20 | use std::path::Path; 21 | use std::collections::HashMap; 22 | 23 | use notify::event::{ AccessKind, AccessMode }; 24 | use notify::{ recommended_watcher, Event, RecursiveMode, Watcher, EventKind }; 25 | use std::sync::mpsc; 26 | 27 | #[derive(Debug, Clone)] 28 | #[allow(unused)] 29 | pub enum MetaData { 30 | Variables { 31 | name: String, 32 | value: String, 33 | }, 34 | Function { 35 | name: String, 36 | body: Vec, 37 | }, 38 | Keyframes { 39 | name: String, 40 | body: HashMap>, 41 | }, 42 | } 43 | 44 | fn main() -> Result<()> { 45 | let matches = Command::new("RCSS") 46 | .version("0.1.1") 47 | .about("Bringing Rust to CSS") 48 | .long_about( 49 | "For more information and to contribute, visit: https://github.com/ved-patel226/RCSS" 50 | ) 51 | .arg(Arg::new("folder").help("Input directory to watch").required(true).index(1)) 52 | .arg( 53 | Arg::new("verbose") 54 | .short('v') 55 | .long("verbose") 56 | .help("Print verbose processing information") 57 | .action(clap::ArgAction::SetTrue) 58 | ) 59 | .get_matches(); 60 | 61 | let input_path = Path::new(matches.get_one::("folder").unwrap()); 62 | let current_path = std::env::current_dir()?; 63 | 64 | let rcss_input_path = current_path.join(input_path); 65 | let css_input_path = rcss_input_path.join("../css"); 66 | 67 | if !css_input_path.exists() { 68 | std::fs::create_dir_all(&css_input_path)?; 69 | } 70 | let css_input_path = css_input_path.canonicalize()?; 71 | 72 | let verbose = matches.get_flag("verbose"); 73 | 74 | let mut project_meta_data: HashMap> = HashMap::new(); 75 | 76 | let mut rcss_files = Vec::new(); 77 | 78 | fn collect_rcss_files( 79 | dir: &Path, 80 | rcss_files: &mut Vec, 81 | base_path: &Path 82 | ) -> Result<()> { 83 | for entry in std::fs::read_dir(dir)? { 84 | let entry = entry?; 85 | let path = entry.path(); 86 | 87 | if path.is_dir() { 88 | collect_rcss_files(&path, rcss_files, base_path)?; 89 | } else if path.extension().and_then(|ext| ext.to_str()) == Some("rcss") { 90 | if let Ok(relative_path) = path.strip_prefix(base_path) { 91 | rcss_files.push(relative_path.to_path_buf()); 92 | } 93 | } 94 | } 95 | Ok(()) 96 | } 97 | 98 | collect_rcss_files(&rcss_input_path, &mut rcss_files, &rcss_input_path)?; 99 | 100 | let mut initial_compile_errors = 0; 101 | 102 | for rcss_file in &rcss_files { 103 | if 104 | let Err(_) = compile( 105 | rcss_input_path.join(rcss_file).to_str().unwrap(), 106 | css_input_path.join(rcss_file).with_extension("css").to_str().unwrap(), 107 | rcss_input_path.to_str().unwrap(), 108 | &mut project_meta_data, 109 | verbose, 110 | true 111 | ) 112 | { 113 | initial_compile_errors += 1; 114 | } 115 | } 116 | 117 | if initial_compile_errors > 0 { 118 | println!("Stopping execution due to initial errors. Fix above before continuing.."); 119 | std::process::exit(1); 120 | } else { 121 | println!( 122 | "Initial check successful. Watching {} for changes...", 123 | &rcss_input_path.display() 124 | ); 125 | } 126 | 127 | let (tx, rx) = mpsc::channel::>(); 128 | 129 | let mut watcher = recommended_watcher(tx).map_err(|e| 130 | std::io::Error::new(std::io::ErrorKind::Other, e) 131 | )?; 132 | 133 | // Add a path to be watched. All files and directories at that path and 134 | // below will be monitored for changes. 135 | watcher 136 | .watch(Path::new(input_path), RecursiveMode::Recursive) 137 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; 138 | 139 | for res in rx { 140 | match res { 141 | Ok(path) => { 142 | // if file is written to 143 | if let EventKind::Access(AccessKind::Close(AccessMode::Write)) = path.kind { 144 | if path.paths[0].extension().and_then(|s| s.to_str()) == Some("rcss") { 145 | let rcss_file = path.paths[0].strip_prefix(&rcss_input_path).unwrap(); 146 | 147 | let rcss_combined_path = rcss_input_path.join(rcss_file); 148 | let css_combined_path = css_input_path 149 | .join(rcss_file) 150 | .with_extension("css"); 151 | 152 | let _ = compile( 153 | rcss_combined_path.to_str().unwrap(), 154 | css_combined_path.to_str().unwrap(), 155 | rcss_input_path.to_str().unwrap(), 156 | &mut project_meta_data, 157 | verbose, 158 | false 159 | ); 160 | } 161 | } 162 | } 163 | 164 | Err(e) => println!("watch error: {:?}", e), 165 | } 166 | } 167 | 168 | Ok(()) 169 | } 170 | -------------------------------------------------------------------------------- /src/process_x/functions.rs: -------------------------------------------------------------------------------- 1 | use pest::iterators::Pair; 2 | use crate::{ compile::Rule, MetaData, error::{ RCSSError, get_error_context, display_error } }; 3 | 4 | pub fn process_function_definition( 5 | mut meta_data: Vec, 6 | pair: Pair, 7 | raw_rcss: &str, 8 | input_path: &str, 9 | initial_compile: bool 10 | ) -> Result, RCSSError> { 11 | let _ = initial_compile; 12 | 13 | let inner_pairs = pair.into_inner(); 14 | 15 | let mut name = String::new(); 16 | let mut declerations: Vec = vec![]; 17 | 18 | for in_pair in inner_pairs { 19 | match in_pair.as_rule() { 20 | Rule::function_name => { 21 | name = in_pair.as_str().trim().to_string(); 22 | } 23 | 24 | Rule::function_block => { 25 | let function_block_inner_pairs = in_pair.into_inner(); 26 | 27 | for func_in_pair in function_block_inner_pairs { 28 | match func_in_pair.as_rule() { 29 | Rule::declaration => { 30 | let declaration_inner = func_in_pair.clone().into_inner(); 31 | let mut variable_reference = String::new(); 32 | 33 | for dec_in_pair in declaration_inner { 34 | match dec_in_pair.as_rule() { 35 | Rule::variable_reference => { 36 | variable_reference = dec_in_pair.as_str().to_string(); 37 | } 38 | 39 | _ => {} 40 | } 41 | } 42 | 43 | let default_value = func_in_pair.as_str().trim().to_string(); 44 | 45 | if !variable_reference.is_empty() { 46 | let mut found_var = false; 47 | 48 | for md in &meta_data { 49 | if let MetaData::Variables { name, value } = md { 50 | if name == variable_reference.trim_start_matches('&') { 51 | found_var = true; 52 | 53 | let replaced_value = default_value.replace( 54 | &variable_reference, 55 | value 56 | ); 57 | declerations.push(replaced_value); 58 | } 59 | } 60 | } 61 | 62 | if !found_var { 63 | let position = func_in_pair.line_col(); 64 | let line = position.0; 65 | let column = position.1; 66 | let context = get_error_context(raw_rcss, line, 2); 67 | 68 | let err = RCSSError::VariableError { 69 | file_path: input_path.into(), 70 | line, 71 | column, 72 | variable_name: variable_reference 73 | .trim_start_matches("&") 74 | .to_string(), 75 | message: format!( 76 | "Could not find variable: {}", 77 | variable_reference.trim_start_matches("&") 78 | ), 79 | context, 80 | }; 81 | 82 | display_error(&err); 83 | return Err(err); 84 | } 85 | } else { 86 | declerations.push(default_value); 87 | } 88 | } 89 | 90 | _ => {} 91 | } 92 | } 93 | } 94 | 95 | Rule::parameter_list => {} 96 | 97 | _ => {} 98 | } 99 | } 100 | 101 | meta_data.push(MetaData::Function { name, body: declerations }); 102 | 103 | Ok(meta_data) 104 | } 105 | -------------------------------------------------------------------------------- /src/process_x/imports.rs: -------------------------------------------------------------------------------- 1 | use pest::iterators::Pair; 2 | use crate::{ 3 | compile::Rule, 4 | error::{ display_error, RCSSError, get_error_context }, 5 | MetaData, 6 | Result, 7 | }; 8 | use std::collections::HashMap; 9 | 10 | pub fn process_import_statement( 11 | meta_data: &mut Vec, 12 | project_meta_data: &mut HashMap>, 13 | raw_rcss: &str, 14 | input_path: &str, 15 | relative_path: &str, 16 | pair: Pair 17 | ) -> Result> { 18 | let inner_pairs = pair.clone().into_inner(); 19 | let mut target_import_file: Vec = Vec::new(); 20 | 21 | for import_in_pair in inner_pairs { 22 | match import_in_pair.as_rule() { 23 | Rule::identifier => { 24 | target_import_file.push(import_in_pair.as_str().to_string()); 25 | } 26 | 27 | _ => {} 28 | } 29 | } 30 | 31 | //TODO - redo this better.. it sucks rn 32 | let full_path = format!("{}/{}", relative_path, target_import_file.join("/")) + ".rcss"; 33 | 34 | if let Some(imported_meta_data) = project_meta_data.get(&full_path) { 35 | meta_data.extend(imported_meta_data.clone()); 36 | } else { 37 | let position = pair.line_col(); 38 | let line = position.0; 39 | let column = position.1; 40 | let context = get_error_context(raw_rcss, line, 2); 41 | 42 | let err = RCSSError::ImportError { 43 | file_path: input_path.into(), 44 | line: line, 45 | column: column, 46 | message: "File not found".to_string(), 47 | context: context, 48 | }; 49 | 50 | display_error(&err); 51 | return Err(err); 52 | } 53 | 54 | Ok(meta_data.clone()) 55 | } 56 | -------------------------------------------------------------------------------- /src/process_x/keyframes.rs: -------------------------------------------------------------------------------- 1 | use pest::iterators::Pair; 2 | use crate::{ compile::Rule, MetaData, error::{ RCSSError, get_error_context, display_error } }; 3 | use std::collections::HashMap; 4 | 5 | pub fn process_keyframes_definition( 6 | mut keyframes: HashMap>>, 7 | pair: Pair, 8 | meta_data: &[MetaData], 9 | raw_rcss: &str, 10 | input_path: &str 11 | ) -> Result>>, RCSSError> { 12 | let inner_pairs = pair.into_inner(); 13 | let mut name = String::new(); 14 | let mut selector_to_declarations: HashMap> = HashMap::new(); 15 | let mut current_selector = String::new(); 16 | 17 | for in_pair in inner_pairs { 18 | match in_pair.as_rule() { 19 | Rule::keyframes_name => { 20 | name = in_pair.as_str().to_string(); 21 | } 22 | 23 | Rule::keyframe_selector_block => { 24 | let key_selector_block_inner_pairs = in_pair.into_inner(); 25 | 26 | for ksb_in_pair in key_selector_block_inner_pairs { 27 | match ksb_in_pair.as_rule() { 28 | // from/to/100% 29 | Rule::keyframe_selector => { 30 | current_selector = ksb_in_pair.as_str().to_string(); 31 | } 32 | 33 | // color: red; 34 | Rule::declaration => { 35 | if !current_selector.is_empty() { 36 | let declaration_inner = ksb_in_pair.clone().into_inner(); 37 | let mut variable_reference = String::new(); 38 | 39 | for dec_in_pair in declaration_inner { 40 | match dec_in_pair.as_rule() { 41 | Rule::variable_reference => { 42 | variable_reference = dec_in_pair.as_str().to_string(); 43 | } 44 | 45 | _ => {} 46 | } 47 | } 48 | 49 | let default_value = ksb_in_pair.as_str().trim().to_string(); 50 | 51 | if !variable_reference.is_empty() { 52 | let mut found_var = false; 53 | 54 | for md in meta_data { 55 | if let MetaData::Variables { name, value } = md { 56 | if name == variable_reference.trim_start_matches('&') { 57 | found_var = true; 58 | 59 | let replaced_value = default_value.replace( 60 | &variable_reference, 61 | value 62 | ); 63 | selector_to_declarations 64 | .entry(current_selector.clone()) 65 | .or_insert_with(Vec::new) 66 | .push(replaced_value); 67 | } 68 | } 69 | } 70 | 71 | if !found_var { 72 | let position = ksb_in_pair.line_col(); 73 | let line = position.0; 74 | let column = position.1; 75 | let context = get_error_context(raw_rcss, line, 2); 76 | 77 | let err = RCSSError::VariableError { 78 | file_path: input_path.into(), 79 | line, 80 | column, 81 | variable_name: variable_reference 82 | .trim_start_matches("&") 83 | .to_string(), 84 | message: format!( 85 | "Could not find variable: {}", 86 | variable_reference.trim_start_matches("&") 87 | ), 88 | context, 89 | }; 90 | 91 | display_error(&err); 92 | return Err(err); 93 | } 94 | } else { 95 | selector_to_declarations 96 | .entry(current_selector.clone()) 97 | .or_insert_with(Vec::new) 98 | .push(default_value); 99 | } 100 | } 101 | } 102 | 103 | Rule::right_curly_brace => { 104 | current_selector = String::new(); 105 | } 106 | 107 | _ => {} 108 | } 109 | } 110 | } 111 | 112 | _ => {} 113 | } 114 | } 115 | 116 | keyframes.insert(format!("@keyframes {}", name), selector_to_declarations); 117 | Ok(keyframes) 118 | } 119 | -------------------------------------------------------------------------------- /src/process_x/media_queries.rs: -------------------------------------------------------------------------------- 1 | use pest::iterators::Pair; 2 | use crate::{ compile::Rule, error::Result, process_x::rule_normal, MetaData }; 3 | use std::collections::HashMap; 4 | 5 | pub fn process_media_query( 6 | mut media_queries: HashMap>>, 7 | pair: Pair, 8 | 9 | // Arguments passed to rule_nomral 10 | meta_data: &Vec, 11 | raw_rcss: &str, 12 | input_path: &str 13 | ) -> Result>>> { 14 | let inner_pairs = pair.into_inner(); 15 | let mut condition = String::new(); 16 | let mut declarations: HashMap> = HashMap::new(); 17 | 18 | for inner_pair in inner_pairs { 19 | match inner_pair.as_rule() { 20 | Rule::media_condition => { 21 | condition = format!("@media {}", inner_pair.as_str().trim()); 22 | } 23 | 24 | Rule::rule_normal => { 25 | declarations = rule_normal::process_rule_normal( 26 | meta_data.clone(), 27 | declarations, 28 | inner_pair, 29 | raw_rcss, 30 | input_path 31 | )?; 32 | } 33 | 34 | _ => {} 35 | } 36 | } 37 | 38 | media_queries.insert(condition, declarations); 39 | 40 | Ok(media_queries) 41 | } 42 | -------------------------------------------------------------------------------- /src/process_x/rule_normal.rs: -------------------------------------------------------------------------------- 1 | use pest::iterators::Pair; 2 | use crate::{ 3 | compile::Rule, 4 | error::{ display_error, RCSSError, get_error_context }, 5 | MetaData, 6 | Result, 7 | }; 8 | use std::collections::HashMap; 9 | 10 | pub fn process_rule_normal( 11 | meta_data: Vec, 12 | mut declarations: HashMap>, 13 | pair: Pair, 14 | raw_rcss: &str, 15 | input_path: &str 16 | ) -> Result>> { 17 | let inner_pairs = pair.into_inner(); 18 | let mut current_selector: Vec = Vec::new(); 19 | 20 | for in_pair in inner_pairs { 21 | match in_pair.as_rule() { 22 | Rule::selector => { 23 | // if not the pseduo thing 24 | let selector_str = in_pair.as_str().trim(); 25 | 26 | if selector_str.starts_with("&::") || selector_str.starts_with("&:") { 27 | // Handle parent selector reference with pseudo-elements/classes 28 | if let Some(last) = current_selector.last_mut() { 29 | // Append the pseudo-element/class to the parent selector 30 | *last = format!("{}{}", last, selector_str.trim_start_matches('&')); 31 | } else { 32 | // This shouldn't happen with valid RCSS, but handle it gracefully 33 | current_selector.push(selector_str.trim_start_matches('&').to_string()); 34 | } 35 | } else { 36 | // Regular selector (no parent reference) 37 | current_selector.push(selector_str.to_string()); 38 | } 39 | } 40 | 41 | Rule::right_curly_brace => { 42 | if let Some(last) = current_selector.last_mut() { 43 | // Split by either ':' or '::', whichever comes first 44 | let mut split_idx = None; 45 | 46 | if let Some(idx) = last.find("::") { 47 | split_idx = Some(idx); 48 | } else if let Some(idx) = last.find(':') { 49 | // Ensure it's the first ':' from the left, not part of '::' 50 | if last.get(idx..idx + 2) != Some("::") { 51 | split_idx = Some(idx); 52 | } 53 | } 54 | 55 | if let Some(idx) = split_idx { 56 | *last = last[..idx].to_string(); 57 | continue; 58 | } 59 | } 60 | 61 | if !current_selector.is_empty() { 62 | current_selector.pop(); 63 | } 64 | } 65 | 66 | Rule::declaration => { 67 | // Extract "color" and "height" from the declaration 68 | let mut decl_str = in_pair.as_str().trim().to_string(); 69 | // Split by ':' to get property and value 70 | 71 | let mut referenced_vars = Vec::new(); 72 | 73 | if let Some((_property, value)) = decl_str.split_once(':') { 74 | for token in value.split_whitespace() { 75 | if token.starts_with("&") { 76 | let var = token 77 | .trim_start_matches('&') 78 | .trim_end_matches(|c| (c == ';' || c == ',' || c == ')')); 79 | 80 | referenced_vars.push(var); 81 | } 82 | } 83 | } 84 | 85 | // Collect all replacements to avoid borrowing issues 86 | let mut replacements = Vec::new(); 87 | for data in &meta_data { 88 | if let MetaData::Variables { name, value: var_value } = data { 89 | if referenced_vars.contains(&name.as_str()) { 90 | replacements.push((format!("&{}", name), var_value.clone())); 91 | referenced_vars.retain(|v| v != name); 92 | } 93 | } 94 | } 95 | 96 | for (pattern, replacement) in replacements { 97 | decl_str = decl_str.replace(&pattern, &replacement); 98 | } 99 | 100 | let joined_selector = current_selector.join(" "); 101 | let key = joined_selector.trim(); 102 | 103 | if let Some(values) = declarations.get_mut(key) { 104 | values.push(decl_str.clone()); 105 | } else { 106 | declarations.insert(key.to_string(), vec![decl_str.clone()]); 107 | } 108 | } 109 | 110 | Rule::user_created_function_call => { 111 | let user_created_func_inner_pairs = in_pair.clone().into_inner(); 112 | let mut func_name = String::new(); 113 | let mut func_declarations: Vec = Vec::new(); 114 | 115 | for ucfunc_in_pair in user_created_func_inner_pairs { 116 | match ucfunc_in_pair.as_rule() { 117 | Rule::function_name => { 118 | func_name = ucfunc_in_pair.as_str().trim().to_string(); 119 | } 120 | 121 | _ => {} 122 | } 123 | } 124 | 125 | for data in &meta_data { 126 | if let MetaData::Function { name, body } = data { 127 | if func_name == *name { 128 | func_declarations = body.clone(); 129 | } 130 | } 131 | } 132 | 133 | if func_declarations.is_empty() { 134 | let position = in_pair.line_col(); 135 | let line = position.0; 136 | let column = position.1; 137 | let context = get_error_context(raw_rcss, line, 2); 138 | 139 | let err = RCSSError::FunctionError { 140 | file_path: input_path.to_string().into(), 141 | function_name: func_name, 142 | message: "Function not declared in scope".to_string(), 143 | line: line, 144 | column: column, 145 | context: context, 146 | }; 147 | 148 | display_error(&err); 149 | 150 | return Err(err); 151 | } 152 | 153 | let joined_selector = current_selector.join(" "); 154 | 155 | let key = joined_selector.trim(); 156 | 157 | if let Some(values) = declarations.get_mut(key) { 158 | values.extend(func_declarations.clone()); 159 | } else { 160 | declarations.insert(key.to_string(), func_declarations.clone()); 161 | } 162 | } 163 | 164 | _ => {} 165 | } 166 | } 167 | 168 | Ok(declarations) 169 | } 170 | -------------------------------------------------------------------------------- /src/process_x/variables.rs: -------------------------------------------------------------------------------- 1 | use pest::iterators::Pair; 2 | use crate::{ MetaData, compile::Rule }; 3 | 4 | pub fn process_variable_declaration( 5 | mut meta_data: Vec, 6 | pair: Pair 7 | ) -> Vec { 8 | let inner_pairs = pair.into_inner(); 9 | 10 | let mut name = String::new(); 11 | let mut value = String::new(); 12 | 13 | for in_pair in inner_pairs { 14 | match in_pair.as_rule() { 15 | Rule::variable_name => { 16 | name = in_pair.as_str().to_string(); 17 | } 18 | 19 | Rule::string_literal => { 20 | value = in_pair.as_str().to_string(); 21 | } 22 | 23 | _ => {} 24 | } 25 | } 26 | 27 | if name.is_empty() || value.is_empty() { 28 | return meta_data; 29 | } 30 | 31 | value = value.trim_matches('"').to_string(); 32 | 33 | meta_data.push(MetaData::Variables { name: name, value: value }); 34 | 35 | meta_data 36 | } 37 | -------------------------------------------------------------------------------- /styles/css/components/Atoms/icon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | font-size: 32px; 3 | transition: all 0.4s ease; 4 | } 5 | 6 | .icon:hover { 7 | cursor: pointer; 8 | filter: drop-shadow(0 2px 8px rgba(214, 125, 116, 0.3)); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /styles/rcss/common.rcss: -------------------------------------------------------------------------------- 1 | let var: "rgba(214, 125, 116, 0.3)"; -------------------------------------------------------------------------------- /styles/rcss/components/Atoms/icon.module.rcss: -------------------------------------------------------------------------------- 1 | use common; 2 | 3 | .icon { 4 | font-size: 32px; 5 | transition: all 0.4s ease; 6 | 7 | &:hover { 8 | filter: drop-shadow(0 2px 8px &var); 9 | cursor: pointer; 10 | } 11 | } --------------------------------------------------------------------------------