1717//! should catch the majority of "broken link" cases.
1818
1919use std:: cell:: { Cell , RefCell } ;
20+ use std:: collections:: hash_map:: Entry ;
2021use std:: collections:: { HashMap , HashSet } ;
2122use std:: fs;
2223use std:: io:: ErrorKind ;
24+ use std:: iter:: once;
2325use std:: path:: { Component , Path , PathBuf } ;
2426use std:: rc:: Rc ;
2527use std:: time:: Instant ;
@@ -112,6 +114,7 @@ macro_rules! t {
112114
113115struct Cli {
114116 docs : PathBuf ,
117+ link_targets_dirs : Vec < PathBuf > ,
115118}
116119
117120fn 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
172189fn 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
177194struct 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