Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This document contains the help content for the `oseda` command-line program.
* [`oseda check`↴](#oseda-check)
* [`oseda deploy`↴](#oseda-deploy)
* [`oseda fork`↴](#oseda-fork)
* [`oseda export`↴](#oseda-export)

## `oseda`

Expand All @@ -24,6 +25,7 @@ oseda project scafolding CLI
* `check` — Check the Oseda project in the working directory for common errors
* `deploy` — Deploy your Oseda project to github to add to oseda.net
* `fork` — Fork the library repository to submit your course
* `export` — Export the Oseda project to a PDF file This will install the npm package `decktape` This relies on a chromium backend, as a result, it may take a while to run



Expand Down Expand Up @@ -84,6 +86,23 @@ Fork the library repository to submit your course



## `oseda export`

Export the Oseda project to a PDF file This will install the npm package `decktape` This relies on a chromium backend, as a result, it may take a while to run

**Usage:** `oseda export [OPTIONS]`

###### **Options:**

* `--output <OUTPUT>` — String name of the output PDF file

Default value: `slides.pdf`
* `--port <PORT>` — Port the project runs on

Default value: `3000`



<hr/>

<small><i>
Expand Down
2 changes: 2 additions & 0 deletions scripts/test-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ cd test

pwd
./oseda init --title ExampleProject --tags economics ComPuterScience --color red --template MaRKDoWN

mv oseda ExampleProject
3 changes: 3 additions & 0 deletions src/bin/oseda.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use oseda_cli::{
cmd::{
check,
deploy::{self},
export::{self},
fork::{self},
init, run,
},
Expand All @@ -31,6 +32,8 @@ fn main() {
println!("See deployment instructions...");
}),
Commands::Fork => fork::fork(),
Commands::Export(options) => export::export(options.clone())
.map(|_| println!("Successfully export project to {0}", options.port)),
};

// little annoying, but makes the exit code match what users would expect
Expand Down
75 changes: 75 additions & 0 deletions src/cmd/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use std::{
error::Error,
process::Command,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};

use clap::Args;

use crate::{cmd::run, net::kill_port};

/// Options struct for the export subcommand
#[derive(Args, Debug, Clone)]
pub struct ExportOptions {
/// String name of the output PDF file
#[arg(long, default_value = "slides.pdf")]
pub output: String,
/// Port the project runs on
#[arg(long, default_value_t = 3000)]
pub port: u16,
}

/// Export the current Oseda project to a PDF file via `decktape`
pub fn export(opts: ExportOptions) -> Result<(), Box<dyn Error>> {
if kill_port(opts.port).is_err() {
eprintln!("Warning, could not kill value on desired port")
}

let output = Command::new("npm")
.args(["install", "decktape@3.15.0"])
.current_dir(".")
.output()?;

if !output.status.success() {
eprintln!(
"Decktape installation failure: {}",
String::from_utf8_lossy(&output.stderr)
);
return Err("npm init failed".into());
}

// decktape automatic http://localhost:3000/ Desktop/IntroToRust/slides.pdf

let shutdown_flag = Arc::new(AtomicBool::new(false));
let run_flag = shutdown_flag.clone();

let run_handle = std::thread::spawn(move || run::run_with_shutdown(run_flag));

// wait a moment for the localhost server to spin up
std::thread::sleep(std::time::Duration::from_millis(10000));

let addr = format!("http://localhost:{}", opts.port);

// run decktape, assuming the server has spun up by now
let export_output = Command::new("decktape")
.args(["automatic", &addr, &opts.output])
.output()?;

// send shutdown flag, should signal to run_with_shutdown to kill the process
shutdown_flag.store(true, Ordering::SeqCst);
// wait to run to terminate (hopefully gracefully) and join the process to cur. thread
let _ = run_handle.join();

if !export_output.status.success() {
eprintln!(
"Decktape PDF export failure: {}",
String::from_utf8_lossy(&export_output.stderr)
);
return Err("npm init failed".into());
}

Ok(())
}
1 change: 1 addition & 0 deletions src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod check;
pub mod deploy;
pub mod export;
pub mod fork;
pub mod init;
pub mod run;
30 changes: 24 additions & 6 deletions src/cmd/run.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
use std::{process::Command, sync::mpsc};
use std::{
process::Command,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};

/// More in depth errors that could cause a project not to run
#[derive(Debug)]
Expand Down Expand Up @@ -28,6 +35,11 @@ impl std::fmt::Display for OsedaRunError {
/// * `Ok(())` if both the build and serve steps succeed
/// * `Err(OsedaRunError)` if any step fails (missing vite isn't installed, or `serve` fails to start)
pub fn run() -> Result<(), OsedaRunError> {
// todo refactor the other check command to use this
run_with_shutdown(Arc::new(AtomicBool::new(false)))
}

pub fn run_with_shutdown(shutdown_flag: Arc<AtomicBool>) -> Result<(), OsedaRunError> {
// command run failure and command status are considered different, handled accordingly
match Command::new("npx").arg("vite").arg("build").status() {
Ok(status) => {
Expand Down Expand Up @@ -59,15 +71,21 @@ pub fn run() -> Result<(), OsedaRunError> {
// spawn will leave child running the background. Need to listen for ctrl+c, snatch it. Then kill subprocess

// https://github.com/Detegr/rust-ctrlc
let (tx, rx) = mpsc::channel();
// let (tx, rx) = mpsc::channel();
let ctrlc_flag = shutdown_flag.clone();
ctrlc::set_handler(move || {
println!("\nSIGINT received. Attempting graceful shutdown...");
let _ = tx.send(());
ctrlc_flag.store(true, Ordering::SeqCst);
})
.expect("Error setting Ctrl+C handler");
.map_err(|e| {
println!("Error setting ctrl+c handler: {e}");
OsedaRunError::ServeError("failed to set handler".into())
})?;

// block until ctrl+c
rx.recv().unwrap();
// block until ctrl+c or sigkill or flag set otherwise (e.g. via export)
while !shutdown_flag.load(Ordering::SeqCst) {
std::thread::sleep(Duration::from_millis(100));
}

// attempt to kill the child process
if let Err(e) = child.kill() {
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ pub enum Commands {
Deploy(cmd::deploy::DeployOptions),
/// Fork the library repository to submit your course
Fork,
/// Export the Oseda project to a PDF file
/// This will install the npm package `decktape`
/// This relies on a chromium backend, as a result, it may take a while to run
Export(cmd::export::ExportOptions),
}
Loading