Skip to main content

sqruff_lib_core/
helpers.rs

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