Skip to main content

oseda_cli/cmd/
export.rs

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    net::kill_port,
15    puppeteer::{is_puppeteer_chrome_installed, prompt_install_puppeteer_chrome},
16};
17
18/// Options struct for the export subcommand
19#[derive(Args, Debug, Clone)]
20pub struct ExportOptions {
21    /// String name of the output PDF file
22    #[arg(long, default_value = "slides.pdf")]
23    pub output: String,
24    /// Port the project runs on
25    #[arg(long, default_value_t = 3000)]
26    pub port: u16,
27}
28
29/// Export the current Oseda project to a PDF file via `decktape`
30pub fn export(opts: ExportOptions) -> Result<(), Box<dyn Error>> {
31    println!("Cleaning any existing oseda processing...");
32    if kill_port(opts.port).is_err() {
33        eprintln!("Warning, could not kill value on desired port")
34    }
35
36    let output = Command::new("npm")
37        .args(["install", "decktape@3.15.0"])
38        .current_dir(".")
39        .output()?;
40
41    // prompt for puppeteer and confirm installation
42    if !is_puppeteer_chrome_installed() {
43        prompt_install_puppeteer_chrome()?;
44    }
45    println!("Puppeteer Chrome is installed, continuing...");
46
47    if !output.status.success() {
48        eprintln!(
49            "Decktape installation failure: {}",
50            String::from_utf8_lossy(&output.stderr)
51        );
52        return Err("npm init failed".into());
53    }
54
55    // decktape automatic http://localhost:3000/ Desktop/IntroToRust/slides.pdf
56
57    let shutdown_flag = Arc::new(AtomicBool::new(false));
58    let run_flag = shutdown_flag.clone();
59
60    let run_handle = std::thread::spawn(move || run::run_with_shutdown(run_flag));
61
62    // wait a moment for the localhost server to spin up
63    std::thread::sleep(std::time::Duration::from_millis(10000));
64
65    let addr = format!("http://localhost:{}", opts.port);
66
67    // run decktape, assuming the server has spun up by now
68    let export_output = Command::new("npm")
69        .args(["exec", "decktape", "reveal", &addr, &opts.output])
70        .output()?;
71
72    // send shutdown flag, should signal to run_with_shutdown to kill the process
73    shutdown_flag.store(true, Ordering::SeqCst);
74    // wait to run to terminate (hopefully gracefully) and join the process to cur. thread
75    let _ = run_handle.join();
76
77    if !export_output.status.success() {
78        eprintln!(
79            "Decktape PDF export failure: {}",
80            String::from_utf8_lossy(&export_output.stderr)
81        );
82        return Err("npm init failed".into());
83    }
84
85    Ok(())
86}