├── .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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Rusty Cascading Style Sheets (RCSS)
16 |
17 |
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 |
87 |
88 | Installation
89 |
90 |
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 | }
--------------------------------------------------------------------------------