Skip to content

Commit 2995467

Browse files
committed
add --link-targets-dir flag to linkchecker
1 parent 97e9d74 commit 2995467

File tree

1 file changed

+35
-6
lines changed

1 file changed

+35
-6
lines changed

src/tools/linkchecker/main.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
//! should catch the majority of "broken link" cases.
1818
1919
use std::cell::{Cell, RefCell};
20+
use std::collections::hash_map::Entry;
2021
use std::collections::{HashMap, HashSet};
2122
use std::fs;
2223
use std::io::ErrorKind;
24+
use std::iter::once;
2325
use std::path::{Component, Path, PathBuf};
2426
use std::rc::Rc;
2527
use std::time::Instant;
@@ -112,6 +114,7 @@ macro_rules! t {
112114

113115
struct Cli {
114116
docs: PathBuf,
117+
link_targets_dirs: Vec<PathBuf>,
115118
}
116119

117120
fn main() {
@@ -123,7 +126,11 @@ fn main() {
123126
}
124127
};
125128

126-
let mut checker = Checker { root: cli.docs.clone(), cache: HashMap::new() };
129+
let mut checker = Checker {
130+
root: cli.docs.clone(),
131+
link_targets_dirs: cli.link_targets_dirs,
132+
cache: HashMap::new(),
133+
};
127134
let mut report = Report {
128135
errors: 0,
129136
start: Instant::now(),
@@ -150,13 +157,20 @@ fn parse_cli() -> Result<Cli, String> {
150157

151158
let mut verbatim = false;
152159
let mut docs = None;
160+
let mut link_targets_dirs = Vec::new();
153161

154162
let mut args = std::env::args().skip(1);
155163
while let Some(arg) = args.next() {
156164
if !verbatim && arg == "--" {
157165
verbatim = true;
158166
} else if !verbatim && (arg == "-h" || arg == "--help") {
159167
usage_and_exit(0)
168+
} else if !verbatim && arg == "--link-targets-dir" {
169+
link_targets_dirs.push(to_canonical_path(
170+
&args.next().ok_or("missing value for --link-targets-dir")?,
171+
)?);
172+
} else if !verbatim && let Some(value) = arg.strip_prefix("--link-targets-dir=") {
173+
link_targets_dirs.push(to_canonical_path(value)?);
160174
} else if !verbatim && arg.starts_with('-') {
161175
return Err(format!("unknown flag: {arg}"));
162176
} else if docs.is_none() {
@@ -166,16 +180,20 @@ fn parse_cli() -> Result<Cli, String> {
166180
}
167181
}
168182

169-
Ok(Cli { docs: to_canonical_path(&docs.ok_or("missing first positional argument")?)? })
183+
Ok(Cli {
184+
docs: to_canonical_path(&docs.ok_or("missing first positional argument")?)?,
185+
link_targets_dirs,
186+
})
170187
}
171188

172189
fn usage_and_exit(code: i32) -> ! {
173-
eprintln!("usage: linkchecker <path>");
190+
eprintln!("usage: linkchecker PATH [--link-targets-dir=PATH ...]");
174191
std::process::exit(code)
175192
}
176193

177194
struct Checker {
178195
root: PathBuf,
196+
link_targets_dirs: Vec<PathBuf>,
179197
cache: Cache,
180198
}
181199

@@ -468,15 +486,23 @@ impl Checker {
468486
let pretty_path =
469487
file.strip_prefix(&self.root).unwrap_or(file).to_str().unwrap().to_string();
470488

471-
let entry =
472-
self.cache.entry(pretty_path.clone()).or_insert_with(|| match fs::metadata(file) {
489+
for base in once(&self.root).chain(self.link_targets_dirs.iter()) {
490+
let entry = self.cache.entry(pretty_path.clone());
491+
if let Entry::Occupied(e) = &entry
492+
&& !matches!(e.get(), FileEntry::Missing)
493+
{
494+
break;
495+
}
496+
497+
let file = base.join(&pretty_path);
498+
entry.insert_entry(match fs::metadata(&file) {
473499
Ok(metadata) if metadata.is_dir() => FileEntry::Dir,
474500
Ok(_) => {
475501
if file.extension().and_then(|s| s.to_str()) != Some("html") {
476502
FileEntry::OtherFile
477503
} else {
478504
report.html_files += 1;
479-
load_html_file(file, report)
505+
load_html_file(&file, report)
480506
}
481507
}
482508
Err(e) if e.kind() == ErrorKind::NotFound => FileEntry::Missing,
@@ -492,6 +518,9 @@ impl Checker {
492518
panic!("unexpected read error for {}: {}", file.display(), e);
493519
}
494520
});
521+
}
522+
523+
let entry = self.cache.get(&pretty_path).unwrap();
495524
(pretty_path, entry)
496525
}
497526
}

0 commit comments

Comments
 (0)