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::{is_cwd_oseda_project, 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 if !is_cwd_oseda_project() {
50 return Err("Current working directory is not an Oseda project".into());
51 }
52
53 println!("Cleaning any existing oseda processing...");
54 if kill_port(opts.port).is_err() {
55 eprintln!("Warning, could not kill value on desired port")
56 }
57
58 let output = Command::new("npm")
59 .args(["install", "decktape@3.15.0"])
60 .current_dir(".")
61 .output()?;
62
63 if !is_puppeteer_chrome_installed() {
65 prompt_install_puppeteer_chrome()?;
66 }
67 println!("Puppeteer Chrome is installed, continuing...");
68
69 if !output.status.success() {
70 eprintln!(
71 "Decktape installation failure: {}",
72 String::from_utf8_lossy(&output.stderr)
73 );
74 return Err("npm init failed".into());
75 }
76
77 let shutdown_flag = Arc::new(AtomicBool::new(false));
80 let run_flag = shutdown_flag.clone();
81
82 let run_handle = std::thread::spawn(move || run::run_with_shutdown(run_flag));
83
84 std::thread::sleep(std::time::Duration::from_millis(10000));
86
87 let addr = format!("http://localhost:{}", opts.port);
88
89 let export_output = Command::new("npm")
91 .args([
92 "exec",
93 "decktape",
94 "--",
95 "reveal",
96 "--fragments",
97 &addr,
98 &opts.output_or_default(),
99 ])
100 .output()?;
101
102 shutdown_flag.store(true, Ordering::SeqCst);
104 let _ = run_handle.join();
106
107 if !export_output.status.success() {
108 eprintln!(
109 "Decktape PDF export failure: {}",
110 String::from_utf8_lossy(&export_output.stderr)
111 );
112 return Err("npm init failed".into());
113 }
114
115 Ok(())
116}