projectdetect/indicator.rs
1//! Indicators (match rules) and the [`Match`] result type.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// A single match rule evaluated against a directory's listing.
7///
8/// Indicators are OR'd within a [`crate::ProjectType`]: the first one that
9/// fires identifies the directory as that type. The variant determines what
10/// the rule sees:
11///
12/// - [`Indicator::HasFile`] — case-insensitive exact **file** basename match
13/// (ASCII fold, e.g. `NuGet.Config` matches `nuget.config`).
14/// - [`Indicator::HasGlob`] — glob over **file** basenames only
15/// (e.g. `*.tf`). Directories are never matched.
16/// - [`Indicator::HasSubdirGlob`] — glob over immediate **subdirectory**
17/// basenames only (e.g. `*.xcodeproj`). Files are never matched.
18/// - [`Indicator::Cel`] — a CEL expression over the `files` and `subdirs`
19/// lists. Requires the `cel` cargo feature; without it, registering a type
20/// with this indicator fails with [`crate::Error::CelFeatureDisabled`].
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum Indicator {
23 /// Case-insensitive exact file-basename match.
24 HasFile(String),
25 /// Glob over file basenames.
26 HasGlob(String),
27 /// Glob over immediate-subdirectory basenames.
28 HasSubdirGlob(String),
29 /// CEL expression over `files` / `subdirs` (requires the `cel` feature).
30 Cel(String),
31}
32
33impl fmt::Display for Indicator {
34 /// Human-readable form surfaced in [`Match::indicator`] for "why did this
35 /// match" debuggability. A `HasSubdirGlob` renders with a trailing slash
36 /// (e.g. `MyApp.xcodeproj/`) to signal a directory marker; a `Cel`
37 /// indicator renders as `cel:<expr>`.
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 Indicator::HasFile(s) | Indicator::HasGlob(s) => f.write_str(s),
41 Indicator::HasSubdirGlob(s) => write!(f, "{s}/"),
42 Indicator::Cel(e) => write!(f, "cel:{e}"),
43 }
44 }
45}
46
47/// Couples a matched project type with the indicator that fired. Surfaced by
48/// [`crate::Registry::detect`] so consumers can audit detection decisions.
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50pub struct Match {
51 /// The [`crate::ProjectType::name`] that matched.
52 #[serde(rename = "type")]
53 pub r#type: String,
54 /// The [`Indicator`]'s [`Display`](std::fmt::Display) form that fired.
55 pub indicator: String,
56}