netsblox_vm/
template.rs

1//! Various templated source files.
2
3use alloc::string::String;
4use alloc::vec::Vec;
5use core::time::Duration;
6use core::fmt::Write;
7
8#[cfg(feature = "serde")]
9use serde::Serialize;
10
11use crate::process::ErrorSummary;
12
13/// A status update in the structure expected by the standard js extension.
14#[cfg_attr(feature = "serde", derive(Serialize))]
15pub struct Status {
16    pub running: bool,
17    pub output: String,
18    pub errors: Vec<ErrorSummary>,
19}
20
21/// An empty project.
22///
23/// This can be used for default initializing a project runner with a no-op project.
24pub const EMPTY_PROJECT: &str = include_str!("assets/empty-proj.xml");
25
26/// An entry to display in the syscall dropdown when running in server mode.
27///
28/// A single syscall can be listed multiple times, e.g., under different submenu categorizations.
29/// These are not checked against the syscalls actually supported by your runtime.
30/// You are responsible for implementing syscalls and ensuring they are accurately shown in the menu if desired.
31pub enum SyscallMenu {
32    /// A syscall name.
33    Entry { label: String, value: String },
34    /// A labeled submenu of syscalls.
35    Submenu { label: String, content: Vec<SyscallMenu> },
36}
37impl SyscallMenu {
38    /// Creates an instance of [`SyscallMenu::Entry`] with the given string as both the label and value.
39    pub fn simple_entry(value: String) -> Self {
40        SyscallMenu::Entry { label: value.clone(), value }
41    }
42    fn format(items: &[Self]) -> String {
43        fn format_impl(value: &SyscallMenu, res: &mut String) {
44            match value {
45                SyscallMenu::Entry { label, value } => write!(res, "{label:?}:{value:?},").unwrap(),
46                SyscallMenu::Submenu { label, content } => {
47                    write!(res, "{label:?}:{{").unwrap();
48                    for value in content {
49                        format_impl(value, res);
50                    }
51                    res.push_str("},");
52                }
53            }
54        }
55        let mut res = String::with_capacity(64);
56        res.push('{');
57        for item in items {
58            format_impl(item, &mut res);
59        }
60        res.push('}');
61        res
62    }
63}
64#[test]
65fn test_syscall_menu_format() {
66    assert_eq!(SyscallMenu::format(&[]), "{}");
67    assert_eq!(SyscallMenu::format(&[SyscallMenu::Entry { label: "foo".into(), value: "gtr".into() }]), r#"{"foo":"gtr",}"#);
68    assert_eq!(SyscallMenu::format(&[SyscallMenu::Entry { label: "foo".into(), value: "gtr".into() }, SyscallMenu::Entry { label: "bar".into(), value: "baz".into() }]), r#"{"foo":"gtr","bar":"baz",}"#);
69    assert_eq!(SyscallMenu::format(&[SyscallMenu::simple_entry("foo".into()), SyscallMenu::Submenu { label: "test".into(), content: vec![] }, SyscallMenu::Entry { label: "bam".into(), value: "s".into() }]), r#"{"foo":"foo","test":{},"bam":"s",}"#);
70    assert_eq!(SyscallMenu::format(&[SyscallMenu::Submenu { label: "tes\'  \' \"t\"f".into(), content: vec![] }]), r#"{"tes'  ' \"t\"f":{},}"#);
71    assert_eq!(SyscallMenu::format(&[SyscallMenu::Entry { label: "foo\"b\'ar".into(), value: "gtr\'\"test".into() }]), r#"{"foo\"b'ar":"gtr'\"test",}"#);
72
73}
74
75/// Arguments used to construct a templated extension.
76pub struct ExtensionArgs<'a> {
77    /// The NetsBlox VM server to connect to.
78    pub server: &'a str,
79    /// The syscall menu structure to generate for syscall blocks.
80    pub syscalls: &'a [SyscallMenu],
81    /// A list of XML element names to omit from the XML sent to the VM server.
82    pub omitted_elements: &'a [&'a str],
83    /// The duration between successive calls to pull status from the VM server.
84    pub pull_interval: Duration,
85}
86impl ExtensionArgs<'_> {
87    /// Renders the provided arguments into an extension.
88    pub fn render(&self) -> String {
89        format!(include_str!("assets/extension.js"),
90            server = self.server,
91            syscalls = SyscallMenu::format(self.syscalls),
92            omitted_elements = self.omitted_elements,
93            pull_interval_ms = self.pull_interval.as_millis(),
94        )
95    }
96}