Skip to main content

flams_utils/
lib.rs

1//#![feature(ptr_as_ref_unchecked)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4pub mod binary;
5#[cfg(feature = "async")]
6pub mod change_listener;
7pub mod escaping;
8pub mod gc;
9pub mod globals;
10pub mod id_counters;
11mod inner_arc;
12pub mod logs;
13pub mod parsing;
14//pub mod regex;
15pub mod settings;
16pub mod sourcerefs;
17//pub mod time;
18mod treelike;
19pub mod vecmap;
20//pub mod file_id;
21
22use std::hint::unreachable_unchecked;
23
24pub use parking_lot;
25pub use triomphe;
26
27pub mod prelude {
28    pub use super::vecmap::{VecMap, VecSet};
29    pub type HMap<K, V> = rustc_hash::FxHashMap<K, V>;
30    pub type HSet<V> = rustc_hash::FxHashSet<V>;
31    pub use crate::inner_arc::InnerArc;
32    pub use crate::treelike::*;
33}
34
35pub(crate) mod __private {
36    pub trait Sealed {}
37}
38
39pub trait CharExt {
40    fn into_bytes(self) -> arrayvec::ArrayVec<u8, 4>;
41}
42
43impl CharExt for char {
44    fn into_bytes(self) -> arrayvec::ArrayVec<u8, 4> {
45        const TAG_CONT: u8 = 0b1000_0000;
46        const TAG_TWO_B: u8 = 0b1100_0000;
47        const TAG_THREE_B: u8 = 0b1110_0000;
48        const TAG_FOUR_B: u8 = 0b1111_0000;
49        let num = self as u32;
50        let mut ret = arrayvec::ArrayVec::new();
51
52        // SAFETY: character length is between 1 and 4
53        unsafe {
54            match self.len_utf8() {
55                1 => {
56                    #[allow(clippy::cast_possible_truncation)]
57                    ret.push_unchecked(num as u8);
58                }
59                2 => {
60                    ret.push_unchecked((num >> 6 & 0x1F) as u8 | TAG_TWO_B);
61                    ret.push_unchecked((num & 0x3F) as u8 | TAG_CONT);
62                }
63                3 => {
64                    ret.push_unchecked((num >> 12 & 0x0F) as u8 | TAG_THREE_B);
65                    ret.push_unchecked((num >> 6 & 0x3F) as u8 | TAG_CONT);
66                    ret.push_unchecked((num & 0x3F) as u8 | TAG_CONT);
67                }
68                4 => {
69                    ret = [
70                        (num >> 18 & 0x07) as u8 | TAG_FOUR_B,
71                        (num >> 12 & 0x3F) as u8 | TAG_CONT,
72                        (num >> 6 & 0x3F) as u8 | TAG_CONT,
73                        (num & 0x3F) as u8 | TAG_CONT,
74                    ]
75                    .into();
76                }
77                _ => unreachable_unchecked(),
78            }
79        }
80        ret
81    }
82}
83
84#[cfg(target_family = "wasm")]
85type Str = String;
86#[cfg(not(target_family = "wasm"))]
87type Str = Box<str>;
88
89pub fn hashstr<A: std::hash::Hash>(prefix: &str, a: &A) -> String {
90    use std::hash::BuildHasher;
91    let h = rustc_hash::FxBuildHasher.hash_one(a);
92    format!("{prefix}{h:02x}")
93}
94
95#[cfg(feature = "tokio")]
96pub fn background<F: FnOnce() + Send + 'static>(f: F) {
97    let span = tracing::Span::current();
98    tokio::task::spawn_blocking(move || span.in_scope(f));
99}
100
101pub fn in_span<F: FnOnce() -> R, R>(f: F) -> impl FnOnce() -> R {
102    let span = tracing::Span::current();
103    move || {
104        let _span = span.enter();
105        f()
106    }
107}
108/*
109#[cfg(feature = "serde")]
110pub trait Hexable: Sized {
111    /// #### Errors
112    fn as_hex(&self) -> eyre::Result<String>;
113    /// #### Errors
114    fn from_hex(s: &str) -> eyre::Result<Self>;
115}
116#[cfg(feature = "serde")]
117impl<T: Sized + serde::Serialize + for<'de> serde::Deserialize<'de>> Hexable for T {
118    fn as_hex(&self) -> eyre::Result<String> {
119        use std::fmt::Write;
120        let bc = bincode::serde::encode_to_vec(self, bincode::config::standard())?;
121        let mut ret = String::with_capacity(bc.len() * 2);
122        for b in bc {
123            write!(ret, "{b:02X}")?;
124        }
125        Ok(ret)
126    }
127    fn from_hex(s: &str) -> eyre::Result<Self> {
128        let bytes: Result<Vec<_>, _> = if s.len().is_multiple_of(2) {
129            (0..s.len())
130                .step_by(2)
131                .filter_map(|i| s.get(i..i + 2))
132                .map(|sub| u8::from_str_radix(sub, 16))
133                .collect()
134        } else {
135            return Err(eyre::eyre!("Incompatible string length"));
136        };
137        bincode::serde::decode_from_slice(&bytes?, bincode::config::standard())
138            .map(|(r, _)| r)
139            .map_err(Into::into)
140    }
141}
142*/
143pub mod fs {
144    use std::path::Path;
145
146    use eyre::Context;
147
148    /// #### Errors
149    pub fn copy_dir_all(src: &Path, dst: &Path) -> eyre::Result<()> {
150        std::fs::create_dir_all(dst)
151            .wrap_err_with(|| format!("Error creating {}", dst.display()))?;
152        for entry in
153            std::fs::read_dir(src).wrap_err_with(|| format!("Error reading {}", src.display()))?
154        {
155            let entry = entry
156                .wrap_err_with(|| format!("Error getting file entry for {}", src.display()))?;
157            let ty = entry.file_type().wrap_err_with(|| {
158                format!("Error determining file type of {}", entry.path().display())
159            })?;
160            let target = dst.join(entry.file_name());
161            if ty.is_dir() {
162                copy_dir_all(&entry.path(), &target)?;
163            } else {
164                let md = entry.metadata().wrap_err_with(|| {
165                    format!("Error obtaining metatada for {}", entry.path().display())
166                })?;
167                std::fs::copy(entry.path(), &target).wrap_err_with(|| {
168                    format!(
169                        "Error copying {} to {}",
170                        entry.path().display(),
171                        target.display()
172                    )
173                })?;
174                let mtime = filetime::FileTime::from_last_modification_time(&md);
175                filetime::set_file_mtime(&target, mtime).wrap_err_with(|| {
176                    format!(
177                        "Error setting file modification time for {}",
178                        target.display()
179                    )
180                })?;
181            }
182        }
183        Ok(())
184    }
185}
186/*
187#[derive(Debug, Clone, PartialEq, Eq)]
188#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
189#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
190#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
191pub enum CSS {
192    Link(#[cfg_attr(feature = "wasm", tsify(type = "string"))] Str),
193    Inline(#[cfg_attr(feature = "wasm", tsify(type = "string"))] Str),
194    Class {
195        #[cfg_attr(feature = "wasm", tsify(type = "string"))]
196        name: Str,
197        #[cfg_attr(feature = "wasm", tsify(type = "string"))]
198        css: Str,
199    },
200}
201impl PartialOrd for CSS {
202    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
203        Some(self.cmp(other))
204    }
205}
206impl Ord for CSS {
207    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
208        fn classnum(s: &str) -> u8 {
209            match s {
210                s if s.starts_with("ftml-subproblem") => 1,
211                s if s.starts_with("ftml-problem") => 2,
212                s if s.starts_with("ftml-example") => 3,
213                s if s.starts_with("ftml-definition") => 4,
214                s if s.starts_with("ftml-paragraph") => 5,
215                "ftml-subsubsection" => 6,
216                "ftml-subsection" => 7,
217                "ftml-section" => 8,
218                "ftml-chapter" => 9,
219                "ftml-part" => 10,
220                _ => 0,
221            }
222        }
223        use std::cmp::Ordering;
224        match (self, other) {
225            (Self::Link(l1), Self::Link(l2)) | (Self::Inline(l1), Self::Inline(l2)) => l1.cmp(l2),
226            (Self::Link(_), Self::Inline(_))
227            | (Self::Link(_) | Self::Inline(_), Self::Class { .. }) => Ordering::Less,
228            (Self::Inline(_), Self::Link(_))
229            | (Self::Class { .. }, Self::Inline(_) | Self::Link(_)) => Ordering::Greater,
230            (Self::Class { name: n1, css: c1 }, Self::Class { name: n2, css: c2 }) => {
231                (classnum(n1), n1, c1).cmp(&(classnum(n2), n2, c2))
232            }
233        }
234    }
235}
236impl CSS {
237    pub fn merge(v: Vec<Self>) -> Vec<Self> {
238        use lightningcss::traits::ToCss;
239        use lightningcss::{
240            printer::PrinterOptions,
241            rules::{CssRule, CssRuleList},
242            selector::Component,
243            stylesheet::{MinifyOptions, ParserOptions, StyleSheet},
244        };
245
246        let mut links = Vec::new();
247        let mut strings = Vec::new();
248        for c in v {
249            match c {
250                Self::Link(_) => links.push(c),
251                Self::Inline(css) | Self::Class { css, .. } => strings.push(css),
252            }
253        }
254
255        let mut sheet = StyleSheet::new(
256            Vec::new(),
257            CssRuleList(Vec::new()),
258            ParserOptions::default(),
259        );
260        let inlines = smallvec::SmallVec::<_, 2>::new();
261        for s in &strings {
262            if let Ok(rs) = StyleSheet::parse(s, ParserOptions::default()) {
263                sheet.rules.0.extend(rs.rules.0.into_iter());
264            } else {
265                tracing::warn!("Not class-able: {s}");
266            }
267        }
268        let _ = sheet.minify(MinifyOptions::default());
269
270        let mut classes = Vec::new();
271        for rule in std::mem::take(&mut sheet.rules.0) {
272            match rule {
273                CssRule::Style(style) => {
274                    if style.vendor_prefix.is_empty()
275                        && style.selectors.0.len() == 1
276                        && style.selectors.0[0].len() == 1
277                        && matches!(
278                            style.selectors.0[0].iter().next(),
279                            Some(Component::Class(_))
280                        )
281                    {
282                        let Some(Component::Class(class_name)) = style.selectors.0[0].iter().next()
283                        else {
284                            impossible!()
285                        };
286                        if let Ok(s) = style.to_css_string(PrinterOptions::default()) {
287                            classes.push(Self::Class {
288                                name: class_name.to_string().into(),
289                                css: s.into(),
290                            });
291                        } else {
292                            tracing::warn!("Illegal CSS: {style:?}");
293                        }
294                    } else {
295                        if let Ok(s) = style.to_css_string(PrinterOptions::default()) {
296                            tracing::warn!("Not class-able: {s}");
297                            links.push(Self::Inline(s.into()));
298                        } else {
299                            tracing::warn!("Illegal CSS: {style:?}");
300                        }
301                    }
302                }
303                rule => {
304                    if let Ok(s) = rule.to_css_string(PrinterOptions::default()) {
305                        tracing::warn!("Not class-able: {s}");
306                        links.push(Self::Inline(s.into()));
307                    } else {
308                        tracing::warn!("Illegal CSS: {rule:?}");
309                    }
310                }
311            }
312        }
313        drop(sheet);
314
315        links.extend(inlines.into_iter().map(|i| Self::Inline(strings.remove(i))));
316        links.extend(classes);
317        links
318    }
319
320    #[must_use]
321    pub fn split(css: &str) -> Vec<Self> {
322        use lightningcss::traits::ToCss;
323        use lightningcss::{
324            printer::PrinterOptions,
325            rules::CssRule,
326            selector::Component,
327            stylesheet::{ParserOptions, StyleSheet},
328        };
329        let Ok(ruleset) = StyleSheet::parse(css, ParserOptions::default()) else {
330            tracing::warn!("Not class-able: {css}");
331            return vec![Self::Inline(css.to_string().into())];
332        };
333        if ruleset.sources.iter().any(|s| !s.is_empty()) {
334            tracing::warn!("Not class-able: {css}");
335            return vec![Self::Inline(css.to_string().into())];
336        }
337        ruleset
338            .rules
339            .0
340            .into_iter()
341            .filter_map(|rule| match rule {
342                CssRule::Style(style) => {
343                    if style.vendor_prefix.is_empty()
344                        && style.selectors.0.len() == 1
345                        && style.selectors.0[0].len() == 1
346                        && matches!(
347                            style.selectors.0[0].iter().next(),
348                            Some(Component::Class(_))
349                        )
350                    {
351                        let Some(Component::Class(class_name)) = style.selectors.0[0].iter().next()
352                        else {
353                            impossible!()
354                        };
355                        style
356                            .to_css_string(PrinterOptions::default())
357                            .ok()
358                            .map(|s| Self::Class {
359                                name: class_name.to_string().into(),
360                                css: s.into(),
361                            })
362                    } else {
363                        style
364                            .to_css_string(PrinterOptions::default())
365                            .ok()
366                            .map(|s| {
367                                tracing::warn!("Not class-able: {s}");
368                                Self::Inline(s.into())
369                            })
370                    }
371                }
372                o => o.to_css_string(PrinterOptions::default()).ok().map(|s| {
373                    tracing::warn!("Not class-able: {s}");
374                    Self::Inline(s.into())
375                }),
376            })
377            .collect()
378    }
379}
380 */
381
382#[macro_export]
383macro_rules! impossible {
384    () => {{
385        #[cfg(debug_assertions)]
386        {
387            unreachable!()
388        }
389        #[cfg(not(debug_assertions))]
390        {
391            unsafe { std::hint::unreachable_unchecked() }
392        }
393    }};
394    ($s:literal) => {
395        #[cfg(debug_assertions)]
396        {
397            panic!($s)
398        }
399        #[cfg(not(debug_assertions))]
400        {
401            unsafe { std::hint::unreachable_unchecked() }
402        }
403    };
404    (?) => {
405        unreachable!()
406    };
407    (? $s:literal) => {{ panic!($s) }};
408}
409
410#[macro_export]
411macro_rules! unwrap {
412    ($e: expr) => { $e.unwrap_or_else(|| {$crate::impossible!();}) };
413    (? $e: expr) => { $e.unwrap_or_else(|| {$crate::impossible!(?);}) };
414    ($e: expr;$l:literal) => { $e.unwrap_or_else(|| {$crate::impossible!($l);}) };
415    (? $e: expr;$l:literal) => { $e.unwrap_or_else(|| {$crate::impossible!(? $l);}) };
416}
417
418#[cfg(feature = "serde")]
419pub trait CondSerialize: serde::Serialize {}
420#[cfg(feature = "serde")]
421impl<T: serde::Serialize> CondSerialize for T {}
422
423#[cfg(not(feature = "serde"))]
424pub trait CondSerialize {}
425#[cfg(not(feature = "serde"))]
426impl<T> CondSerialize for T {}
427
428/*
429#[allow(clippy::unwrap_used)]
430#[allow(clippy::cognitive_complexity)]
431#[allow(clippy::similar_names)]
432#[test]
433fn css_things() {
434    use lightningcss::traits::ToCss;
435    use lightningcss::{
436        printer::PrinterOptions,
437        rules::CssRule,
438        selector::Component,
439        stylesheet::{MinifyOptions, ParserOptions, StyleSheet},
440    };
441    tracing_subscriber::fmt().init();
442    let css = include_str!("../../../resources/assets/rustex-v0-1-16.css");
443    let rules = StyleSheet::parse(css, ParserOptions::default()).unwrap();
444    let roundtrip = rules.to_css(PrinterOptions::default()).unwrap();
445    tracing::info!("{}", roundtrip.code);
446    let test = "
447        .ftml-paragraph {
448            > .ftml-title {
449                font-weight: bold;
450            }
451            margin: 0;
452        }
453    ";
454    let mut ruleset = StyleSheet::parse(test, ParserOptions::default()).unwrap();
455    ruleset.minify(MinifyOptions::default()).unwrap();
456    assert!(ruleset.sources.iter().all(String::is_empty));
457    tracing::info!("Result: {ruleset:#?}");
458    for rule in ruleset.rules.0 {
459        match rule {
460            CssRule::Style(style) => {
461                assert!(style.vendor_prefix.is_empty());
462                assert!(style.selectors.0.len() == 1);
463                assert!(style.selectors.0[0].len() == 1);
464                tracing::info!(
465                    "Here: {}",
466                    style.to_css_string(PrinterOptions::default()).unwrap()
467                );
468                let sel = style.selectors.0[0].iter().next().unwrap();
469                assert!(matches!(sel, Component::Class(_)));
470                let Component::Class(cls) = sel else {
471                    impossible!()
472                };
473                let cls_str = &**cls;
474                tracing::info!("Class: {cls_str}");
475            }
476            o => panic!("Unexpected rule: {o:#?}"),
477        }
478    }
479}
480
481pub trait PathExt {
482    const PATH_SEPARATOR: char;
483    fn as_slash_str(&self) -> String;
484    fn same_fs_as<P: AsRef<std::path::Path>>(&self, other: &P) -> bool;
485    fn rename_safe<P: AsRef<std::path::Path>>(&self, target: &P) -> eyre::Result<()>;
486}
487impl<T: AsRef<std::path::Path>> PathExt for T {
488    #[cfg(target_os = "windows")]
489    const PATH_SEPARATOR: char = '\\';
490    #[cfg(not(target_os = "windows"))]
491    const PATH_SEPARATOR: char = '/';
492    fn as_slash_str(&self) -> String {
493        if cfg!(windows) {
494            unwrap!(self.as_ref().as_os_str().to_str()).replace('\\', "/")
495        } else {
496            unwrap!(self.as_ref().as_os_str().to_str()).to_string()
497        }
498    }
499    #[cfg(target_os = "windows")]
500    fn same_fs_as<P: AsRef<std::path::Path>>(&self, other: &P) -> bool {
501        let Some(p1) = self
502            .as_ref()
503            .components()
504            .next()
505            .and_then(|c| c.as_os_str().to_str())
506        else {
507            return false;
508        };
509        let Some(p2) = other
510            .as_ref()
511            .components()
512            .next()
513            .and_then(|c| c.as_os_str().to_str())
514        else {
515            return false;
516        };
517        p1 == p2
518    }
519    #[cfg(target_arch = "wasm32")]
520    fn same_fs_as<P: AsRef<std::path::Path>>(&self, other: &P) -> bool {
521        impossible!()
522    }
523
524    #[cfg(not(any(target_os = "windows", target_arch = "wasm32")))]
525    fn same_fs_as<P: AsRef<std::path::Path>>(&self, other: &P) -> bool {
526        use std::os::unix::fs::MetadataExt;
527        fn existent_parent(p: &std::path::Path) -> &std::path::Path {
528            if p.exists() {
529                return p;
530            }
531            existent_parent(p.parent().unwrap_or_else(|| unreachable!()))
532        }
533        let p1 = existent_parent(self.as_ref());
534        let p2 = existent_parent(other.as_ref());
535        let md1 = p1.metadata().unwrap_or_else(|_| unreachable!());
536        let md2 = p2.metadata().unwrap_or_else(|_| unreachable!());
537        md1.dev() == md2.dev()
538    }
539    fn rename_safe<P: AsRef<std::path::Path>>(&self, target: &P) -> eyre::Result<()> {
540        Ok(if self.same_fs_as(target) {
541            std::fs::rename(self.as_ref(), target.as_ref())?
542        } else {
543            crate::fs::copy_dir_all(self.as_ref(), target.as_ref())?
544        })
545    }
546}
547 */