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 normalize(p: &Path) -> PathBuf {
47 let mut stack: Vec<Component> = vec![];
48
49 for component in p.components() {
54 match component {
55 Component::CurDir => {}
57
58 Component::ParentDir => {
60 let top = stack.last().copied();
62
63 match top {
64 Some(c) => {
66 match c {
67 Component::Prefix(_) => {
69 stack.push(component);
70 }
71
72 Component::RootDir => {}
74
75 Component::CurDir => {
78 unreachable!();
79 }
80
81 Component::ParentDir => {
85 stack.push(component);
86 }
87
88 Component::Normal(_) => {
90 let _ = stack.pop();
91 }
92 }
93 }
94
95 None => {
97 stack.push(component);
98 }
99 }
100 }
101
102 _ => {
104 stack.push(component);
105 }
106 }
107 }
108
109 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}