From a2df29ddbe4abb628e555b7eb4778b7e7fb56863 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Fri, 12 Jul 2024 16:53:31 -0700 Subject: [PATCH] feat: make snippet executors multiplatform --- build.rs | 31 ----- config-file-schema.json | 39 ++++++ executors.yaml | 73 ++++++++++ executors/c++.sh | 5 - executors/c.sh | 5 - executors/go.sh | 8 -- executors/java.sh | 7 - executors/js.sh | 3 - executors/kotlin.sh | 7 - executors/lua.sh | 3 - executors/nushell.sh | 3 - executors/perl.sh | 3 - executors/python.sh | 3 - executors/ruby.sh | 3 - executors/rust-script.sh | 3 - executors/rust.sh | 5 - flake.nix | 2 +- src/custom.rs | 25 +++- src/demo.rs | 4 +- src/execute.rs | 262 ++++++++++++++++-------------------- src/export.rs | 6 +- src/lib.rs | 2 +- src/main.rs | 11 +- src/markdown/elements.rs | 3 +- src/presenter.rs | 6 +- src/processing/builder.rs | 8 +- src/processing/execution.rs | 12 +- 27 files changed, 283 insertions(+), 259 deletions(-) create mode 100644 executors.yaml delete mode 100644 executors/c++.sh delete mode 100644 executors/c.sh delete mode 100644 executors/go.sh delete mode 100644 executors/java.sh delete mode 100644 executors/js.sh delete mode 100644 executors/kotlin.sh delete mode 100644 executors/lua.sh delete mode 100644 executors/nushell.sh delete mode 100644 executors/perl.sh delete mode 100644 executors/python.sh delete mode 100644 executors/ruby.sh delete mode 100755 executors/rust-script.sh delete mode 100644 executors/rust.sh diff --git a/build.rs b/build.rs index d1cfa7ff..70eb5093 100644 --- a/build.rs +++ b/build.rs @@ -34,39 +34,8 @@ fn build_themes(out_dir: &str) -> io::Result<()> { Ok(()) } -fn build_executors(out_dir: &str) -> io::Result<()> { - let output_path = format!("{out_dir}/executors.rs"); - let mut output_file = BufWriter::new(File::create(output_path)?); - output_file.write_all(b"use std::collections::BTreeMap as Map;\n")?; - output_file.write_all(b"use once_cell::sync::Lazy;\n")?; - output_file.write_all(b"static EXECUTORS: Lazy> = Lazy::new(|| Map::from([\n")?; - - let mut paths = fs::read_dir("executors")?.collect::>>()?; - paths.sort_by_key(|e| e.path()); - for file in paths { - let metadata = file.metadata()?; - if !metadata.is_file() { - panic!("found non file in executors directory"); - } - let path = file.path(); - let contents = fs::read(&path)?; - let file_name = path.file_name().unwrap().to_string_lossy(); - let (language, extension) = file_name.split_once('.').unwrap(); - if extension != "sh" { - panic!("extension must be 'sh'"); - } - output_file.write_all(format!("(\"{language}\".parse().unwrap(), {contents:?}.as_slice()),\n").as_bytes())?; - } - output_file.write_all(b"]));\n")?; - - // Rebuild if anything changes. - println!("cargo:rerun-if-changed=executors"); - Ok(()) -} - fn main() -> io::Result<()> { let out_dir = env::var("OUT_DIR").unwrap(); build_themes(&out_dir)?; - build_executors(&out_dir)?; Ok(()) } diff --git a/config-file-schema.json b/config-file-schema.json index d282112f..e1fa836f 100644 --- a/config-file-schema.json +++ b/config-file-schema.json @@ -211,6 +211,38 @@ }, "additionalProperties": false }, + "LanguageSnippetExecutionConfig": { + "description": "The snippet execution configuration for a specific programming language.", + "type": "object", + "required": [ + "commands", + "filename" + ], + "properties": { + "commands": { + "description": "The commands to be run when executing snippets for this programming language.", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "environment": { + "description": "The environment variables to set before invoking every command.", + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "filename": { + "description": "The filename to use for the snippet input file.", + "type": "string" + } + } + }, "MermaidConfig": { "type": "object", "properties": { @@ -293,6 +325,13 @@ "enable" ], "properties": { + "custom": { + "description": "Custom snippet executors.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/LanguageSnippetExecutionConfig" + } + }, "enable": { "description": "Whether to enable snippet execution.", "type": "boolean" diff --git a/executors.yaml b/executors.yaml new file mode 100644 index 00000000..a516c65b --- /dev/null +++ b/executors.yaml @@ -0,0 +1,73 @@ +bash: + filename: script.sh + commands: + - ["bash", "script.sh"] +c++: + filename: snippet.cpp + commands: + - ["g++", "-std=c++20", "snippet.cpp", "-o", "snippet"] + - ["./snippet"] +c: + filename: snippet.c + commands: + - ["gcc", "snippet.c", "-o", "snippet"] + - ["./snippet"] +fish: + filename: script.fish + commands: + - ["fish", "script.fish"] +go: + filename: snippet.go + environment: + GO11MODULE: off + commands: + - ["go", "run", "snippet.go"] +java: + filename: Snippet.java + commands: + - ["java", "Snippet.java"] +js: + filename: snippet.js + commands: + - ["node", "snippet.js"] +kotlin: + filename: snippet.kts + commands: + - ["kotlinc", "-script", "script.kts"] +lua: + filename: snippet.lua + commands: + - ["lua", "snippet.lua"] +nushell: + filename: snippet.nu + commands: + - ["nu", "snippet.nu"] +perl: + filename: snippet.pl + commands: + - ["perl", "snippet.pl"] +python: + filename: snippet.py + commands: + - ["python", "-u", "snippet.py"] +ruby: + filename: snippet.rb + commands: + - ["ruby", "snippet.rb"] +rust-script: + filename: snippet.rs + commands: + - ["rust-script", "snippet.rs"] +rust: + filename: snippet.rs + commands: + - ["rustc", "--crate-name", "presenterm_snippet", "snippet.rs", "-o", "snippet"] + - ["./snippet"] +sh: + filename: script.sh + commands: + - ["sh", "script.sh"] +zsh: + filename: script.sh + commands: + - ["zsh", "script.sh"] diff --git a/executors/c++.sh b/executors/c++.sh deleted file mode 100644 index 6b32dc4a..00000000 --- a/executors/c++.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -temp=$(mktemp) -g++ -std=c++20 -x c++ "$1" -o "$temp" -"$temp" diff --git a/executors/c.sh b/executors/c.sh deleted file mode 100644 index 502ac654..00000000 --- a/executors/c.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -temp=$(mktemp) -gcc -x c "$1" -o "$temp" -"$temp" diff --git a/executors/go.sh b/executors/go.sh deleted file mode 100644 index 5845525f..00000000 --- a/executors/go.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -export GO111MODULE=off -tempdir=$(mktemp -d) -cd "$tempdir" -mv "$1" main.go -go run main.go -rm -rf "$tempdir" diff --git a/executors/java.sh b/executors/java.sh deleted file mode 100644 index df77d3e1..00000000 --- a/executors/java.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -tempdir=$(mktemp -d) -cd "$tempdir" -cp "$1" Main.java -java Main.java -rm -rf "$tempdir" diff --git a/executors/js.sh b/executors/js.sh deleted file mode 100644 index faee6097..00000000 --- a/executors/js.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -node "$1" diff --git a/executors/kotlin.sh b/executors/kotlin.sh deleted file mode 100644 index e6bbf1f5..00000000 --- a/executors/kotlin.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -tempdir=$(mktemp -d) -cd "$tempdir" -cp "$1" script.kts -kotlinc -script script.kts -rm -rf "$tempdir" diff --git a/executors/lua.sh b/executors/lua.sh deleted file mode 100644 index ff061c17..00000000 --- a/executors/lua.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -lua "$1" diff --git a/executors/nushell.sh b/executors/nushell.sh deleted file mode 100644 index 7a4285fa..00000000 --- a/executors/nushell.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -nu "$1" diff --git a/executors/perl.sh b/executors/perl.sh deleted file mode 100644 index c68ff366..00000000 --- a/executors/perl.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -perl "$1" diff --git a/executors/python.sh b/executors/python.sh deleted file mode 100644 index 8d3de71e..00000000 --- a/executors/python.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -exec python -u "$1" diff --git a/executors/ruby.sh b/executors/ruby.sh deleted file mode 100644 index 70134e5c..00000000 --- a/executors/ruby.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -ruby "$1" diff --git a/executors/rust-script.sh b/executors/rust-script.sh deleted file mode 100755 index 04b7836d..00000000 --- a/executors/rust-script.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -rust-script "$1" diff --git a/executors/rust.sh b/executors/rust.sh deleted file mode 100644 index e8e34e24..00000000 --- a/executors/rust.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -temp=$(mktemp) -rustc --crate-name "presenterm_snippet" "$1" -o "$temp" -"$temp" diff --git a/flake.nix b/flake.nix index be0b6409..656c6740 100644 --- a/flake.nix +++ b/flake.nix @@ -27,7 +27,7 @@ "src" "themes" "bat" - "executors" + "executors.yaml" ]; buildSrc = flakeboxLib.filterSubPaths { diff --git a/src/custom.rs b/src/custom.rs index 29775d1d..11f34554 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -1,12 +1,17 @@ use crate::{ input::user::KeyBinding, + markdown::elements::CodeLanguage, media::{emulator::TerminalEmulator, kitty::KittyMode}, GraphicsMode, }; use clap::ValueEnum; use schemars::JsonSchema; use serde::Deserialize; -use std::{fs, io, path::Path}; +use std::{ + collections::{BTreeMap, HashMap}, + fs, io, + path::Path, +}; #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] @@ -134,6 +139,10 @@ pub struct SnippetConfig { pub struct SnippetExecConfig { /// Whether to enable snippet execution. pub enable: bool, + + /// Custom snippet executors. + #[serde(default)] + pub custom: BTreeMap, } #[derive(Clone, Debug, Deserialize, JsonSchema)] @@ -190,6 +199,20 @@ pub(crate) fn default_mermaid_scale() -> u32 { 2 } +/// The snippet execution configuration for a specific programming language. +#[derive(Clone, Debug, Deserialize, JsonSchema)] +pub struct LanguageSnippetExecutionConfig { + /// The filename to use for the snippet input file. + pub filename: String, + + /// The environment variables to set before invoking every command. + #[serde(default)] + pub environment: HashMap, + + /// The commands to be run when executing snippets for this programming language. + pub commands: Vec>, +} + #[derive(Clone, Debug, Default, Deserialize, ValueEnum, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum ImageProtocol { diff --git a/src/demo.rs b/src/demo.rs index 1b811aaf..409cd3e0 100644 --- a/src/demo.rs +++ b/src/demo.rs @@ -1,5 +1,5 @@ use crate::{ - execute::CodeExecutor, + execute::SnippetExecutor, input::{ source::Command, user::{CommandKeyBindings, UserInput}, @@ -102,7 +102,7 @@ impl ThemesDemo { let mut resources = Resources::new("non_existent", image_registry.clone()); let mut third_party = ThirdPartyRender::default(); let options = PresentationBuilderOptions::default(); - let executer = Rc::new(CodeExecutor::default()); + let executer = Rc::new(SnippetExecutor::default()); let bindings_config = Default::default(); let builder = PresentationBuilder::new( theme, diff --git a/src/execute.rs b/src/execute.rs index 7f9dc567..5b69ab95 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -1,60 +1,54 @@ //! Code execution. -use crate::markdown::elements::{Code, CodeLanguage}; +use crate::{ + custom::LanguageSnippetExecutionConfig, + markdown::elements::{Code, CodeLanguage}, +}; +use once_cell::sync::Lazy; +use os_pipe::PipeReader; use std::{ - collections::BTreeMap, - ffi::OsStr, - fs, + collections::{BTreeMap, HashMap}, + fs::File, io::{self, BufRead, BufReader, Write}, - path::{Path, PathBuf}, - process::{self, Stdio}, + process::{self, Child, Stdio}, sync::{Arc, Mutex}, thread, }; -use tempfile::NamedTempFile; +use tempfile::TempDir; -include!(concat!(env!("OUT_DIR"), "/executors.rs")); +static EXECUTORS: Lazy> = + Lazy::new(|| serde_yaml::from_slice(include_bytes!("../executors.yaml")).expect("executors.yaml is broken")); /// Allows executing code. -#[derive(Default, Debug)] -pub struct CodeExecutor { - custom_executors: BTreeMap>, +#[derive(Debug)] +pub struct SnippetExecutor { + executors: BTreeMap, } -impl CodeExecutor { - pub fn load(executors_path: &Path) -> Result { - let mut custom_executors = BTreeMap::new(); - if let Ok(paths) = fs::read_dir(executors_path) { - for executor in paths { - let executor = executor?; - let path = executor.path(); - let filename = path.file_name().unwrap_or_default().to_string_lossy(); - let Some((name, extension)) = filename.split_once('.') else { - return Err(LoadExecutorsError::InvalidExecutor(path, "no extension")); - }; - if extension != "sh" { - return Err(LoadExecutorsError::InvalidExecutor(path, "non .sh extension")); +impl SnippetExecutor { + pub fn new( + custom_executors: BTreeMap, + ) -> Result { + let mut executors = EXECUTORS.clone(); + executors.extend(custom_executors); + for (language, config) in &executors { + if config.filename.is_empty() { + return Err(InvalidSnippetConfig(language.clone(), "filename is empty")); + } + if config.commands.is_empty() { + return Err(InvalidSnippetConfig(language.clone(), "no commands given")); + } + for command in &config.commands { + if command.is_empty() { + return Err(InvalidSnippetConfig(language.clone(), "empty command given")); } - let language: CodeLanguage = match name.parse() { - Ok(CodeLanguage::Unknown(_)) => { - return Err(LoadExecutorsError::InvalidExecutor(path, "unknown language")); - } - Ok(language) => language, - Err(_) => return Err(LoadExecutorsError::InvalidExecutor(path, "invalid code language")), - }; - let file_contents = fs::read(path)?; - custom_executors.insert(language, file_contents); } } - Ok(Self { custom_executors }) + Ok(Self { executors }) } pub(crate) fn is_execution_supported(&self, language: &CodeLanguage) -> bool { - if matches!(language, CodeLanguage::Shell(_)) { - true - } else { - EXECUTORS.contains_key(language) || self.custom_executors.contains_key(language) - } + self.executors.contains_key(language) } /// Execute a piece of code. @@ -62,70 +56,39 @@ impl CodeExecutor { if !code.attributes.execute { return Err(CodeExecuteError::NotExecutableCode); } - match &code.language { - CodeLanguage::Shell(interpreter) => { - let args: &[&str] = &[]; - Self::execute_shell(interpreter, code.executable_contents().as_bytes(), args) - } - lang => { - let executor = self.executor(lang).ok_or(CodeExecuteError::UnsupportedExecution)?; - Self::execute_lang(executor, code.executable_contents().as_bytes()) - } - } + let Some(config) = self.executors.get(&code.language) else { + return Err(CodeExecuteError::UnsupportedExecution); + }; + Self::execute_lang(config, code.executable_contents().as_bytes()) } - fn executor(&self, language: &CodeLanguage) -> Option<&[u8]> { - if let Some(executor) = self.custom_executors.get(language) { - return Some(executor); + fn execute_lang(config: &LanguageSnippetExecutionConfig, code: &[u8]) -> Result { + let script_dir = + tempfile::Builder::default().prefix(".presenterm").tempdir().map_err(CodeExecuteError::TempDir)?; + let snippet_path = script_dir.path().join(&config.filename); + { + let mut snippet_file = File::create(snippet_path).map_err(CodeExecuteError::TempDir)?; + snippet_file.write_all(code).map_err(CodeExecuteError::TempDir)?; } - EXECUTORS.get(language).copied() - } - - fn execute_shell(interpreter: &str, code: &[u8], args: &[S]) -> Result - where - S: AsRef, - { - let mut output_file = NamedTempFile::new().map_err(CodeExecuteError::TempFile)?; - output_file.write_all(code).map_err(CodeExecuteError::TempFile)?; - output_file.flush().map_err(CodeExecuteError::TempFile)?; - let (reader, writer) = os_pipe::pipe().map_err(CodeExecuteError::Pipe)?; - let writer_clone = writer.try_clone().map_err(CodeExecuteError::Pipe)?; - let process_handle = process::Command::new("/usr/bin/env") - .arg(interpreter) - .arg(output_file.path()) - .args(args) - .stdin(Stdio::null()) - .stdout(writer) - .stderr(writer_clone) - .spawn() - .map_err(CodeExecuteError::SpawnProcess)?; let state: Arc> = Default::default(); - let reader_handle = ProcessReader::spawn(process_handle, state.clone(), output_file, reader); - let handle = ExecutionHandle { state, reader_handle, program_path: None }; + let reader_handle = + CommandsRunner::spawn(state.clone(), script_dir, config.commands.clone(), config.environment.clone()); + let handle = ExecutionHandle { state, reader_handle }; Ok(handle) } +} - fn execute_lang(executor: &[u8], code: &[u8]) -> Result { - let mut code_file = NamedTempFile::new().map_err(CodeExecuteError::TempFile)?; - code_file.write_all(code).map_err(CodeExecuteError::TempFile)?; - - let path = code_file.path(); - let mut handle = Self::execute_shell("bash", executor, &[path])?; - handle.program_path = Some(code_file); - Ok(handle) +impl Default for SnippetExecutor { + fn default() -> Self { + Self::new(Default::default()).expect("initialization failed") } } -/// An error during the load of custom executors. +/// An invalid executor was found. #[derive(thiserror::Error, Debug)] -pub enum LoadExecutorsError { - #[error("io: {0}")] - Io(#[from] io::Error), - - #[error("invalid executor '{0}': {1}")] - InvalidExecutor(PathBuf, &'static str), -} +#[error("invalid snippet execution for '{0:?}': {1}")] +pub struct InvalidSnippetConfig(CodeLanguage, &'static str); /// An error during the execution of some code. #[derive(thiserror::Error, Debug)] @@ -136,11 +99,11 @@ pub(crate) enum CodeExecuteError { #[error("code is not marked for execution")] NotExecutableCode, - #[error("error creating temporary file: {0}")] - TempFile(io::Error), + #[error("error creating temporary directory: {0}")] + TempDir(io::Error), - #[error("error spawning process: {0}")] - SpawnProcess(io::Error), + #[error("error spawning process '{0}': {1}")] + SpawnProcess(String, io::Error), #[error("error creating pipe: {0}")] Pipe(io::Error), @@ -152,7 +115,6 @@ pub(crate) struct ExecutionHandle { state: Arc>, #[allow(dead_code)] reader_handle: thread::JoinHandle<()>, - program_path: Option, } impl ExecutionHandle { @@ -163,38 +125,75 @@ impl ExecutionHandle { } /// Consumes the output of a process and stores it in a shared state. -struct ProcessReader { - handle: process::Child, +struct CommandsRunner { state: Arc>, - #[allow(dead_code)] - file_handle: NamedTempFile, - reader: os_pipe::PipeReader, + script_directory: TempDir, } -impl ProcessReader { +impl CommandsRunner { fn spawn( - handle: process::Child, state: Arc>, - file_handle: NamedTempFile, - reader: os_pipe::PipeReader, + script_directory: TempDir, + commands: Vec>, + env: HashMap, ) -> thread::JoinHandle<()> { - let reader = Self { handle, state, file_handle, reader }; - thread::spawn(|| reader.run()) + let reader = Self { state, script_directory }; + thread::spawn(|| reader.run(commands, env)) } - fn run(mut self) { - let _ = Self::process_output(self.state.clone(), self.reader); - let success = match self.handle.wait() { - Ok(code) => code.success(), - _ => false, - }; - let status = match success { + fn run(self, commands: Vec>, env: HashMap) { + let mut last_result = true; + for command in commands { + last_result = self.run_command(command, &env); + if !last_result { + break; + } + } + let status = match last_result { true => ProcessStatus::Success, false => ProcessStatus::Failure, }; self.state.lock().unwrap().status = status; } + fn run_command(&self, command: Vec, env: &HashMap) -> bool { + let (mut child, reader) = match self.launch_process(command, env) { + Ok(inner) => inner, + Err(e) => { + let mut state = self.state.lock().unwrap(); + state.status = ProcessStatus::Failure; + state.output.push(e.to_string()); + return false; + } + }; + let _ = Self::process_output(self.state.clone(), reader); + + match child.wait() { + Ok(code) => code.success(), + _ => false, + } + } + + fn launch_process( + &self, + commands: Vec, + env: &HashMap, + ) -> Result<(Child, PipeReader), CodeExecuteError> { + let (reader, writer) = os_pipe::pipe().map_err(CodeExecuteError::Pipe)?; + let writer_clone = writer.try_clone().map_err(CodeExecuteError::Pipe)?; + let (command, args) = commands.split_first().expect("no commands"); + let child = process::Command::new(command) + .args(args) + .envs(env) + .current_dir(self.script_directory.path()) + .stdin(Stdio::null()) + .stdout(writer) + .stderr(writer_clone) + .spawn() + .map_err(|e| CodeExecuteError::SpawnProcess(command.clone(), e))?; + Ok((child, reader)) + } + fn process_output(state: Arc>, reader: os_pipe::PipeReader) -> io::Result<()> { let reader = BufReader::new(reader); for line in reader.lines() { @@ -231,8 +230,6 @@ impl ProcessStatus { #[cfg(test)] mod test { - use tempfile::tempdir; - use super::*; use crate::markdown::elements::CodeAttributes; @@ -247,7 +244,7 @@ echo 'bye'" language: CodeLanguage::Shell("sh".into()), attributes: CodeAttributes { execute: true, ..Default::default() }, }; - let handle = CodeExecutor::default().execute(&code).expect("execution failed"); + let handle = SnippetExecutor::default().execute(&code).expect("execution failed"); let state = loop { let state = handle.state(); if state.status.is_finished() { @@ -267,7 +264,7 @@ echo 'bye'" language: CodeLanguage::Shell("sh".into()), attributes: CodeAttributes { execute: false, ..Default::default() }, }; - let result = CodeExecutor::default().execute(&code); + let result = SnippetExecutor::default().execute(&code); assert!(result.is_err()); } @@ -283,7 +280,7 @@ echo 'hello world' language: CodeLanguage::Shell("sh".into()), attributes: CodeAttributes { execute: true, ..Default::default() }, }; - let handle = CodeExecutor::default().execute(&code).expect("execution failed"); + let handle = SnippetExecutor::default().execute(&code).expect("execution failed"); let state = loop { let state = handle.state(); if state.status.is_finished() { @@ -308,7 +305,7 @@ echo 'hello world' language: CodeLanguage::Shell("sh".into()), attributes: CodeAttributes { execute: true, ..Default::default() }, }; - let handle = CodeExecutor::default().execute(&code).expect("execution failed"); + let handle = SnippetExecutor::default().execute(&code).expect("execution failed"); let state = loop { let state = handle.state(); if state.status.is_finished() { @@ -322,28 +319,7 @@ echo 'hello world' } #[test] - fn custom_executor() { - let dir = tempdir().unwrap(); - fs::write(dir.path().join("rust.sh"), "hi").expect("writing script failed"); - let executor = CodeExecutor::load(dir.path()).expect("load filed"); - - let script = executor.custom_executors.get(&CodeLanguage::Rust).expect("rust not found"); - assert_eq!(script, b"hi"); - } - - #[test] - fn unknown_executor_language() { - let dir = tempdir().unwrap(); - fs::write(dir.path().join("potato.sh"), "").expect("writing script failed"); - let executor = CodeExecutor::load(dir.path()); - assert!(matches!(executor, Err(LoadExecutorsError::InvalidExecutor(_, _)))); - } - - #[test] - fn invalid_executor_extension() { - let dir = tempdir().unwrap(); - fs::write(dir.path().join("rust.potato"), "").expect("writing script failed"); - let executor = CodeExecutor::load(dir.path()); - assert!(matches!(executor, Err(LoadExecutorsError::InvalidExecutor(_, _)))); + fn built_in_executors() { + SnippetExecutor::new(Default::default()).expect("invalid default executors"); } } diff --git a/src/export.rs b/src/export.rs index 5cc4788f..9401805f 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,6 +1,6 @@ use crate::{ custom::KeyBindingsConfig, - execute::CodeExecutor, + execute::SnippetExecutor, markdown::parse::ParseError, media::{ image::{Image, ImageSource}, @@ -30,7 +30,7 @@ pub struct Exporter<'a> { default_theme: &'a PresentationTheme, resources: Resources, third_party: ThirdPartyRender, - code_executor: Rc, + code_executor: Rc, themes: Themes, options: PresentationBuilderOptions, } @@ -42,7 +42,7 @@ impl<'a> Exporter<'a> { default_theme: &'a PresentationTheme, resources: Resources, third_party: ThirdPartyRender, - code_executor: Rc, + code_executor: Rc, themes: Themes, options: PresentationBuilderOptions, ) -> Self { diff --git a/src/lib.rs b/src/lib.rs index d16d5e56..3e7917d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ pub(crate) mod tools; pub use crate::{ custom::{Config, ImageProtocol, ValidateOverflows}, demo::ThemesDemo, - execute::CodeExecutor, + execute::SnippetExecutor, export::{ExportError, Exporter}, input::source::CommandSource, markdown::parse::MarkdownParser, diff --git a/src/main.rs b/src/main.rs index 49de3174..45d89a87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,10 @@ use clap::{error::ErrorKind, CommandFactory, Parser}; use comrak::Arena; use directories::ProjectDirs; use presenterm::{ - CodeExecutor, CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol, - ImageRegistry, MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme, PresentationThemeSet, - Presenter, PresenterOptions, Resources, Themes, ThemesDemo, ThirdPartyConfigs, ThirdPartyRender, ValidateOverflows, + CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol, ImageRegistry, + MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme, PresentationThemeSet, Presenter, + PresenterOptions, Resources, SnippetExecutor, Themes, ThemesDemo, ThirdPartyConfigs, ThirdPartyRender, + ValidateOverflows, }; use std::{ env, io, @@ -91,7 +92,7 @@ fn create_splash() -> String { struct Customizations { config: Config, themes: Themes, - code_executor: CodeExecutor, + code_executor: SnippetExecutor, } fn load_customizations(config_file_path: Option) -> Result> { @@ -107,7 +108,7 @@ fn load_customizations(config_file_path: Option) -> Result { parser: MarkdownParser<'a>, resources: Resources, third_party: ThirdPartyRender, - code_executor: Rc, + code_executor: Rc, state: PresenterState, slides_with_pending_async_renders: HashSet, image_printer: Arc, @@ -63,7 +63,7 @@ impl<'a> Presenter<'a> { parser: MarkdownParser<'a>, resources: Resources, third_party: ThirdPartyRender, - code_executor: Rc, + code_executor: Rc, themes: Themes, image_printer: Arc, options: PresenterOptions, diff --git a/src/processing/builder.rs b/src/processing/builder.rs index 94a876f0..22621beb 100644 --- a/src/processing/builder.rs +++ b/src/processing/builder.rs @@ -1,7 +1,7 @@ use super::{execution::SnippetExecutionDisabledOperation, modals::KeyBindingsModalBuilder}; use crate::{ custom::{KeyBindingsConfig, OptionsConfig}, - execute::CodeExecutor, + execute::SnippetExecutor, markdown::{ elements::{ Code, CodeLanguage, Highlight, HighlightGroup, ListItem, ListItemType, MarkdownElement, ParagraphElement, @@ -96,7 +96,7 @@ pub(crate) struct PresentationBuilder<'a> { chunk_mutators: Vec>, slides: Vec, highlighter: CodeHighlighter, - code_executor: Rc, + code_executor: Rc, theme: Cow<'a, PresentationTheme>, resources: &'a mut Resources, third_party: &'a mut ThirdPartyRender, @@ -117,7 +117,7 @@ impl<'a> PresentationBuilder<'a> { default_theme: &'a PresentationTheme, resources: &'a mut Resources, third_party: &'a mut ThirdPartyRender, - code_executor: Rc, + code_executor: Rc, themes: &'a Themes, image_registry: ImageRegistry, bindings_config: KeyBindingsConfig, @@ -1081,7 +1081,7 @@ mod test { let theme = PresentationTheme::default(); let mut resources = Resources::new("/tmp", Default::default()); let mut third_party = ThirdPartyRender::default(); - let code_executor = Rc::new(CodeExecutor::default()); + let code_executor = Rc::new(SnippetExecutor::default()); let themes = Themes::default(); let bindings = KeyBindingsConfig::default(); let builder = PresentationBuilder::new( diff --git a/src/processing/execution.rs b/src/processing/execution.rs index fbeeb436..f26e24bf 100644 --- a/src/processing/execution.rs +++ b/src/processing/execution.rs @@ -1,6 +1,6 @@ use super::separator::RenderSeparator; use crate::{ - execute::{CodeExecutor, ExecutionHandle, ExecutionState, ProcessStatus}, + execute::{ExecutionHandle, ExecutionState, ProcessStatus, SnippetExecutor}, markdown::elements::{Code, Text, TextBlock}, presentation::{AsRenderOperations, PreformattedLine, RenderAsync, RenderAsyncState, RenderOperation}, render::properties::WindowSize, @@ -12,7 +12,7 @@ use std::{cell::RefCell, mem, rc::Rc}; use unicode_width::UnicodeWidthStr; #[derive(Debug)] -struct RunCodeOperationInner { +struct RunSnippetOperationInner { handle: Option, output_lines: Vec, state: RenderAsyncState, @@ -21,24 +21,24 @@ struct RunCodeOperationInner { #[derive(Debug)] pub(crate) struct RunSnippetOperation { code: Code, - executor: Rc, + executor: Rc, default_colors: Colors, block_colors: Colors, status_colors: ExecutionStatusBlockStyle, - inner: Rc>, + inner: Rc>, state_description: RefCell, } impl RunSnippetOperation { pub(crate) fn new( code: Code, - executor: Rc, + executor: Rc, default_colors: Colors, block_colors: Colors, status_colors: ExecutionStatusBlockStyle, ) -> Self { let inner = - RunCodeOperationInner { handle: None, output_lines: Vec::new(), state: RenderAsyncState::default() }; + RunSnippetOperationInner { handle: None, output_lines: Vec::new(), state: RenderAsyncState::default() }; let running_colors = status_colors.running.clone(); Self { code,