use crate::ecosystem::Ecosystem as E;
use crate::text::strip_suffix_ignore_ascii_case;
use crate::{StackKind, StackLabel, labels};
pub type ProcessRule = (&'static [&'static str], StackLabel, E);
pub const PROCESS_RULES: &[ProcessRule] = &[
(&["node", "nodejs"], labels::NODE, E::Node),
(&["python", "python3", "pythonw"], labels::PYTHON, E::Python),
(&["ruby"], labels::RUBY, E::Ruby),
(&["java", "javaw"], labels::JAVA, E::Jvm),
(&["go"], labels::GO, E::Go),
(&["deno"], labels::DENO, E::Deno),
(&["bun"], labels::BUN, E::Node),
(&["dotnet"], labels::DOTNET, E::DotNet),
(&["php", "php-fpm"], labels::PHP, E::Php),
(&["perl"], labels::PERL, E::Other),
(&["cargo", "rustc"], labels::RUST, E::Rust),
(
&["erlang", "beam.smp", "erl", "werl"],
labels::ERLANG,
E::Beam,
),
(&["elixir"], labels::ELIXIR, E::Beam),
(&["dart"], labels::DART, E::Other),
(&["swift"], labels::SWIFT, E::Other),
(&["postgres", "postgresql"], labels::POSTGRESQL, E::Other),
(&["mysqld", "mysql"], labels::MYSQL, E::Other),
(&["mariadbd", "mariadb"], labels::MARIADB, E::Other),
(&["mongod", "mongos"], labels::MONGODB, E::Other),
(&["redis-server", "redis"], labels::REDIS, E::Other),
(&["valkey-server", "valkey"], labels::VALKEY, E::Other),
(&["memcached"], labels::MEMCACHED, E::Other),
(&["clickhouse-server"], labels::CLICKHOUSE, E::Other),
(&["cockroach"], labels::COCKROACHDB, E::Other),
(&["sqlservr"], labels::SQL_SERVER, E::Other),
(&["nginx"], labels::NGINX, E::Other),
(&["apache2", "httpd"], labels::APACHE, E::Other),
(&["caddy"], labels::CADDY, E::Other),
(&["traefik"], labels::TRAEFIK, E::Other),
(&["envoy"], labels::ENVOY, E::Other),
(&["haproxy"], labels::HAPROXY, E::Other),
(&["w3wp"], labels::IIS, E::Other),
(&["gunicorn"], labels::GUNICORN, E::Python),
(&["uvicorn"], labels::UVICORN, E::Python),
(&["puma"], labels::PUMA, E::Ruby),
(&["elasticsearch"], labels::ELASTICSEARCH, E::Other),
(&["opensearch"], labels::OPENSEARCH, E::Other),
(&["rabbitmq-server"], labels::RABBITMQ, E::Other),
(&["kafka"], labels::KAFKA, E::Other),
(&["webpack"], labels::WEBPACK, E::Node),
(&["vite"], labels::VITE, E::Node),
(&["next-server"], labels::NEXT_JS, E::Node),
(&["nuxt"], labels::NUXT, E::Node),
(&["hugo"], labels::HUGO, E::Other),
(&["jekyll"], labels::JEKYLL, E::Ruby),
(&["flask"], labels::FLASK, E::Python),
(&["rails"], labels::RAILS, E::Ruby),
(&["gradle"], labels::JAVA_GRADLE, E::Jvm),
(&["mvn"], labels::JAVA_MAVEN, E::Jvm),
];
#[must_use]
pub fn detect_from_process(process_name: &str) -> Option<StackLabel> {
find_process_rule(process_name).map(|(_, label, _)| label.clone())
}
#[must_use]
pub fn detect_from_process_names(process_name: &str, exe_name: Option<&str>) -> Option<StackLabel> {
find_process_rule_by_names(process_name, exe_name).map(|(_, label, _)| label.clone())
}
const TITLED_PROCESSES: &[&str] = &["next-server", "puma", "gunicorn"];
pub fn find_process_rule_by_names(
process_name: &str,
exe_name: Option<&str>,
) -> Option<&'static ProcessRule> {
find_process_rule(process_name).or_else(|| exe_name.and_then(find_process_rule))
}
fn find_process_rule(process_name: &str) -> Option<&'static ProcessRule> {
let name = strip_windows_exe_suffix(process_name);
find_exact(name)
.or_else(|| find_titled(name))
.or_else(|| find_versioned_runtime(name))
}
fn find_exact(name: &str) -> Option<&'static ProcessRule> {
PROCESS_RULES
.iter()
.find(|(names, _, _)| names.iter().any(|key| name.eq_ignore_ascii_case(key)))
}
fn find_titled(name: &str) -> Option<&'static ProcessRule> {
let (head, _) = name.split_once([' ', ':'])?;
TITLED_PROCESSES
.iter()
.any(|titled| head.eq_ignore_ascii_case(titled))
.then(|| find_exact(head))
.flatten()
}
fn find_versioned_runtime(name: &str) -> Option<&'static ProcessRule> {
let base = name.trim_end_matches(|c: char| c.is_ascii_digit() || c == '.');
let version = name.get(base.len()..)?;
if base.is_empty() || !version.starts_with(|c: char| c.is_ascii_digit()) {
return None;
}
find_exact(base).filter(|(_, label, _)| label.kind() == StackKind::Runtime)
}
fn strip_windows_exe_suffix(process_name: &str) -> &str {
strip_suffix_ignore_ascii_case(process_name, ".exe").unwrap_or(process_name)
}