sqruff_lib_core/
helpers.rs1use 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
45pub fn skip_last<T>(mut iter: impl Iterator<Item = T>) -> impl Iterator<Item = T> {
46 let last = iter.next();
47 iter.scan(last, |state, item| std::mem::replace(state, Some(item)))
48}
49
50pub fn normalize(p: &Path) -> PathBuf {
52 let mut stack: Vec<Component> = vec![];
53
54 for component in p.components() {
59 match component {
60 Component::CurDir => {}
62
63 Component::ParentDir => {
65 let top = stack.last().copied();
67
68 match top {
69 Some(c) => {
71 match c {
72 Component::Prefix(_) => {
74 stack.push(component);
75 }
76
77 Component::RootDir => {}
79
80 Component::CurDir => {
83 unreachable!();
84 }
85
86 Component::ParentDir => {
90 stack.push(component);
91 }
92
93 Component::Normal(_) => {
95 let _ = stack.pop();
96 }
97 }
98 }
99
100 None => {
102 stack.push(component);
103 }
104 }
105 }
106
107 _ => {
109 stack.push(component);
110 }
111 }
112 }
113
114 if stack.is_empty() {
116 return PathBuf::from(".");
117 }
118
119 let mut norm_path = PathBuf::new();
120
121 for item in &stack {
122 norm_path.push(item);
123 }
124
125 norm_path
126}
127
128pub fn enter_panic(context: String) -> PanicContext {
129 static ONCE: Once = Once::new();
130 ONCE.call_once(PanicContext::init);
131
132 with_ctx(|ctx| ctx.push(context));
133 PanicContext { _priv: () }
134}
135
136#[must_use]
137pub struct PanicContext {
138 _priv: (),
139}
140
141impl PanicContext {
142 #[allow(clippy::print_stderr)]
143 fn init() {
144 let default_hook = panic::take_hook();
145 let hook = move |panic_info: &panic::PanicHookInfo<'_>| {
146 with_ctx(|ctx| {
147 if !ctx.is_empty() {
148 eprintln!("Panic context:");
149 for frame in ctx.iter() {
150 eprintln!("> {frame}\n");
151 }
152 }
153 default_hook(panic_info);
154 });
155 };
156 panic::set_hook(Box::new(hook));
157 }
158}
159
160impl Drop for PanicContext {
161 fn drop(&mut self) {
162 with_ctx(|ctx| assert!(ctx.pop().is_some()));
163 }
164}
165
166fn with_ctx(f: impl FnOnce(&mut Vec<String>)) {
167 thread_local! {
168 static CTX: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
169 }
170 CTX.with(|ctx| f(&mut ctx.borrow_mut()));
171}