Skip to content

Commit 5d44ef9

Browse files
authored
Merge pull request #2796 from ehuss/relative-cmd-preprocessor
Change CmdPreprocessor to use paths relative to the book root
2 parents 4bac548 + e7084e5 commit 5d44ef9

File tree

7 files changed

+137
-67
lines changed

7 files changed

+137
-67
lines changed

crates/mdbook-driver/src/builtin_preprocessors/cmd.rs

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use anyhow::{Context, Result, bail, ensure};
1+
use anyhow::{Context, Result, ensure};
22
use log::{debug, trace, warn};
33
use mdbook_core::book::Book;
44
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
5-
use shlex::Shlex;
65
use std::io::{self, Write};
7-
use std::process::{Child, Command, Stdio};
6+
use std::path::PathBuf;
7+
use std::process::{Child, Stdio};
88

99
/// A custom preprocessor which will shell out to a 3rd-party program.
1010
///
@@ -33,12 +33,13 @@ use std::process::{Child, Command, Stdio};
3333
pub struct CmdPreprocessor {
3434
name: String,
3535
cmd: String,
36+
root: PathBuf,
3637
}
3738

3839
impl CmdPreprocessor {
3940
/// Create a new `CmdPreprocessor`.
40-
pub fn new(name: String, cmd: String) -> CmdPreprocessor {
41-
CmdPreprocessor { name, cmd }
41+
pub fn new(name: String, cmd: String, root: PathBuf) -> CmdPreprocessor {
42+
CmdPreprocessor { name, cmd, root }
4243
}
4344

4445
fn write_input_to_child(&self, child: &mut Child, book: &Book, ctx: &PreprocessorContext) {
@@ -64,22 +65,6 @@ impl CmdPreprocessor {
6465
pub fn cmd(&self) -> &str {
6566
&self.cmd
6667
}
67-
68-
fn command(&self) -> Result<Command> {
69-
let mut words = Shlex::new(&self.cmd);
70-
let executable = match words.next() {
71-
Some(e) => e,
72-
None => bail!("Command string was empty"),
73-
};
74-
75-
let mut cmd = Command::new(executable);
76-
77-
for arg in words {
78-
cmd.arg(arg);
79-
}
80-
81-
Ok(cmd)
82-
}
8368
}
8469

8570
impl Preprocessor for CmdPreprocessor {
@@ -88,12 +73,13 @@ impl Preprocessor for CmdPreprocessor {
8873
}
8974

9075
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
91-
let mut cmd = self.command()?;
76+
let mut cmd = crate::compose_command(&self.cmd, &ctx.root)?;
9277

9378
let mut child = cmd
9479
.stdin(Stdio::piped())
9580
.stdout(Stdio::piped())
9681
.stderr(Stdio::inherit())
82+
.current_dir(&self.root)
9783
.spawn()
9884
.with_context(|| {
9985
format!(
@@ -135,7 +121,7 @@ impl Preprocessor for CmdPreprocessor {
135121
renderer
136122
);
137123

138-
let mut cmd = match self.command() {
124+
let mut cmd = match crate::compose_command(&self.cmd, &self.root) {
139125
Ok(c) => c,
140126
Err(e) => {
141127
warn!(
@@ -153,6 +139,7 @@ impl Preprocessor for CmdPreprocessor {
153139
.stdin(Stdio::null())
154140
.stdout(Stdio::inherit())
155141
.stderr(Stdio::inherit())
142+
.current_dir(&self.root)
156143
.status()
157144
.map(|status| status.code() == Some(0));
158145

@@ -183,8 +170,8 @@ mod tests {
183170

184171
#[test]
185172
fn round_trip_write_and_parse_input() {
186-
let cmd = CmdPreprocessor::new("test".to_string(), "test".to_string());
187173
let md = guide();
174+
let cmd = CmdPreprocessor::new("test".to_string(), "test".to_string(), md.root.clone());
188175
let ctx = PreprocessorContext::new(
189176
md.root.clone(),
190177
md.config.clone(),

crates/mdbook-driver/src/builtin_renderers/mod.rs

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
use anyhow::{Context, Result, bail};
66
use log::{error, info, trace, warn};
77
use mdbook_renderer::{RenderContext, Renderer};
8-
use shlex::Shlex;
98
use std::fs;
109
use std::io::{self, ErrorKind};
11-
use std::path::{Path, PathBuf};
12-
use std::process::{Command, Stdio};
10+
use std::process::Stdio;
1311

1412
pub use self::markdown_renderer::MarkdownRenderer;
1513

@@ -49,30 +47,6 @@ impl CmdRenderer {
4947
pub fn new(name: String, cmd: String) -> CmdRenderer {
5048
CmdRenderer { name, cmd }
5149
}
52-
53-
fn compose_command(&self, root: &Path) -> Result<Command> {
54-
let mut words = Shlex::new(&self.cmd);
55-
let exe = match words.next() {
56-
Some(e) => PathBuf::from(e),
57-
None => bail!("Command string was empty"),
58-
};
59-
60-
let exe = if exe.components().count() == 1 {
61-
// Search PATH for the executable.
62-
exe
63-
} else {
64-
// Relative path is relative to book root.
65-
root.join(&exe)
66-
};
67-
68-
let mut cmd = Command::new(exe);
69-
70-
for arg in words {
71-
cmd.arg(arg);
72-
}
73-
74-
Ok(cmd)
75-
}
7650
}
7751

7852
impl CmdRenderer {
@@ -120,8 +94,8 @@ impl Renderer for CmdRenderer {
12094

12195
let _ = fs::create_dir_all(&ctx.destination);
12296

123-
let mut child = match self
124-
.compose_command(&ctx.root)?
97+
let mut cmd = crate::compose_command(&self.cmd, &ctx.root)?;
98+
let mut child = match cmd
12599
.stdin(Stdio::piped())
126100
.stdout(Stdio::inherit())
127101
.stderr(Stdio::inherit())

crates/mdbook-driver/src/lib.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,34 @@ pub mod init;
6464
mod load;
6565
mod mdbook;
6666

67+
use anyhow::{Result, bail};
6768
pub use mdbook::MDBook;
6869
pub use mdbook_core::{book, config, errors};
70+
use shlex::Shlex;
71+
use std::path::{Path, PathBuf};
72+
use std::process::Command;
73+
74+
/// Creates a [`Command`] for command renderers and preprocessors.
75+
fn compose_command(cmd: &str, root: &Path) -> Result<Command> {
76+
let mut words = Shlex::new(cmd);
77+
let exe = match words.next() {
78+
Some(e) => PathBuf::from(e),
79+
None => bail!("Command string was empty"),
80+
};
81+
82+
let exe = if exe.components().count() == 1 {
83+
// Search PATH for the executable.
84+
exe
85+
} else {
86+
// Relative path is relative to book root.
87+
root.join(&exe)
88+
};
89+
90+
let mut cmd = Command::new(exe);
91+
92+
for arg in words {
93+
cmd.arg(arg);
94+
}
95+
96+
Ok(cmd)
97+
}

crates/mdbook-driver/src/mdbook.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl MDBook {
7070
let book = load_book(src_dir, &config.build)?;
7171

7272
let renderers = determine_renderers(&config)?;
73-
let preprocessors = determine_preprocessors(&config)?;
73+
let preprocessors = determine_preprocessors(&config, &root)?;
7474

7575
Ok(MDBook {
7676
root,
@@ -93,7 +93,7 @@ impl MDBook {
9393
let book = load_book_from_disk(&summary, src_dir)?;
9494

9595
let renderers = determine_renderers(&config)?;
96-
let preprocessors = determine_preprocessors(&config)?;
96+
let preprocessors = determine_preprocessors(&config, &root)?;
9797

9898
Ok(MDBook {
9999
root,
@@ -258,7 +258,7 @@ impl MDBook {
258258

259259
// Index Preprocessor is disabled so that chapter paths
260260
// continue to point to the actual markdown files.
261-
self.preprocessors = determine_preprocessors(&self.config)?
261+
self.preprocessors = determine_preprocessors(&self.config, &self.root)?
262262
.into_iter()
263263
.filter(|pre| pre.name() != IndexPreprocessor::NAME)
264264
.collect();
@@ -440,7 +440,7 @@ struct PreprocessorConfig {
440440
}
441441

442442
/// Look at the `MDBook` and try to figure out what preprocessors to run.
443-
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<dyn Preprocessor>>> {
443+
fn determine_preprocessors(config: &Config, root: &Path) -> Result<Vec<Box<dyn Preprocessor>>> {
444444
// Collect the names of all preprocessors intended to be run, and the order
445445
// in which they should be run.
446446
let mut preprocessor_names = TopologicalSort::<String>::new();
@@ -513,7 +513,7 @@ fn determine_preprocessors(config: &Config) -> Result<Vec<Box<dyn Preprocessor>>
513513
.command
514514
.to_owned()
515515
.unwrap_or_else(|| format!("mdbook-{name}"));
516-
Box::new(CmdPreprocessor::new(name, command))
516+
Box::new(CmdPreprocessor::new(name, command, root.to_owned()))
517517
}
518518
};
519519
preprocessors.push(preprocessor);

crates/mdbook-driver/src/mdbook/tests.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn config_defaults_to_link_and_index_preprocessor_if_not_set() {
4747
// make sure we haven't got anything in the `preprocessor` table
4848
assert!(cfg.preprocessors::<toml::Value>().unwrap().is_empty());
4949

50-
let got = determine_preprocessors(&cfg);
50+
let got = determine_preprocessors(&cfg, Path::new(""));
5151

5252
assert!(got.is_ok());
5353
assert_eq!(got.as_ref().unwrap().len(), 2);
@@ -60,7 +60,7 @@ fn use_default_preprocessors_works() {
6060
let mut cfg = Config::default();
6161
cfg.build.use_default_preprocessors = false;
6262

63-
let got = determine_preprocessors(&cfg).unwrap();
63+
let got = determine_preprocessors(&cfg, Path::new("")).unwrap();
6464

6565
assert_eq!(got.len(), 0);
6666
}
@@ -83,7 +83,7 @@ fn can_determine_third_party_preprocessors() {
8383
// make sure the `preprocessor.random` table exists
8484
assert!(cfg.get::<Value>("preprocessor.random").unwrap().is_some());
8585

86-
let got = determine_preprocessors(&cfg).unwrap();
86+
let got = determine_preprocessors(&cfg, Path::new("")).unwrap();
8787

8888
assert!(got.into_iter().any(|p| p.name() == "random"));
8989
}
@@ -114,7 +114,7 @@ fn preprocessor_before_must_be_array() {
114114

115115
let cfg = Config::from_str(cfg_str).unwrap();
116116

117-
assert!(determine_preprocessors(&cfg).is_err());
117+
assert!(determine_preprocessors(&cfg, Path::new("")).is_err());
118118
}
119119

120120
#[test]
@@ -126,7 +126,7 @@ fn preprocessor_after_must_be_array() {
126126

127127
let cfg = Config::from_str(cfg_str).unwrap();
128128

129-
assert!(determine_preprocessors(&cfg).is_err());
129+
assert!(determine_preprocessors(&cfg, Path::new("")).is_err());
130130
}
131131

132132
#[test]
@@ -142,7 +142,7 @@ fn preprocessor_order_is_honored() {
142142

143143
let cfg = Config::from_str(cfg_str).unwrap();
144144

145-
let preprocessors = determine_preprocessors(&cfg).unwrap();
145+
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
146146
let index = |name| {
147147
preprocessors
148148
.iter()
@@ -179,7 +179,7 @@ fn cyclic_dependencies_are_detected() {
179179

180180
let cfg = Config::from_str(cfg_str).unwrap();
181181

182-
assert!(determine_preprocessors(&cfg).is_err());
182+
assert!(determine_preprocessors(&cfg, Path::new("")).is_err());
183183
}
184184

185185
#[test]
@@ -191,7 +191,7 @@ fn dependencies_dont_register_undefined_preprocessors() {
191191

192192
let cfg = Config::from_str(cfg_str).unwrap();
193193

194-
let preprocessors = determine_preprocessors(&cfg).unwrap();
194+
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
195195

196196
assert!(
197197
!preprocessors
@@ -212,7 +212,7 @@ fn dependencies_dont_register_builtin_preprocessors_if_disabled() {
212212

213213
let cfg = Config::from_str(cfg_str).unwrap();
214214

215-
let preprocessors = determine_preprocessors(&cfg).unwrap();
215+
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
216216

217217
assert!(
218218
!preprocessors

tests/testsuite/book_test.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,26 @@ impl BookTest {
247247
self
248248
}
249249

250+
/// Removes a file or directory relative to the test root.
251+
pub fn rm_r(&mut self, path: impl AsRef<Path>) -> &mut Self {
252+
let path = self.dir.join(path.as_ref());
253+
let meta = match path.symlink_metadata() {
254+
Ok(meta) => meta,
255+
Err(e) => panic!("failed to remove {path:?}, could not read: {e:?}"),
256+
};
257+
// There is a race condition between fetching the metadata and
258+
// actually performing the removal, but we don't care all that much
259+
// for our tests.
260+
if meta.is_dir() {
261+
if let Err(e) = std::fs::remove_dir_all(&path) {
262+
panic!("failed to remove {path:?}: {e:?}");
263+
}
264+
} else if let Err(e) = std::fs::remove_file(&path) {
265+
panic!("failed to remove {path:?}: {e:?}")
266+
}
267+
self
268+
}
269+
250270
/// Builds a Rust program with the given src.
251271
///
252272
/// The given path should be the path where to output the executable in
@@ -319,6 +339,12 @@ impl BookCommand {
319339
self
320340
}
321341

342+
/// Sets the directory used for running the command.
343+
pub fn current_dir<S: AsRef<std::path::Path>>(&mut self, path: S) -> &mut Self {
344+
self.dir = self.dir.join(path.as_ref());
345+
self
346+
}
347+
322348
/// Use this to debug a command.
323349
///
324350
/// Pass the value that you would normally pass to `RUST_LOG`, and this

0 commit comments

Comments
 (0)