rust/crates/stdx/src/lib.rs

261 lines
6.3 KiB
Rust
Raw Normal View History

//! Missing batteries for standard libraries.
use std::{cmp::Ordering, ops, time::Instant};
2020-07-13 08:54:12 -05:00
mod macros;
pub mod process;
pub mod panic_context;
2020-07-13 08:54:12 -05:00
2021-01-26 13:11:12 -06:00
pub use always_assert::{always, never};
2020-04-06 09:58:16 -05:00
#[inline(always)]
pub fn is_ci() -> bool {
option_env!("CI").is_some()
}
2020-07-10 20:04:37 -05:00
#[must_use]
2020-04-10 13:46:42 -05:00
pub fn timeit(label: &'static str) -> impl Drop {
struct Guard {
label: &'static str,
start: Instant,
}
impl Drop for Guard {
fn drop(&mut self) {
2020-08-18 06:20:17 -05:00
eprintln!("{}: {:.2?}", self.label, self.start.elapsed())
2020-04-10 13:46:42 -05:00
}
}
Guard { label, start: Instant::now() }
}
2020-05-03 13:35:21 -05:00
/// Prints backtrace to stderr, useful for debugging.
#[cfg(feature = "backtrace")]
pub fn print_backtrace() {
let bt = backtrace::Backtrace::new();
eprintln!("{:?}", bt);
}
#[cfg(not(feature = "backtrace"))]
pub fn print_backtrace() {
eprintln!(
r#"Enable the backtrace feature.
Uncomment `default = [ "backtrace" ]` in `crates/stdx/Cargo.toml`.
"#
);
}
pub fn to_lower_snake_case(s: &str) -> String {
to_snake_case(s, char::to_ascii_lowercase)
}
pub fn to_upper_snake_case(s: &str) -> String {
to_snake_case(s, char::to_ascii_uppercase)
}
fn to_snake_case<F: Fn(&char) -> char>(s: &str, change_case: F) -> String {
2020-05-03 13:35:21 -05:00
let mut buf = String::with_capacity(s.len());
let mut prev = false;
for c in s.chars() {
2020-10-03 08:45:16 -05:00
// `&& prev` is required to not insert `_` before the first symbol.
2020-05-03 13:35:21 -05:00
if c.is_ascii_uppercase() && prev {
2020-10-03 08:45:16 -05:00
// This check is required to not translate `Weird_Case` into `weird__case`.
if !buf.ends_with('_') {
2020-10-03 08:45:16 -05:00
buf.push('_')
}
2020-05-03 13:35:21 -05:00
}
prev = true;
buf.push(change_case(&c));
2020-05-03 13:35:21 -05:00
}
buf
}
2020-05-19 15:25:07 -05:00
pub fn replace(buf: &mut String, from: char, to: &str) {
if !buf.contains(from) {
return;
}
// FIXME: do this in place.
*buf = buf.replace(from, to)
}
2020-07-30 15:19:58 -05:00
// https://github.com/rust-lang/rust/issues/74773
pub fn split_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
let mut split = haystack.splitn(2, delim);
let prefix = split.next()?;
let suffix = split.next()?;
Some((prefix, suffix))
}
pub fn rsplit_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
let mut split = haystack.rsplitn(2, delim);
let suffix = split.next()?;
let prefix = split.next()?;
Some((prefix, suffix))
}
pub fn trim_indent(mut text: &str) -> String {
if text.starts_with('\n') {
text = &text[1..];
}
let indent = text
.lines()
.filter(|it| !it.trim().is_empty())
.map(|it| it.len() - it.trim_start().len())
.min()
.unwrap_or(0);
lines_with_ends(text)
.map(
|line| {
if line.len() <= indent {
line.trim_start_matches(' ')
} else {
&line[indent..]
}
},
)
.collect()
}
pub fn lines_with_ends(text: &str) -> LinesWithEnds {
LinesWithEnds { text }
}
pub struct LinesWithEnds<'a> {
text: &'a str,
}
impl<'a> Iterator for LinesWithEnds<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
if self.text.is_empty() {
return None;
}
let idx = self.text.find('\n').map_or(self.text.len(), |it| it + 1);
let (res, next) = self.text.split_at(idx);
self.text = next;
Some(res)
}
}
/// Returns `idx` such that:
///
Fix warnings when running `cargo doc --document-private-items` These were the warnings previously: ``` warning: could not parse code block as Rust code --> crates/stdx/src/lib.rs:137:9 | 137 | /// ∀ x in slice[..idx]: pred(x) | _________^ 138 | | /// && ∀ x in slice[idx..]: !pred(x) | |____^ | = note: error from rustc: unknown start of token: \u{2200} warning: 1 warning emitted warning: unresolved link to `package` --> crates/base_db/src/input.rs:181:15 | 181 | /// it's [package].name, can be different for other project types or even | ^^^^^^^ no item named `package` in scope | = note: `#[warn(broken_intra_doc_links)]` on by default = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: unresolved link to `package` --> crates/base_db/src/input.rs:181:15 | 181 | /// it's [package].name, can be different for other project types or even | ^^^^^^^ no item named `package` in scope | = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: 2 warnings emitted warning: unresolved link to `package` --> crates/base_db/src/input.rs:181:15 | 181 | /// it's [package].name, can be different for other project types or even | ^^^^^^^ no item named `package` in scope | = note: `#[warn(broken_intra_doc_links)]` on by default = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: unresolved link to `package` --> crates/base_db/src/input.rs:181:15 | 181 | /// it's [package].name, can be different for other project types or even | ^^^^^^^ no item named `package` in scope | = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: 2 warnings emitted ``` This does *not* fix the following warning, because it is actually rust code and rustdoc is being over eager: ``` warning: Rust code block is empty --> crates/parser/src/grammar.rs:16:5 | 16 | //! ``` | _____^ 17 | | //! // test function_with_zero_parameters 18 | | //! // fn foo() {} 19 | | //! ``` | |_______^ | help: mark blocks that do not contain Rust code as text | 16 | //! ```text | ^^^^^^^ ``` https://github.com/rust-lang/rust/pull/79816 should make this configurable so the warning can be `allow`ed.
2021-01-18 15:44:40 -06:00
/// ```text
/// ∀ x in slice[..idx]: pred(x)
/// && ∀ x in slice[idx..]: !pred(x)
Fix warnings when running `cargo doc --document-private-items` These were the warnings previously: ``` warning: could not parse code block as Rust code --> crates/stdx/src/lib.rs:137:9 | 137 | /// ∀ x in slice[..idx]: pred(x) | _________^ 138 | | /// && ∀ x in slice[idx..]: !pred(x) | |____^ | = note: error from rustc: unknown start of token: \u{2200} warning: 1 warning emitted warning: unresolved link to `package` --> crates/base_db/src/input.rs:181:15 | 181 | /// it's [package].name, can be different for other project types or even | ^^^^^^^ no item named `package` in scope | = note: `#[warn(broken_intra_doc_links)]` on by default = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: unresolved link to `package` --> crates/base_db/src/input.rs:181:15 | 181 | /// it's [package].name, can be different for other project types or even | ^^^^^^^ no item named `package` in scope | = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: 2 warnings emitted warning: unresolved link to `package` --> crates/base_db/src/input.rs:181:15 | 181 | /// it's [package].name, can be different for other project types or even | ^^^^^^^ no item named `package` in scope | = note: `#[warn(broken_intra_doc_links)]` on by default = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: unresolved link to `package` --> crates/base_db/src/input.rs:181:15 | 181 | /// it's [package].name, can be different for other project types or even | ^^^^^^^ no item named `package` in scope | = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: 2 warnings emitted ``` This does *not* fix the following warning, because it is actually rust code and rustdoc is being over eager: ``` warning: Rust code block is empty --> crates/parser/src/grammar.rs:16:5 | 16 | //! ``` | _____^ 17 | | //! // test function_with_zero_parameters 18 | | //! // fn foo() {} 19 | | //! ``` | |_______^ | help: mark blocks that do not contain Rust code as text | 16 | //! ```text | ^^^^^^^ ``` https://github.com/rust-lang/rust/pull/79816 should make this configurable so the warning can be `allow`ed.
2021-01-18 15:44:40 -06:00
/// ```
///
/// https://github.com/rust-lang/rust/issues/73831
pub fn partition_point<T, P>(slice: &[T], mut pred: P) -> usize
where
P: FnMut(&T) -> bool,
{
let mut left = 0;
let mut right = slice.len();
while left != right {
let mid = left + (right - left) / 2;
// SAFETY:
// When left < right, left <= mid < right.
// Therefore left always increases and right always decreases,
// and either of them is selected.
// In both cases left <= right is satisfied.
// Therefore if left < right in a step,
// left <= right is satisfied in the next step.
// Therefore as long as left != right, 0 <= left < right <= len is satisfied
// and if this case 0 <= mid < len is satisfied too.
let value = unsafe { slice.get_unchecked(mid) };
if pred(value) {
left = mid + 1;
} else {
right = mid;
}
}
left
}
2021-01-10 05:36:15 -06:00
pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize>
where
F: FnMut(&T) -> Ordering,
{
let start = partition_point(slice, |it| key(it) == Ordering::Less);
let len = partition_point(&slice[start..], |it| key(it) == Ordering::Equal);
2021-01-10 05:36:15 -06:00
start..start + len
}
pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
struct D<F: FnOnce()>(Option<F>);
impl<F: FnOnce()> Drop for D<F> {
fn drop(&mut self) {
if let Some(f) = self.0.take() {
f()
}
}
}
D(Some(f))
}
#[repr(transparent)]
pub struct JodChild(pub std::process::Child);
2021-01-07 11:08:34 -06:00
impl ops::Deref for JodChild {
type Target = std::process::Child;
fn deref(&self) -> &std::process::Child {
2021-01-07 11:08:34 -06:00
&self.0
}
}
impl ops::DerefMut for JodChild {
fn deref_mut(&mut self) -> &mut std::process::Child {
2021-01-07 11:08:34 -06:00
&mut self.0
}
}
impl Drop for JodChild {
fn drop(&mut self) {
let _ = self.0.kill();
2021-01-22 10:50:56 -06:00
let _ = self.0.wait();
2021-01-07 11:08:34 -06:00
}
}
impl JodChild {
pub fn into_inner(self) -> std::process::Child {
// SAFETY: repr transparent
unsafe { std::mem::transmute::<JodChild, std::process::Child>(self) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trim_indent() {
assert_eq!(trim_indent(""), "");
assert_eq!(
trim_indent(
"
hello
world
"
),
"hello\nworld\n"
);
assert_eq!(
trim_indent(
"
hello
world"
),
"hello\nworld"
);
assert_eq!(trim_indent(" hello\n world\n"), "hello\nworld\n");
assert_eq!(
trim_indent(
"
fn main() {
return 92;
}
"
),
"fn main() {\n return 92;\n}\n"
);
}
}