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}