pub mod print;
use std::process::{Command, Stdio};
use std::time::{SystemTime, UNIX_EPOCH};
use cfg_match::cfg_match;
macro_rules! def_matched_item {
($cfg:ident, $doc:literal, $name:ident, $err:literal, $($k:literal: $v: literal);* $(;)?) => {
cfg_match! {
$(
$cfg = $k => {
#[doc = $doc]
pub const $name: &str = $v;
}
)*
_ => {
compile_error!($err)
pub const $name: &str = "";
}
}
};
}
def_matched_item! {
target_os,
"String representing the current operating system",
OS_STRING,
"Target operating system is unsupported",
"linux": "linux";
"windows": "windows";
"macos": "macos";
"ios": "ios";
"android": "android";
"freebsd": "freebsd";
"dragonfly": "dragonfly";
"bitrig": "bitrig";
"netbsd": "netbsd";
"openbsd": "openbsd";
}
def_matched_item! {
target_arch,
"String representing the current architecture",
ARCH_STRING,
"Target architecture is unsupported",
"x86": "x86";
"x86_64": "x86_64";
"arm": "arm";
"aarch64": "aarch64";
"riscv32": "riscv32";
"riscv64": "riscv64";
"mips": "mips";
"mips64": "mips64";
"powerpc": "powerpc";
"powerpc64": "powerpc64";
}
cfg_match! {
target_os = "linux" => {
pub const PREFERRED_ARCHIVE: &str = "tar.gz";
}
_ => {
pub const PREFERRED_ARCHIVE: &str = "zip";
}
}
pub fn preferred_archive_extension() -> String {
format!(".{PREFERRED_ARCHIVE}")
}
cfg_match! {
target_pointer_width = "64" => {
pub const TARGET_BITS_STR: &str = "64";
}
_ => {
pub const TARGET_BITS_STR: &str = "32";
}
}
#[macro_export]
macro_rules! skip_fail {
($res:expr) => {
match $res {
Ok(val) => val,
Err(..) => continue,
}
};
}
#[macro_export]
macro_rules! skip_none {
($res:expr) => {
match $res {
Some(val) => val,
None => continue,
}
};
}
pub fn cap_first_letter(string: &str) -> String {
let mut c = string.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().chain(c).collect(),
}
}
pub fn merge_options<T>(left: Option<T>, right: Option<T>) -> Option<T> {
if right.is_some() {
right
} else {
left
}
}
pub fn utc_timestamp() -> anyhow::Result<u64> {
Ok(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs())
}
pub trait ToInt {
fn to_int(&self) -> i32;
}
impl ToInt for bool {
fn to_int(&self) -> i32 {
*self as i32
}
}
cfg_match! {
target_os = "linux" => {
const URL_OPEN_CMD: Option<&str> = Some("xdg-open");
}
target_os = "windows" => {
const URL_OPEN_CMD: Option<&str> = Some("start");
}
target_os = "macos" => {
const URL_OPEN_CMD: Option<&str> = Some("open");
}
_ => {
const URL_OPEN_CMD: Option<&str> = None;
}
}
pub fn open_link(link: &str) -> anyhow::Result<()> {
let Some(cmd) = URL_OPEN_CMD else {
return Ok(());
};
Command::new(cmd)
.arg(link)
.stderr(Stdio::null())
.stdout(Stdio::null())
.spawn()?;
Ok(())
}
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub fn yes_no(string: &str) -> Option<bool> {
match string {
"yes" => Some(true),
"no" => Some(false),
_ => None,
}
}
pub fn is_valid_identifier(id: &str) -> bool {
for c in id.chars() {
if !c.is_ascii() {
return false;
}
if c.is_ascii_punctuation() {
match c {
'_' | '-' | '.' => {}
_ => return false,
}
}
if c.is_ascii_whitespace() {
return false;
}
}
true
}
#[derive(Deserialize, Debug, Clone, Eq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum DeserListOrSingle<T> {
Single(T),
List(Vec<T>),
}
impl<T> Default for DeserListOrSingle<T> {
fn default() -> Self {
Self::List(Vec::default())
}
}
impl<T: Serialize> Serialize for DeserListOrSingle<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::List(list) => {
if list.len() == 1 {
list[0].serialize(serializer)
} else {
list.serialize(serializer)
}
}
Self::Single(val) => val.serialize(serializer),
}
}
}
impl<T> DeserListOrSingle<T> {
pub fn is_empty(&self) -> bool {
matches!(self, Self::List(list) if list.is_empty())
}
pub fn is_option_empty(val: &Option<Self>) -> bool {
val.is_none() || matches!(val, Some(val) if val.is_empty())
}
pub fn iter(&self) -> DeserListOrSingleIter<'_, T> {
match &self {
Self::Single(val) => {
DeserListOrSingleIter(DeserListOrSingleIterState::Single(Some(val)))
}
Self::List(list) => {
DeserListOrSingleIter(DeserListOrSingleIterState::List(list.iter()))
}
}
}
}
impl<T: Clone> DeserListOrSingle<T> {
pub fn get_vec(&self) -> Vec<T> {
match &self {
Self::Single(val) => vec![val.clone()],
Self::List(list) => list.clone(),
}
}
pub fn merge(&mut self, other: Self) {
let mut self_vec = self.get_vec();
self_vec.extend(other.iter().cloned());
*self = Self::List(self_vec);
}
}
impl<T: PartialEq> PartialEq for DeserListOrSingle<T> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(DeserListOrSingle::Single(l), DeserListOrSingle::Single(r)) => l == r,
(DeserListOrSingle::List(l), DeserListOrSingle::List(r)) => l == r,
(DeserListOrSingle::List(l), DeserListOrSingle::Single(r)) => {
l.len() == 1 && l.first().expect("Length is 1") == r
}
(DeserListOrSingle::Single(l), DeserListOrSingle::List(r)) => {
r.len() == 1 && r.first().expect("Length is 1") == l
}
}
}
}
impl<T: Clone> Extend<T> for DeserListOrSingle<T> {
fn extend<U: IntoIterator<Item = T>>(&mut self, iter: U) {
if let Self::Single(item) = self {
*self = Self::List(vec![item.clone()]);
}
if let Self::List(list) = self {
list.extend(iter);
if list.len() == 1 {
*self = Self::Single(list.first().expect("Length is 1").clone());
}
}
}
}
pub struct DeserListOrSingleIter<'a, T>(DeserListOrSingleIterState<'a, T>);
enum DeserListOrSingleIterState<'a, T> {
Single(Option<&'a T>),
List(std::slice::Iter<'a, T>),
}
impl<'a, T> Iterator for DeserListOrSingleIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
match &mut self.0 {
DeserListOrSingleIterState::Single(val) => val.take(),
DeserListOrSingleIterState::List(slice_iter) => slice_iter.next(),
}
}
}
pub trait DefaultExt {
fn is_default(&self) -> bool;
}
impl<T: Default + PartialEq> DefaultExt for T {
fn is_default(&self) -> bool {
self == &Self::default()
}
}
#[macro_export]
macro_rules! try_3 {
($op:block) => {
if let Ok(out) = $op {
Ok(out)
} else {
if let Ok(out) = $op {
Ok(out)
} else {
$op
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_id_validation() {
assert!(is_valid_identifier("hello"));
assert!(is_valid_identifier("Hello"));
assert!(is_valid_identifier("H3110"));
assert!(is_valid_identifier("hello-world"));
assert!(is_valid_identifier("hello_world"));
assert!(is_valid_identifier("hello.world"));
assert!(!is_valid_identifier("hello*world"));
assert!(!is_valid_identifier("hello\nworld"));
assert!(!is_valid_identifier("hello world"));
}
#[test]
fn test_deser_list_or_single_iter() {
let item = DeserListOrSingle::Single(7);
assert_eq!(item.iter().next(), Some(&7));
let item = DeserListOrSingle::List(vec![1, 2, 3]);
let mut iter = item.iter();
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&3));
}
}