sqruff_lib_core/
helpers.rs1use 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
46pub fn normalize(p: &Path) -> PathBuf {
48 let mut stack: Vec<Component> = vec![];
49
50 for component in p.components() {
55 match component {
56 Component::CurDir => {}
58
59 Component::ParentDir => {
61 let top = stack.last().copied();
63
64 match top {
65 Some(c) => {
67 match c {
68 Component::Prefix(_) => {
70 stack.push(component);
71 }
72
73 Component::RootDir => {}
75
76 Component::CurDir => {
79 unreachable!();
80 }
81
82 Component::ParentDir => {
86 stack.push(component);
87 }
88
89 Component::Normal(_) => {
91 let _ = stack.pop();
92 }
93 }
94 }
95
96 None => {
98 stack.push(component);
99 }
100 }
101 }
102
103 _ => {
105 stack.push(component);
106 }
107 }
108 }
109
110 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}