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