Skip to main content

StackDetector

Struct StackDetector 

Source
pub struct StackDetector { /* private fields */ }
Expand description

Cache-owning detector for repeated stack and project lookups.

A detector is intentionally stateful. Cache entries are retained until the detector is dropped or clear is called, so it is best used for one coherent scan of process or project metadata. Call clear between scans when filesystem changes should be observed.

§Home Ceiling

Upward project walks stop before testing the detector’s home directory, so stray marker files directly in a user’s home do not claim unrelated processes. new and Default use crate::home_dir; with_home overrides it.

§Stack Priority

detect_stack resolves a label in this order:

  1. Image name via crate::detect_from_image.
  2. Process or executable name via crate::detect_from_process_names, when that label is final: StackKind::Framework, StackKind::Database, StackKind::Service, or any future kind.
  3. Project config, when the process label is a StackKind::Runtime or StackKind::Tool, or when the process is unknown but its executable belongs to the project: it lies inside the project root, it was built by go run or go test into a temporary go-build* directory (Go config only), or it lies in the target directory of a Cargo workspace that contains the root (Rust config only).
  4. The process label, if any.

Config detection is ecosystem-aware. A known runtime or tool accepts only config labels from its own ecosystem: a php or php-fpm process in a Laravel project that also has vite.config.js is Laravel, a node or vite process there is Vite, and a python process in a Next.js project stays Python. Deno config has its own ecosystem, so a node or bun process next to deno.json keeps its label, while a deno process also accepts Node config. A Python process takes only framework labels from config, so gunicorn in a Python project with no recognized framework stays Gunicorn. An unknown process uses every rule, in the order of crate::detect_from_config, except that when its executable lies inside the project root, Rust, Go, .NET, and JVM config are tried first: a binary at tmp/main in a repo with go.mod, package.json, and vite.config.js is Go, not Vite. An executable under the project’s node_modules (node_modules/@esbuild/linux-x64/bin/esbuild) is a Node build tool, so Node config is tried first instead and the same repo gives Vite.

§Examples

use what_stack::{StackDetector, StackInput};

let mut detector = StackDetector::new();
let label = detector.detect_stack(StackInput::new("postgres").image("postgres:16"));

assert_eq!(label.expect("known image"), "PostgreSQL");

Implementations§

Source§

impl StackDetector

Source

pub fn new() -> Self

Create a detector whose home ceiling is the current user’s home directory, as returned by crate::home_dir.

Source

pub fn with_home(home: Option<PathBuf>) -> Self

Create a detector with an explicit home ceiling.

None disables the ceiling, so upward walks may reach the file system root (bounded by crate::MAX_WALK_DEPTH).

Source

pub fn home(&self) -> Option<&Path>

Return the configured home ceiling.

Source

pub fn clear(&mut self)

Drop all cached project-root and config results.

The home ceiling is kept. Call this between scans when one detector is reused and filesystem changes should be observed.

Source

pub fn detect_project_root( &mut self, input: ProjectInput<'_>, ) -> Option<PathBuf>

Detect a project root from process-like path inputs.

Uses the same fallback order as crate::resolve_project_root with the detector’s home ceiling. Results are cached by visited directory. Positive hits cache the visited directories from the start up to the discovered root; negative walks cache the visited directories as misses, except when the walk stopped at crate::MAX_WALK_DEPTH, because a walk from a shallower visited directory can reach further up. This mirrors the process-enrichment hot path where many entries share a working directory or project ancestor.

§Examples
use std::path::Path;
use what_stack::{ProjectInput, StackDetector};

let mut detector = StackDetector::new();
let root = detector.detect_project_root(ProjectInput::new().cwd(Path::new(".")));
println!("{root:?}");
Source

pub fn detect_stack(&mut self, input: StackInput<'_>) -> Option<StackLabel>

Detect a stack label from image, process, and project metadata.

Image and process matching are pure string operations. Config matching reads the given project-root directory on the first lookup and caches the result for future calls with the same path. See the type-level documentation for the priority and config guard.

Trait Implementations§

Source§

impl Debug for StackDetector

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for StackDetector

Source§

fn default() -> Self

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.