Skip to main content

kernel/config/
interpreter.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! The interpreter a building names for its shell lines; the part
7//! `crates/kernel/spec/Config.lean` §8-22 specifies.
8
9use serde::{Deserialize, Serialize};
10
11use crate::error::{AxCode, AxError};
12
13/// Which interpreter a building's shell lines run under
14/// (`crates/kernel/spec/Config.lean` §8-22, `crates/runtime/Spec.lean`
15/// §8-13-2 D30).
16///
17/// `System` is the platform's own shell, `%COMSPEC%` on Windows and
18/// `$SHELL` elsewhere; `Pwsh` is PowerShell 7 on every platform. A
19/// building that asks for one this machine lacks is refused at the
20/// call rather than given the other, because a line written for one
21/// interpreter is a different language under the other.
22#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "lowercase")]
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25pub enum Interpreter {
26    #[default]
27    System,
28    Pwsh,
29}
30
31impl Interpreter {
32    /// The key a configuration layer writes this under.
33    pub const KEY: &'static str = "interpreter";
34
35    /// Sole constructor from text.
36    ///
37    /// # Errors
38    /// `ConfigInvalid` for any spelling but the two, naming the key and
39    /// the value, with both spellings in the recovery: a guess would run
40    /// a building's lines under an interpreter nobody chose.
41    pub fn parse(raw: &str) -> Result<Interpreter, AxError> {
42        match raw {
43            "system" => Ok(Interpreter::System),
44            "pwsh" => Ok(Interpreter::Pwsh),
45            _ => Err(AxError::failure(
46                AxCode::ConfigInvalid,
47                "read a configuration layer",
48                format!("[sandbox] {}: {raw}", Interpreter::KEY),
49            )
50            .with_recovery(
51                "write `interpreter = \"system\"` for this platform's shell or \
52                 `interpreter = \"pwsh\"` for PowerShell 7",
53            )),
54        }
55    }
56
57    /// The spelling [`Interpreter::parse`] reads back.
58    #[must_use]
59    pub fn as_str(self) -> &'static str {
60        match self {
61            Interpreter::System => "system",
62            Interpreter::Pwsh => "pwsh",
63        }
64    }
65}
66
67#[cfg(test)]
68#[allow(clippy::unwrap_used, reason = "test code")]
69mod tests {
70    use super::Interpreter;
71    use crate::error::AxCode;
72
73    /// The two spellings read back as themselves; any other is refused
74    /// with the key, the value and both spellings, never guessed.
75    #[test]
76    fn two_spellings_are_read_and_any_other_is_refused_with_both_named() {
77        for interpreter in [Interpreter::System, Interpreter::Pwsh] {
78            assert_eq!(
79                Interpreter::parse(interpreter.as_str()).unwrap(),
80                interpreter
81            );
82        }
83        let err = Interpreter::parse("bash").unwrap_err();
84        assert_eq!(err.code(), &AxCode::ConfigInvalid);
85        assert!(err.subject().contains("interpreter") && err.subject().contains("bash"));
86        assert_eq!(
87            err.recovery(),
88            "write `interpreter = \"system\"` for this platform's shell or `interpreter = \"pwsh\"` for PowerShell 7"
89        );
90    }
91}