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