~skye/com/com.rs
view raw
pub mod macros {
pub use crate::{elog, fatal, iota, log, log_fmt, puts};
}
mod colors {
pub const CLR: &str = "\x1b[22;39m";
pub const BOLD_RED: &str = "\x1b[1;91m";
}
/** in k: !n.
* make vec of range 0..n.
*
* $f optionally expands into a map before collect */
#[macro_export]
macro_rules! iota {
($n:expr) => {{
(0..$n).collect::<Vec<_>>()
}};
($n:expr, $f:expr) => {{
(0..$n).map($f).collect::<Vec<_>>()
}};
}
/** crash and die gracefully.
* optional $p for process exit code */
#[macro_export]
macro_rules! fatal {
($($t:tt)*) => {{
eprintln!(
"{}fatal error{}: {}",
crate::colors::BOLD_RED,
crate::colors::CLR,
format!($($t)*)
);
std::process::exit(-1);
}};
($($t:tt)*, $p:expr) => {{
eprintln!(
"{}fatal error{}: {}",
crate::colors::BOLD_RED,
crate::colors::CLR,
format!($($t)*)
);
std::process::exit($p);
}};
}
/** put a string to stdout with no newline */
#[macro_export]
macro_rules! puts {
($($t:tt)*) => {{
use std::io::Write;
print!($($t)*);
match std::io::stdout().flush() {
Ok(_) => (),
Err(e) => fatal!("can't flush stdout: {e}"),
}
}};
}
/** format a log string */
#[macro_export]
macro_rules! log_fmt {
($x:expr, $($y:tt)*) => {{
use colored::Colorize;
format!("{} {} : {}", $x, $crate::com::now().blue(), format!($($y)*))
}};
}
/** print a log message */
#[macro_export]
macro_rules! log {
($($x:tt)*) => {{
println!("{}", log_fmt!("log".yellow(), $($x)*));
}};
}
/** log an error */
#[macro_export]
macro_rules! elog {
($($x:tt)*) => {{
eprintln!("{}", log_fmt!("err".red(), $($x)*));
}};
}
#[allow(dead_code)]
pub mod com {
/** division that rounds up */
pub fn ceil_div(x: usize, y: usize) -> usize {
(x as f64 / y as f64).ceil() as usize
}
/** reverse x. mutates rather than returns */
pub fn rev<T>(x: &mut [T]) {
let l = x.len() - 1;
for i in (0..ceil_div(l, 2)).into_iter() {
x.swap(i, l - i);
}
}
/** basename of a filename */
pub fn basename<T>(x: T) -> String
where
String: From<T>,
{
let x = String::from(x);
let v: Vec<_> = x.split("/").collect();
(if v.len() > 0 { v[v.len() - 1] } else { v[0] }).to_string()
}
/** dirname of a filename.
* warning: leaves a trailing / for root files */
pub fn dirname<T>(x: T) -> String
where
String: From<T>,
{
let x = String::from(x);
let mut v: Vec<_> = x.split("/").collect();
/* remove the last item, which is the basename */
v.pop();
if v.len() > 1 {
v.join("/")
} else {
String::new()
}
}
pub type F<'a> = &'a dyn Fn(char) -> bool;
/** generic trim for trim_right and trim_left, taking an argument
* that determines if the chars are reversed.
* setting rev to true makes it a trim left, otherwise it's
* a trim right */
fn trim_<T>(s: T, c: F, rev: bool) -> String
where
String: From<T>,
{
let mut s = {
let s = String::from(s);
/* reverse if needed */
if rev {
s.chars().rev().collect::<String>()
} else {
s
}
};
/* mutable iterator
* has to be in two separate steps because otherwise it breaks */
let mut i = s.chars().rev().collect::<Vec<_>>();
let mut i = i.iter_mut();
/* pop while char == c */
while let Some(x) = i.next() {
if c(*x) {
let _ = s.pop();
} else {
break;
}
}
if rev {
s.chars().rev().collect::<String>()
} else {
s
}
}
/** trim all chars matching c from the left */
pub fn trim_left<T>(s: T, c: char) -> String
where
String: From<T>,
{
trim_(s, &|x| x == c, true)
}
/** trim all chars matching c from the right */
pub fn trim_right<T>(s: T, c: char) -> String
where
String: From<T>,
{
trim_(s, &|x| x == c, false)
}
/** trim all chars where f */
pub fn trim_left_f<T>(s: T, c: F) -> String
where
String: From<T>,
{
trim_(s, c, true)
}
/** trim all chars where f */
pub fn trim_right_f<T>(s: T, c: F) -> String
where
String: From<T>,
{
trim_(s, c, false)
}
/** convert an int to a corresponding bool */
pub fn i64_to_bool(x: i64) -> bool {
if x == 0 {
false
} else {
true
}
}
/** convert a bool to a corresponding int */
pub fn bool_to_i64(x: bool) -> i64 {
if x {
1
} else {
0
}
}
/** read line from stdin, taking prompt
* like the python thing, yk? */
pub fn input(p: &str) -> String {
/* read into b */
let mut b = String::new();
let stdin = std::io::stdin();
puts!("{p}");
match stdin.read_line(&mut b) {
Ok(x) => x,
Err(e) => fatal!("failed to read line from stdin: {e}"),
};
/* trim writespace bc the newline is tacked
* on and we don't need it anyway */
b.trim().to_string()
}
/** get input or return x if empty */
pub fn input_or<T>(p: &str, x: T) -> String
where
String: From<T>,
{
let i = input(p);
if i == String::new() || is_all_space(&mut i.chars()) {
String::from(x)
} else {
i
}
}
/** are all chars whitespace.
* x has to be a mutable for all */
fn is_all_space(x: &mut std::str::Chars) -> bool {
x.all(|x| match x {
' ' | '\t' | '\n' => true,
_ => false,
})
}
/** get the current time as a nice formatted string */
pub fn now() -> String {
chrono::Local::now().format("%Y-%m-%d %H:%M").to_string()
}
}
#[test]
fn rev_even_len() {
let mut x = vec![1, 2, 3, 4];
com::rev(&mut x);
assert_eq!(x, vec![4, 3, 2, 1]);
}
#[test]
fn rev_odd_len() {
let mut x = iota!(5, |x| x + 1);
com::rev(&mut x);
assert_eq!(x, iota!(5, |x| x + 1).into_iter().rev().collect::<Vec<_>>());
}
#[test]
fn iota_map() {
let x = iota!(5, |x| x + 1);
assert_eq!(x, iota!(5).into_iter().map(|x| x + 1).collect::<Vec<_>>())
}
#[test]
fn basename() {
assert_eq!(com::basename("/home/skye"), "skye");
assert_eq!(com::basename("foo"), "foo");
}
#[test]
fn dirname() {
assert_eq!(com::dirname("/home/skye"), "/home");
assert_eq!(com::dirname("/home"), "");
}
#[test]
fn trim() {
assert_eq!(com::trim_right("foo ", ' '), "foo");
assert_eq!(com::trim_left(" foo", ' '), "foo");
}
#[test]
fn trim_f() {
let w = |x| match x {
' ' | '\n' | '\t' => true,
_ => false,
};
assert_eq!(com::trim_right_f("foo \n\t", &w), "foo");
assert_eq!(com::trim_left_f("\n \tfoo", &w), "foo");
}