#![allow(unused)]
use anyhow::{anyhow, Result};
use std::net::IpAddr;
use std::path::Path;
use tree_sitter::Node;
use crate::lazy_regex;
lazy_regex!(pub NAME_CLEAN_REGEX = r"[_.].*$");
pub fn node_extract_text<'a>(node: &Node, source: &'a [u8]) -> Option<&'a str> {
node.utf8_text(source).ok()
}
pub fn name_normalize(shell: &str) -> Result<String> {
let name = Path::new(shell)
.file_name()
.and_then(|s| s.to_str())
.ok_or_else(|| anyhow!("invalid shell path: {}", shell))?
.to_lowercase();
Ok(NAME_CLEAN_REGEX.replace(&name, "").into_owned())
}
pub fn shell_is_unix(name: &str) -> bool {
if let Ok(sh) = name_normalize(name) {
return matches!(sh.as_str(), "bash" | "sh" | "zsh" | "fish" | "ksh");
}
false
}
pub fn shell_is_win(name: &str) -> bool {
if let Ok(sh) = name_normalize(name) {
return matches!(sh.as_str(), "powershell" | "pwsh" | "cmd");
}
false
}
pub fn shell_is_valid(name: &str) -> bool {
if shell_is_unix(name) || shell_is_win(name) {
return true;
}
if let Ok(sh) = name_normalize(name) {
return matches!(sh.as_str(), "node" | "python");
}
false
}
pub fn process_is_downloader(name: &str) -> bool {
if let Ok(p) = name_normalize(name) {
return matches!(p.as_str(), "curl" | "wget" | "fetch");
}
false
}
pub enum UrlAnalysisResult {
Https,
HttpDomain,
HttpIp,
}
pub fn url_analyze(url: &str) -> Result<UrlAnalysisResult> {
let url = url::Url::parse(url)?;
match url.scheme() {
"https" => Ok(UrlAnalysisResult::Https),
"http" => match url.host() {
Some(url::Host::Ipv4(_)) | Some(url::Host::Ipv6(_)) => Ok(UrlAnalysisResult::HttpIp),
Some(url::Host::Domain(_)) => Ok(UrlAnalysisResult::HttpDomain),
None => Err(anyhow!("missing host")),
},
s => Err(anyhow!("unsupported scheme: {}", s)),
}
}
pub fn normalize_target(t: &str) -> String {
let mut s = t.trim().trim_matches(|c| c == '\'' || c == '"').to_string();
while s.len() > 1 && s.ends_with('/') {
s.pop();
}
s
}
pub fn path_has_marker(p: &str, markers: &[&str]) -> bool {
let s = normalize_target(p);
markers.iter().any(|m| s.contains(m))
}
pub fn cluster_has_flag(arg: &str, flag: char) -> bool {
arg.chars().skip(1).any(|c| c == flag)
}
pub fn command_basename(name: &str) -> String {
Path::new(name)
.file_name()
.and_then(|s| s.to_str())
.unwrap_or(name)
.to_lowercase()
}
pub fn is_block_device(p: &str) -> bool {
let s = normalize_target(p);
if !s.starts_with("/dev/") {
return false;
}
let first = s
.trim_start_matches("/dev/")
.split('/')
.next()
.unwrap_or("");
if first.is_empty() {
return false;
}
if matches!(
first,
"null" | "zero" | "random" | "urandom" | "full" | "tty" | "ptmx" | "pts"
) {
return false;
}
if matches!(first, "mem" | "kmem" | "port") {
return true;
}
for prefix in [
"sd", "hd", "vd", "xvd", "nvme", "mmcblk", "loop", "dm-", "md", "sr", "ram", "fd", "ubd",
] {
if let Some(rest) = first.strip_prefix(prefix) {
let ok = match prefix {
"sd" | "hd" | "vd" | "xvd" => {
let mut chars = rest.chars();
matches!(chars.next(), Some(c) if c.is_ascii_lowercase())
&& chars.all(|c| c.is_ascii_alphanumeric())
}
"nvme" | "mmcblk" => rest.chars().next().is_some_and(|c| c.is_ascii_digit()),
_ => rest.chars().all(|c| c.is_ascii_digit()),
};
if ok {
return true;
}
}
}
false
}
pub fn is_ps_physical_device(p: &str) -> bool {
let s = p.trim().trim_matches(|c| c == '\'' || c == '"');
let lower = s.to_lowercase();
if let Some(rest) = lower.strip_prefix(r"\\.\") {
return rest.starts_with("physicaldrive") || rest.len() == 2 && rest.ends_with(':');
}
false
}