Skip to main content

mars_schema/
dialect.rs

1//! Inbound dialect vocabulary for foreign definitions.
2
3use std::{fmt, str::FromStr};
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
8#[serde(rename_all = "kebab-case")]
9pub enum Dialect {
10    Claude,
11    /// The Agent Skills standard on its own: translation is the identity plus relocation.
12    #[serde(rename = "agent-skills", alias = "agentskills", alias = "agent_skills")]
13    AgentSkills,
14    Codex,
15    /// `open-code` is the spelling v0.19.0 accepted in `mars.toml`.
16    #[serde(rename = "opencode", alias = "open-code")]
17    OpenCode,
18    Cursor,
19    #[serde(rename = "mars-native", alias = "marsnative", alias = "mars_native")]
20    MarsNative,
21}
22
23impl Dialect {
24    pub fn as_str(self) -> &'static str {
25        match self {
26            Self::Claude => "claude",
27            Self::AgentSkills => "agent-skills",
28            Self::Codex => "codex",
29            Self::OpenCode => "opencode",
30            Self::Cursor => "cursor",
31            Self::MarsNative => "mars-native",
32        }
33    }
34
35    pub fn parse(name: &str) -> Option<Self> {
36        match name.trim().to_ascii_lowercase().as_str() {
37            "claude" => Some(Self::Claude),
38            "agent-skills" | "agentskills" | "agent_skills" => Some(Self::AgentSkills),
39            "codex" => Some(Self::Codex),
40            "opencode" => Some(Self::OpenCode),
41            "cursor" => Some(Self::Cursor),
42            "mars-native" | "marsnative" | "mars_native" => Some(Self::MarsNative),
43            _ => None,
44        }
45    }
46}
47
48/// A foreign dialect a root is read as, and why. This one value is what `prepare` classifies a
49/// foreign root with and what `inspect` reports.
50#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
51pub struct DialectChoice {
52    pub dialect: Dialect,
53    pub evidence: DialectEvidence,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
57pub struct DialectEvidence {
58    pub source: DialectEvidenceSource,
59    /// The files that decided a predicted dialect; empty for a declared one.
60    pub paths: Vec<String>,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
64#[serde(rename_all = "lowercase")]
65pub enum DialectEvidenceSource {
66    Declared,
67    Predicted,
68}
69
70impl DialectChoice {
71    pub(crate) fn declared(dialect: Dialect) -> Self {
72        Self {
73            dialect,
74            evidence: DialectEvidence {
75                source: DialectEvidenceSource::Declared,
76                paths: Vec::new(),
77            },
78        }
79    }
80
81    pub(crate) fn predicted(dialect: Dialect, paths: Vec<String>) -> Self {
82        Self {
83            dialect,
84            evidence: DialectEvidence {
85                source: DialectEvidenceSource::Predicted,
86                paths,
87            },
88        }
89    }
90}
91
92impl fmt::Display for Dialect {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.write_str(self.as_str())
95    }
96}
97
98impl FromStr for Dialect {
99    type Err = ();
100
101    fn from_str(value: &str) -> Result<Self, Self::Err> {
102        Self::parse(value).ok_or(())
103    }
104}