Skip to content

Commit 8e17fef

Browse files
authored
Watch dir and add pre-typst command (#30)
Now watches everything in the current directory except files ignored by gitignore. Fixes #28.
1 parent 1f6b045 commit 8e17fef

File tree

4 files changed

+68
-11
lines changed

4 files changed

+68
-11
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ sha2 = "0.10.8"
2121
toml = "0.8"
2222
live-server = "0.10.0"
2323
notify = "8.0"
24-
indoc = "2.0.6"
24+
indoc = "2"
25+
ignore = "0.4"
2526

2627
[dev-dependencies]
2728
assert_cmd = "2"

examples/first.typ

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919

2020
#toolbox.pdfpc.speaker-note("
2121
What if you could easily generate videos from text?
22+
23+
I think that would be pretty cool.
2224
")
2325
]
2426

27+
2528
#slide[
2629
#set page(fill: black, margin: 3em)
2730
#set text(fill: white)
@@ -35,7 +38,12 @@
3538
]
3639

3740
#toolbox.pdfpc.speaker-note("
38-
That would be pretty cool.
3941
Here is a plan to make it happen.
42+
43+
Step 1 is easy. Generate videos.
44+
45+
Step 2 is for you to figure out.
46+
47+
Step 3 is profit.
4048
")
4149
]

src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,18 @@ pub(crate) struct WatchArgs {
117117
/// Port to run the server on.
118118
#[arg(long, default_value = "8080")]
119119
port: u16,
120+
121+
/// Command to run before compiling the Typst file.
122+
#[arg(long)]
123+
pre_typst: Option<String>,
120124
}
121125

122126
#[derive(Clone, Debug, clap::Subcommand)]
123127
enum Task {
124128
/// Build the video.
125129
Build(BuildArgs),
126130

127-
/// Watch the input file and rebuild the video when it changes.
131+
/// Watch the current directory and rebuild the video on change.
128132
Watch(WatchArgs),
129133
}
130134

src/watch.rs

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::build;
22
use crate::slide::Slide;
33
use crate::Arguments;
44
use crate::WatchArgs;
5+
use ignore::Walk;
56
use live_server::listen;
67
use notify::recommended_watcher;
78
use notify::Event;
@@ -139,14 +140,47 @@ fn remove_old_files(args: &Arguments, timestamp: u64) {
139140
}
140141
}
141142

143+
#[derive(Clone, Debug, PartialEq)]
144+
/// Status of the command.
145+
///
146+
/// This can be used to avoid crashing the watch loop completely. Instead,
147+
/// report an error and ignore further actions until the loop is called again.
148+
/// This allows the user to fix the problem and continue without having to
149+
/// manually restart the `trv watch`.
150+
enum Status {
151+
Success,
152+
Failure,
153+
}
154+
155+
fn run_pre_typst(watch_args: &WatchArgs) -> Status {
156+
if let Some(pre_typst) = &watch_args.pre_typst {
157+
tracing::info!("Running pre-typst command...");
158+
let mut cmd = std::process::Command::new("/usr/bin/env");
159+
cmd.arg("bash");
160+
cmd.arg("-c");
161+
cmd.arg(pre_typst);
162+
let output = cmd.output().expect("Failed to run pre-typst command");
163+
if !output.status.success() {
164+
let stderr = String::from_utf8_lossy(&output.stderr);
165+
tracing::error!("pre-typst command failed: {}", stderr.trim());
166+
return Status::Failure;
167+
}
168+
}
169+
Status::Success
170+
}
171+
142172
async fn watch_build(watch_args: &WatchArgs, args: &Arguments) {
143173
let release = false;
144174
let input = watch_args.input.clone();
145175
let audio_codec = None;
146-
let slides = build(input.clone(), args, release, audio_codec).await;
147-
let timestamp = move_files_into_public(args, &slides);
148-
build_index(args, &slides, timestamp, false);
149-
remove_old_files(args, timestamp);
176+
177+
let status = run_pre_typst(watch_args);
178+
if status == Status::Success {
179+
let slides = build(input.clone(), args, release, audio_codec).await;
180+
let timestamp = move_files_into_public(args, &slides);
181+
build_index(args, &slides, timestamp, false);
182+
remove_old_files(args, timestamp);
183+
}
150184
}
151185

152186
fn spawn_server(watch_args: &WatchArgs, args: &Arguments) {
@@ -172,10 +206,20 @@ fn spawn_server(watch_args: &WatchArgs, args: &Arguments) {
172206
pub async fn watch(watch_args: &WatchArgs, args: &Arguments) {
173207
let (tx, rx) = mpsc::channel::<Result<Event>>();
174208
let mut watcher = recommended_watcher(tx).unwrap();
175-
let input = watch_args.input.clone();
176-
watcher
177-
.watch(&input, notify::RecursiveMode::NonRecursive)
178-
.expect("Failed to watch");
209+
let mode = notify::RecursiveMode::NonRecursive;
210+
// Watch the current directory since that is probably the most intuitive
211+
// path to watch. It also would allow watching scripts that are in a
212+
// directory that is above the Typst file. For Typst, files have to be in
213+
// the same directory, but allowing the current directory gives more
214+
// flexibility.
215+
//
216+
// Flatten ignores the errors (e.g., permission errors).
217+
for entry in Walk::new("./").flatten() {
218+
let path = entry.path();
219+
if !path.is_dir() {
220+
watcher.watch(path, mode).expect("Failed to watch");
221+
}
222+
}
179223

180224
let public_path = public_dir(args);
181225
if !public_path.exists() {

0 commit comments

Comments
 (0)