sqruff_lib_core/
helpers.rs

1use std::cell::RefCell;
2use std::hash::BuildHasherDefault;
3use std::panic;
4use std::path::{Component, Path, PathBuf};
5use std::sync::Once;
6
7use crate::parser::matchable::{Matchable, MatchableTraitImpl};
8
9pub type IndexMap<K, V> = indexmap::IndexMap<K, V, BuildHasherDefault<ahash::AHasher>>;
10pub type IndexSet<V> = indexmap::IndexSet<V, BuildHasherDefault<ahash::AHasher>>;
11
12pub trait ToMatchable: Sized {
13    fn to_matchable(self) -> Matchable;
14}
15
16impl<T: Into<MatchableTraitImpl>> ToMatchable for T {
17    fn to_matchable(self) -> Matchable {
18        Matchable::new(self.into())
19    }
20}
21
22pub fn capitalize(s: &str) -> String {
23    assert!(s.is_ascii());
24
25    let mut chars = s.chars();
26    let Some(first_char) = chars.next() else {
27        return String::new();
28    };
29
30    first_char
31        .to_uppercase()
32        .chain(chars.map(|ch| ch.to_ascii_lowercase()))
33        .collect()
34}
35
36pub trait Config: Sized {
37    fn config(mut self, f: impl FnOnce(&mut Self)) -> Self {
38        f(&mut self);
39        self
40    }
41}
42
43impl<T> Config for T {}
44
45// https://github.com/rust-lang/rfcs/issues/2208#issuecomment-342679694
46pub fn normalize(p: &Path) -> PathBuf {
47    let mut stack: Vec<Component> = vec![];
48
49    // We assume .components() removes redundant consecutive path separators.
50    // Note that .components() also does some normalization of '.' on its own
51    // anyways. This '.' normalization happens to be compatible with the
52    // approach below.
53    for component in p.components() {
54        match component {
55            // Drop CurDir components, do not even push onto the stack.
56            Component::CurDir => {}
57
58            // For ParentDir components, we need to use the contents of the stack.
59            Component::ParentDir => {
60                // Look at the top element of stack, if any.
61                let top = stack.last().copied();
62
63                match top {
64                    // A component is on the stack, need more pattern matching.
65                    Some(c) => {
66                        match c {
67                            // Push the ParentDir on the stack.
68                            Component::Prefix(_) => {
69                                stack.push(component);
70                            }
71
72                            // The parent of a RootDir is itself, so drop the ParentDir (no-op).
73                            Component::RootDir => {}
74
75                            // A CurDir should never be found on the stack, since they are dropped
76                            // when seen.
77                            Component::CurDir => {
78                                unreachable!();
79                            }
80
81                            // If a ParentDir is found, it must be due to it piling up at the start
82                            // of a path. Push the new ParentDir onto
83                            // the stack.
84                            Component::ParentDir => {
85                                stack.push(component);
86                            }
87
88                            // If a Normal is found, pop it off.
89                            Component::Normal(_) => {
90                                let _ = stack.pop();
91                            }
92                        }
93                    }
94
95                    // Stack is empty, so path is empty, just push.
96                    None => {
97                        stack.push(component);
98                    }
99                }
100            }
101
102            // All others, simply push onto the stack.
103            _ => {
104                stack.push(component);
105            }
106        }
107    }
108
109    // If an empty PathBuf would be return, instead return CurDir ('.').
110    if stack.is_empty() {
111        return PathBuf::from(".");
112    }
113
114    let mut norm_path = PathBuf::new();
115
116    for item in &stack {
117        norm_path.push(item);
118    }
119
120    norm_path
121}
122
123pub fn enter_panic(context: String) -> PanicContext {
124    static ONCE: Once = Once::new();
125    ONCE.call_once(PanicContext::init);
126
127    with_ctx(|ctx| ctx.push(context));
128    PanicContext { _priv: () }
129}
130
131#[must_use]
132pub struct PanicContext {
133    _priv: (),
134}
135
136impl PanicContext {
137    #[allow(clippy::print_stderr)]
138    fn init() {
139        let default_hook = panic::take_hook();
140        let hook = move |panic_info: &panic::PanicHookInfo<'_>| {
141            with_ctx(|ctx| {
142                if !ctx.is_empty() {
143                    eprintln!("Panic context:");
144                    for frame in ctx.iter() {
145                        eprintln!("> {frame}\n");
146                    }
147                }
148                default_hook(panic_info);
149            });
150        };
151        panic::set_hook(Box::new(hook));
152    }
153}
154
155impl Drop for PanicContext {
156    fn drop(&mut self) {
157        with_ctx(|ctx| assert!(ctx.pop().is_some()));
158    }
159}
160
161fn with_ctx(f: impl FnOnce(&mut Vec<String>)) {
162    thread_local! {
163        static CTX: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
164    }
165    CTX.with(|ctx| f(&mut ctx.borrow_mut()));
166}