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