mirror of
https://github.com/mfontanini/presenterm.git
synced 2025-05-05 23:42:59 +00:00
feat: allow executing arbitrary programming langs
This commit is contained in:
parent
95ee33f1f2
commit
e1e88c780a
41
build.rs
41
build.rs
@ -6,13 +6,13 @@ use std::{
|
|||||||
|
|
||||||
// Take all files under `themes` and turn them into a file that contains a hashmap with their
|
// Take all files under `themes` and turn them into a file that contains a hashmap with their
|
||||||
// contents by name. This is pulled in theme.rs to construct themes.
|
// contents by name. This is pulled in theme.rs to construct themes.
|
||||||
fn main() -> io::Result<()> {
|
fn build_themes(out_dir: &str) -> io::Result<()> {
|
||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
|
||||||
let output_path = format!("{out_dir}/themes.rs");
|
let output_path = format!("{out_dir}/themes.rs");
|
||||||
let mut output_file = BufWriter::new(File::create(output_path)?);
|
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 std::collections::BTreeMap as Map;\n")?;
|
||||||
output_file.write_all(b"use once_cell::sync::Lazy;\n")?;
|
output_file.write_all(b"use once_cell::sync::Lazy;\n")?;
|
||||||
output_file.write_all(b"static THEMES: Lazy<Map<&'static str, &'static [u8]>> = Lazy::new(|| Map::from([\n")?;
|
output_file.write_all(b"static THEMES: Lazy<Map<&'static str, &'static [u8]>> = Lazy::new(|| Map::from([\n")?;
|
||||||
|
|
||||||
let mut paths = fs::read_dir("themes")?.collect::<io::Result<Vec<_>>>()?;
|
let mut paths = fs::read_dir("themes")?.collect::<io::Result<Vec<_>>>()?;
|
||||||
paths.sort_by_key(|e| e.path());
|
paths.sort_by_key(|e| e.path());
|
||||||
for theme_file in paths {
|
for theme_file in paths {
|
||||||
@ -33,3 +33,40 @@ fn main() -> io::Result<()> {
|
|||||||
println!("cargo:rerun-if-changed=themes");
|
println!("cargo:rerun-if-changed=themes");
|
||||||
Ok(())
|
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<Map<crate::markdown::elements::CodeLanguage, &'static [u8]>> = Lazy::new(|| Map::from([\n")?;
|
||||||
|
|
||||||
|
let mut paths = fs::read_dir("executors")?.collect::<io::Result<Vec<_>>>()?;
|
||||||
|
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 executor_name = file_name.split_once('.').unwrap().0;
|
||||||
|
output_file.write_all(
|
||||||
|
format!("(crate::markdown::elements::CodeLanguage::{executor_name}, {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(())
|
||||||
|
}
|
||||||
|
3
executors/Python.sh
Normal file
3
executors/Python.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
exec python "$1"
|
@ -1,4 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
execute::CodeExecuter,
|
||||||
input::{
|
input::{
|
||||||
source::Command,
|
source::Command,
|
||||||
user::{CommandKeyBindings, UserInput},
|
user::{CommandKeyBindings, UserInput},
|
||||||
@ -101,11 +102,13 @@ impl<W: TerminalWrite> ThemesDemo<W> {
|
|||||||
let mut resources = Resources::new("non_existent", image_registry.clone());
|
let mut resources = Resources::new("non_existent", image_registry.clone());
|
||||||
let mut typst = TypstRender::default();
|
let mut typst = TypstRender::default();
|
||||||
let options = PresentationBuilderOptions::default();
|
let options = PresentationBuilderOptions::default();
|
||||||
|
let executer = CodeExecuter;
|
||||||
let bindings_config = Default::default();
|
let bindings_config = Default::default();
|
||||||
let builder = PresentationBuilder::new(
|
let builder = PresentationBuilder::new(
|
||||||
theme,
|
theme,
|
||||||
&mut resources,
|
&mut resources,
|
||||||
&mut typst,
|
&mut typst,
|
||||||
|
&executer,
|
||||||
&self.themes,
|
&self.themes,
|
||||||
image_registry,
|
image_registry,
|
||||||
bindings_config,
|
bindings_config,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use crate::markdown::elements::{Code, CodeLanguage};
|
use crate::markdown::elements::{Code, CodeLanguage};
|
||||||
use std::{
|
use std::{
|
||||||
|
ffi::OsStr,
|
||||||
io::{self, BufRead, BufReader, Write},
|
io::{self, BufRead, BufReader, Write},
|
||||||
process::{self, Stdio},
|
process::{self, Stdio},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
@ -9,37 +10,46 @@ use std::{
|
|||||||
};
|
};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/executors.rs"));
|
||||||
|
|
||||||
/// Allows executing code.
|
/// Allows executing code.
|
||||||
pub(crate) struct CodeExecuter;
|
pub struct CodeExecuter;
|
||||||
|
|
||||||
impl CodeExecuter {
|
impl CodeExecuter {
|
||||||
pub(crate) fn is_execution_supported(&self, language: &CodeLanguage) -> bool {
|
pub(crate) fn is_execution_supported(&self, language: &CodeLanguage) -> bool {
|
||||||
matches!(language, CodeLanguage::Shell(_))
|
if matches!(language, CodeLanguage::Shell(_)) { true } else { EXECUTORS.contains_key(language) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a piece of code.
|
/// Execute a piece of code.
|
||||||
pub(crate) fn execute(&self, code: &Code) -> Result<ExecutionHandle, CodeExecuteError> {
|
pub(crate) fn execute(&self, code: &Code) -> Result<ExecutionHandle, CodeExecuteError> {
|
||||||
if !self.is_execution_supported(&code.language) {
|
|
||||||
return Err(CodeExecuteError::UnsupportedExecution);
|
|
||||||
}
|
|
||||||
if !code.attributes.execute {
|
if !code.attributes.execute {
|
||||||
return Err(CodeExecuteError::NotExecutableCode);
|
return Err(CodeExecuteError::NotExecutableCode);
|
||||||
}
|
}
|
||||||
match &code.language {
|
match &code.language {
|
||||||
CodeLanguage::Shell(interpreter) => Self::execute_shell(interpreter, &code.contents),
|
CodeLanguage::Shell(interpreter) => {
|
||||||
_ => Err(CodeExecuteError::UnsupportedExecution),
|
let args: &[&str] = &[];
|
||||||
|
Self::execute_shell(interpreter, code.contents.as_bytes(), args)
|
||||||
|
}
|
||||||
|
lang => {
|
||||||
|
let executor = EXECUTORS.get(lang).ok_or(CodeExecuteError::UnsupportedExecution)?;
|
||||||
|
Self::execute_lang(executor, code.contents.as_bytes())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_shell(interpreter: &str, code: &str) -> Result<ExecutionHandle, CodeExecuteError> {
|
fn execute_shell<S>(interpreter: &str, code: &[u8], args: &[S]) -> Result<ExecutionHandle, CodeExecuteError>
|
||||||
|
where
|
||||||
|
S: AsRef<OsStr>,
|
||||||
|
{
|
||||||
let mut output_file = NamedTempFile::new().map_err(CodeExecuteError::TempFile)?;
|
let mut output_file = NamedTempFile::new().map_err(CodeExecuteError::TempFile)?;
|
||||||
output_file.write_all(code.as_bytes()).map_err(CodeExecuteError::TempFile)?;
|
output_file.write_all(code).map_err(CodeExecuteError::TempFile)?;
|
||||||
output_file.flush().map_err(CodeExecuteError::TempFile)?;
|
output_file.flush().map_err(CodeExecuteError::TempFile)?;
|
||||||
let (reader, writer) = os_pipe::pipe().map_err(CodeExecuteError::Pipe)?;
|
let (reader, writer) = os_pipe::pipe().map_err(CodeExecuteError::Pipe)?;
|
||||||
let writer_clone = writer.try_clone().map_err(CodeExecuteError::Pipe)?;
|
let writer_clone = writer.try_clone().map_err(CodeExecuteError::Pipe)?;
|
||||||
let process_handle = process::Command::new("/usr/bin/env")
|
let process_handle = process::Command::new("/usr/bin/env")
|
||||||
.arg(interpreter)
|
.arg(interpreter)
|
||||||
.arg(output_file.path())
|
.arg(output_file.path())
|
||||||
|
.args(args)
|
||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
.stdout(writer)
|
.stdout(writer)
|
||||||
.stderr(writer_clone)
|
.stderr(writer_clone)
|
||||||
@ -48,7 +58,17 @@ impl CodeExecuter {
|
|||||||
|
|
||||||
let state: Arc<Mutex<ExecutionState>> = Default::default();
|
let state: Arc<Mutex<ExecutionState>> = Default::default();
|
||||||
let reader_handle = ProcessReader::spawn(process_handle, state.clone(), output_file, reader);
|
let reader_handle = ProcessReader::spawn(process_handle, state.clone(), output_file, reader);
|
||||||
let handle = ExecutionHandle { state, reader_handle };
|
let handle = ExecutionHandle { state, reader_handle, program_path: None };
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_lang(executor: &[u8], code: &[u8]) -> Result<ExecutionHandle, CodeExecuteError> {
|
||||||
|
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)
|
Ok(handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,6 +98,7 @@ pub(crate) struct ExecutionHandle {
|
|||||||
state: Arc<Mutex<ExecutionState>>,
|
state: Arc<Mutex<ExecutionState>>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
reader_handle: thread::JoinHandle<()>,
|
reader_handle: thread::JoinHandle<()>,
|
||||||
|
program_path: Option<NamedTempFile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionHandle {
|
impl ExecutionHandle {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
custom::KeyBindingsConfig,
|
custom::KeyBindingsConfig,
|
||||||
|
execute::CodeExecuter,
|
||||||
markdown::parse::ParseError,
|
markdown::parse::ParseError,
|
||||||
media::{
|
media::{
|
||||||
image::{Image, ImageSource},
|
image::{Image, ImageSource},
|
||||||
@ -83,6 +84,7 @@ impl<'a> Exporter<'a> {
|
|||||||
self.default_theme,
|
self.default_theme,
|
||||||
&mut self.resources,
|
&mut self.resources,
|
||||||
&mut self.typst,
|
&mut self.typst,
|
||||||
|
&CodeExecuter,
|
||||||
&self.themes,
|
&self.themes,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
KeyBindingsConfig::default(),
|
KeyBindingsConfig::default(),
|
||||||
|
@ -23,6 +23,7 @@ pub(crate) mod typst;
|
|||||||
pub use crate::{
|
pub use crate::{
|
||||||
custom::{Config, ImageProtocol, ValidateOverflows},
|
custom::{Config, ImageProtocol, ValidateOverflows},
|
||||||
demo::ThemesDemo,
|
demo::ThemesDemo,
|
||||||
|
execute::CodeExecuter,
|
||||||
export::{ExportError, Exporter},
|
export::{ExportError, Exporter},
|
||||||
input::source::CommandSource,
|
input::source::CommandSource,
|
||||||
markdown::parse::MarkdownParser,
|
markdown::parse::MarkdownParser,
|
||||||
|
10
src/main.rs
10
src/main.rs
@ -2,9 +2,9 @@ use clap::{error::ErrorKind, CommandFactory, Parser};
|
|||||||
use comrak::Arena;
|
use comrak::Arena;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use presenterm::{
|
use presenterm::{
|
||||||
CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol, ImageRegistry,
|
CodeExecuter, CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol,
|
||||||
LoadThemeError, MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme, PresentationThemeSet,
|
ImageRegistry, LoadThemeError, MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme,
|
||||||
Presenter, PresenterOptions, Resources, Themes, ThemesDemo, TypstRender, ValidateOverflows,
|
PresentationThemeSet, Presenter, PresenterOptions, Resources, Themes, ThemesDemo, TypstRender, ValidateOverflows,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
env, io,
|
env, io,
|
||||||
@ -206,6 +206,7 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let registry = ImageRegistry(printer.clone());
|
let registry = ImageRegistry(printer.clone());
|
||||||
let resources = Resources::new(resources_path, registry.clone());
|
let resources = Resources::new(resources_path, registry.clone());
|
||||||
let typst = TypstRender::new(config.typst.ppi, registry, resources_path);
|
let typst = TypstRender::new(config.typst.ppi, registry, resources_path);
|
||||||
|
let code_executer = CodeExecuter;
|
||||||
if cli.export_pdf || cli.generate_pdf_metadata {
|
if cli.export_pdf || cli.generate_pdf_metadata {
|
||||||
let mut exporter = Exporter::new(parser, &default_theme, resources, typst, themes, options);
|
let mut exporter = Exporter::new(parser, &default_theme, resources, typst, themes, options);
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
@ -232,7 +233,8 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
bindings: config.bindings,
|
bindings: config.bindings,
|
||||||
validate_overflows,
|
validate_overflows,
|
||||||
};
|
};
|
||||||
let presenter = Presenter::new(&default_theme, commands, parser, resources, typst, themes, printer, options);
|
let presenter =
|
||||||
|
Presenter::new(&default_theme, commands, parser, resources, typst, code_executer, themes, printer, options);
|
||||||
presenter.present(&path)?;
|
presenter.present(&path)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -186,8 +186,8 @@ pub(crate) struct Code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The language of a piece of code.
|
/// The language of a piece of code.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, EnumIter)]
|
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, PartialOrd, Ord)]
|
||||||
pub(crate) enum CodeLanguage {
|
pub enum CodeLanguage {
|
||||||
Ada,
|
Ada,
|
||||||
Asp,
|
Asp,
|
||||||
Awk,
|
Awk,
|
||||||
|
@ -1,20 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
custom::KeyBindingsConfig,
|
custom::KeyBindingsConfig, diff::PresentationDiffer, execute::CodeExecuter, export::ImageReplacer, input::source::{Command, CommandSource}, markdown::parse::{MarkdownParser, ParseError}, media::{printer::ImagePrinter, register::ImageRegistry}, presentation::Presentation, processing::builder::{BuildError, PresentationBuilder, PresentationBuilderOptions, Themes}, render::{
|
||||||
diff::PresentationDiffer,
|
|
||||||
export::ImageReplacer,
|
|
||||||
input::source::{Command, CommandSource},
|
|
||||||
markdown::parse::{MarkdownParser, ParseError},
|
|
||||||
media::{printer::ImagePrinter, register::ImageRegistry},
|
|
||||||
presentation::Presentation,
|
|
||||||
processing::builder::{BuildError, PresentationBuilder, PresentationBuilderOptions, Themes},
|
|
||||||
render::{
|
|
||||||
draw::{RenderError, RenderResult, TerminalDrawer},
|
draw::{RenderError, RenderResult, TerminalDrawer},
|
||||||
properties::WindowSize,
|
properties::WindowSize,
|
||||||
validate::OverflowValidator,
|
validate::OverflowValidator,
|
||||||
},
|
}, resource::Resources, theme::PresentationTheme, typst::TypstRender
|
||||||
resource::Resources,
|
|
||||||
theme::PresentationTheme,
|
|
||||||
typst::TypstRender,
|
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
@ -43,6 +32,7 @@ pub struct Presenter<'a> {
|
|||||||
parser: MarkdownParser<'a>,
|
parser: MarkdownParser<'a>,
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
typst: TypstRender,
|
typst: TypstRender,
|
||||||
|
code_executer: CodeExecuter,
|
||||||
state: PresenterState,
|
state: PresenterState,
|
||||||
slides_with_pending_widgets: HashSet<usize>,
|
slides_with_pending_widgets: HashSet<usize>,
|
||||||
image_printer: Rc<ImagePrinter>,
|
image_printer: Rc<ImagePrinter>,
|
||||||
@ -59,6 +49,7 @@ impl<'a> Presenter<'a> {
|
|||||||
parser: MarkdownParser<'a>,
|
parser: MarkdownParser<'a>,
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
typst: TypstRender,
|
typst: TypstRender,
|
||||||
|
code_executer: CodeExecuter,
|
||||||
themes: Themes,
|
themes: Themes,
|
||||||
image_printer: Rc<ImagePrinter>,
|
image_printer: Rc<ImagePrinter>,
|
||||||
options: PresenterOptions,
|
options: PresenterOptions,
|
||||||
@ -69,6 +60,7 @@ impl<'a> Presenter<'a> {
|
|||||||
parser,
|
parser,
|
||||||
resources,
|
resources,
|
||||||
typst,
|
typst,
|
||||||
|
code_executer,
|
||||||
state: PresenterState::Empty,
|
state: PresenterState::Empty,
|
||||||
slides_with_pending_widgets: HashSet::new(),
|
slides_with_pending_widgets: HashSet::new(),
|
||||||
image_printer,
|
image_printer,
|
||||||
@ -257,6 +249,7 @@ impl<'a> Presenter<'a> {
|
|||||||
self.default_theme,
|
self.default_theme,
|
||||||
&mut self.resources,
|
&mut self.resources,
|
||||||
&mut self.typst,
|
&mut self.typst,
|
||||||
|
&self.code_executer,
|
||||||
&self.themes,
|
&self.themes,
|
||||||
ImageRegistry(self.image_printer.clone()),
|
ImageRegistry(self.image_printer.clone()),
|
||||||
self.options.bindings.clone(),
|
self.options.bindings.clone(),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
custom::{KeyBindingsConfig, OptionsConfig},
|
custom::{KeyBindingsConfig, OptionsConfig},
|
||||||
|
execute::CodeExecuter,
|
||||||
markdown::{
|
markdown::{
|
||||||
elements::{
|
elements::{
|
||||||
Code, CodeLanguage, Highlight, HighlightGroup, ListItem, ListItemType, MarkdownElement, ParagraphElement,
|
Code, CodeLanguage, Highlight, HighlightGroup, ListItem, ListItemType, MarkdownElement, ParagraphElement,
|
||||||
@ -94,6 +95,7 @@ pub(crate) struct PresentationBuilder<'a> {
|
|||||||
chunk_mutators: Vec<Box<dyn ChunkMutator>>,
|
chunk_mutators: Vec<Box<dyn ChunkMutator>>,
|
||||||
slides: Vec<Slide>,
|
slides: Vec<Slide>,
|
||||||
highlighter: CodeHighlighter,
|
highlighter: CodeHighlighter,
|
||||||
|
code_executer: &'a CodeExecuter,
|
||||||
theme: Cow<'a, PresentationTheme>,
|
theme: Cow<'a, PresentationTheme>,
|
||||||
resources: &'a mut Resources,
|
resources: &'a mut Resources,
|
||||||
typst: &'a mut TypstRender,
|
typst: &'a mut TypstRender,
|
||||||
@ -113,6 +115,7 @@ impl<'a> PresentationBuilder<'a> {
|
|||||||
default_theme: &'a PresentationTheme,
|
default_theme: &'a PresentationTheme,
|
||||||
resources: &'a mut Resources,
|
resources: &'a mut Resources,
|
||||||
typst: &'a mut TypstRender,
|
typst: &'a mut TypstRender,
|
||||||
|
code_executer: &'a CodeExecuter,
|
||||||
themes: &'a Themes,
|
themes: &'a Themes,
|
||||||
image_registry: ImageRegistry,
|
image_registry: ImageRegistry,
|
||||||
bindings_config: KeyBindingsConfig,
|
bindings_config: KeyBindingsConfig,
|
||||||
@ -124,6 +127,7 @@ impl<'a> PresentationBuilder<'a> {
|
|||||||
chunk_mutators: Vec::new(),
|
chunk_mutators: Vec::new(),
|
||||||
slides: Vec::new(),
|
slides: Vec::new(),
|
||||||
highlighter: CodeHighlighter::default(),
|
highlighter: CodeHighlighter::default(),
|
||||||
|
code_executer,
|
||||||
theme: Cow::Borrowed(default_theme),
|
theme: Cow::Borrowed(default_theme),
|
||||||
resources,
|
resources,
|
||||||
typst,
|
typst,
|
||||||
@ -680,7 +684,7 @@ impl<'a> PresentationBuilder<'a> {
|
|||||||
self.chunk_mutators.push(Box::new(HighlightMutator::new(context)));
|
self.chunk_mutators.push(Box::new(HighlightMutator::new(context)));
|
||||||
}
|
}
|
||||||
if code.attributes.execute {
|
if code.attributes.execute {
|
||||||
self.push_code_execution(code);
|
self.push_code_execution(code)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -728,7 +732,10 @@ impl<'a> PresentationBuilder<'a> {
|
|||||||
(output, context)
|
(output, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_code_execution(&mut self, code: Code) {
|
fn push_code_execution(&mut self, code: Code) -> Result<(), BuildError> {
|
||||||
|
if !self.code_executer.is_execution_supported(&code.language) {
|
||||||
|
return Err(BuildError::UnsupportedExecution(code.language));
|
||||||
|
}
|
||||||
let operation = RunCodeOperation::new(
|
let operation = RunCodeOperation::new(
|
||||||
code,
|
code,
|
||||||
self.theme.default_style.colors.clone(),
|
self.theme.default_style.colors.clone(),
|
||||||
@ -736,6 +743,7 @@ impl<'a> PresentationBuilder<'a> {
|
|||||||
);
|
);
|
||||||
let operation = RenderOperation::RenderOnDemand(Rc::new(operation));
|
let operation = RenderOperation::RenderOnDemand(Rc::new(operation));
|
||||||
self.chunk_operations.push(operation);
|
self.chunk_operations.push(operation);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn terminate_slide(&mut self) {
|
fn terminate_slide(&mut self) {
|
||||||
@ -898,6 +906,9 @@ pub enum BuildError {
|
|||||||
|
|
||||||
#[error("typst render failed: {0}")]
|
#[error("typst render failed: {0}")]
|
||||||
TypstRender(#[from] TypstRenderError),
|
TypstRender(#[from] TypstRenderError),
|
||||||
|
|
||||||
|
#[error("language {0:?} does not support execution")]
|
||||||
|
UnsupportedExecution(CodeLanguage),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
@ -1050,12 +1061,14 @@ mod test {
|
|||||||
let theme = PresentationTheme::default();
|
let theme = PresentationTheme::default();
|
||||||
let mut resources = Resources::new("/tmp", Default::default());
|
let mut resources = Resources::new("/tmp", Default::default());
|
||||||
let mut typst = TypstRender::default();
|
let mut typst = TypstRender::default();
|
||||||
|
let code_executer = CodeExecuter;
|
||||||
let themes = Themes::default();
|
let themes = Themes::default();
|
||||||
let bindings = KeyBindingsConfig::default();
|
let bindings = KeyBindingsConfig::default();
|
||||||
let builder = PresentationBuilder::new(
|
let builder = PresentationBuilder::new(
|
||||||
&theme,
|
&theme,
|
||||||
&mut resources,
|
&mut resources,
|
||||||
&mut typst,
|
&mut typst,
|
||||||
|
&code_executer,
|
||||||
&themes,
|
&themes,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
bindings,
|
bindings,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user