use std::path::{Path, PathBuf};
use globset::{Glob, GlobSetBuilder};
use owo_colors::OwoColorize;
use tokio::process::Command;
pub fn contains_subsequence(haystack: &[u8], needle: &[u8]) -> bool {
haystack.windows(needle.len()).any(|window| window == needle)
}
pub fn contains_substring(vec: &[u8], substring: &str) -> bool {
if let Ok(s) = std::str::from_utf8(vec) {
s.contains(substring)
} else {
false
}
}
pub fn unwrap_ref_option<T: Clone>(o: &Option<T>, default: T) -> T {
if let Some(v) = o {
v.clone()
} else {
default
}
}
pub fn result_hint(s: bool) -> String {
if s {
"✓".green().to_string()
} else {
"x".red().to_string()
}
}
pub fn create_glob_builder(glob: &Option<Vec<String>>) -> Option<GlobSetBuilder> {
if let Some(v) = glob {
let mut builder = GlobSetBuilder::new();
v.iter().for_each(|f| {
if let Ok(s) = Glob::new(f) {
builder.add(s);
}
});
Some(builder)
} else {
None
}
}
pub async fn run_command(cmd: &str, args: &[&str], dir: impl AsRef<Path>) -> anyhow::Result<()> {
let output = Command::new(cmd).current_dir(dir).args(args).output().await?;
if output.status.success() {
Ok(())
} else {
let status = output.status;
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
let msg = format!(
"Command failed with status: {}, stderr: {},\n{}",
status,
stderr,
format!("stdout: {}", stdout).cyan()
);
Err(anyhow::Error::msg(msg.red().to_string()))
}
}
pub fn get_relative_path(
base: &Option<impl AsRef<Path>>,
target: impl AsRef<Path>,
) -> Option<PathBuf> {
if let Some(base) = base {
target.as_ref().strip_prefix(base).ok().map(PathBuf::from)
} else {
Some(target.as_ref().to_path_buf())
}
}
pub async fn is_clear(path: impl AsRef<Path>) -> anyhow::Result<()> {
let output =
Command::new("git").current_dir(path).arg("status").arg("--porcelain").output().await?;
let error =
Err(anyhow::Error::msg("Unclean working tree. Commit or stash changes first.".yellow()));
if output.status.success() && output.stderr.is_empty() && output.stdout.is_empty() {
return Ok(());
}
error
}
pub const EMPTY_MARKER: &'static str = "--";
pub const HOME_ICON: &'static str = "🏠";
pub fn base_pair(k: &str, v: &str, colon: bool) -> String {
let colon = if let true = colon { ":" } else { "" };
format!("{}{} {}", k, colon, v.bright_cyan())
}
pub fn pair(k: &str, v: &str) -> String {
base_pair(k, v, true)
}
pub fn pair_no_colon(k: &str, v: &str) -> String {
base_pair(k, v, false)
}
#[derive(Debug, PartialEq)]
pub enum RunState {
Halt,
Building,
Finish,
}
impl Default for RunState {
fn default() -> Self {
Self::Halt
}
}
impl RunState {
pub fn display(&self) -> String {
match self {
RunState::Building => format!("{:?}", self),
RunState::Finish => format!("{:?}", self),
RunState::Halt => format!("{:?}", self),
}
}
pub fn max_len() -> usize {
[
RunState::Building.display().len(),
RunState::Finish.display().len(),
RunState::Halt.display().len(),
]
.iter()
.copied()
.max()
.unwrap()
}
}