1use std::{
2 error::Error,
3 process::Command,
4 sync::{
5 atomic::{AtomicBool, Ordering},
6 Arc,
7 },
8};
9
10use clap::Args;
11
12use crate::{
13 cmd::run,
14 config::read_and_validate_config,
15 net::kill_port,
16 puppeteer::{is_puppeteer_chrome_installed, prompt_install_puppeteer_chrome},
17};
18
19#[derive(Args, Debug, Clone)]
21pub struct ExportOptions {
22 pub output: Option<String>,
24 #[arg(long, default_value_t = 3000)]
26 pub port: u16,
27}
28
29impl ExportOptions {
30 pub fn output_or_default(&self) -> String {
31 self.output.clone().unwrap_or_else(get_default_output)
32 }
33}
34
35fn get_default_output() -> String {
36 let default = String::from("slides.pdf");
37
38 let Ok(config) = read_and_validate_config() else {
39 println!("Warning: Could not read oseda-config.json");
40 println!("Using slides.pdf as name instead");
41 return default;
42 };
43
44 format!("{}.pdf", config.title.trim())
45}
46
47pub fn export(opts: ExportOptions) -> Result<(), Box<dyn Error>> {
49 println!("Cleaning any existing oseda processing...");
50 if kill_port(opts.port).is_err() {
51 eprintln!("Warning, could not kill value on desired port")
52 }
53
54 let output = Command::new("npm")
55 .args(["install", "decktape@3.15.0"])
56 .current_dir(".")
57 .output()?;
58
59 if !is_puppeteer_chrome_installed() {
61 prompt_install_puppeteer_chrome()?;
62 }
63 println!("Puppeteer Chrome is installed, continuing...");
64
65 if !output.status.success() {
66 eprintln!(
67 "Decktape installation failure: {}",
68 String::from_utf8_lossy(&output.stderr)
69 );
70 return Err("npm init failed".into());
71 }
72
73 let shutdown_flag = Arc::new(AtomicBool::new(false));
76 let run_flag = shutdown_flag.clone();
77
78 let run_handle = std::thread::spawn(move || run::run_with_shutdown(run_flag));
79
80 std::thread::sleep(std::time::Duration::from_millis(10000));
82
83 let addr = format!("http://localhost:{}", opts.port);
84
85 let export_output = Command::new("npm")
87 .args([
88 "exec",
89 "decktape",
90 "--",
91 "reveal",
92 "--fragments",
93 &addr,
94 &opts.output_or_default(),
95 ])
96 .output()?;
97
98 shutdown_flag.store(true, Ordering::SeqCst);
100 let _ = run_handle.join();
102
103 if !export_output.status.success() {
104 eprintln!(
105 "Decktape PDF export failure: {}",
106 String::from_utf8_lossy(&export_output.stderr)
107 );
108 return Err("npm init failed".into());
109 }
110
111 Ok(())
112}