@@ -87,6 +87,9 @@ pub fn std_links(chapter: &Chapter) -> String {
8787 // Append the link definitions to the bottom of the chapter.
8888 write ! ( output, "\n " ) . unwrap ( ) ;
8989 for ( ( link, dest) , url) in links. iter ( ) . zip ( urls) {
90+ // Convert links to be relative so that links work offline and
91+ // with the linkchecker.
92+ let url = relative_url ( url, chapter) ;
9093 if let Some ( dest) = dest {
9194 write ! ( output, "[{dest}]: {url}\n " ) . unwrap ( ) ;
9295 } else {
@@ -188,3 +191,24 @@ fn run_rustdoc(tmp: &TempDir, links: &[(&str, Option<&str>)], chapter: &Chapter)
188191 process:: exit ( 1 ) ;
189192 }
190193}
194+
195+ static DOC_URL : Lazy < Regex > = Lazy :: new ( || {
196+ Regex :: new ( r"^https://doc.rust-lang.org/(?:nightly|beta|stable|dev|1\.[0-9]+\.[0-9]+)" ) . unwrap ( )
197+ } ) ;
198+
199+ /// Converts a URL to doc.rust-lang.org to be relative.
200+ fn relative_url ( url : & str , chapter : & Chapter ) -> String {
201+ // Set SPEC_RELATIVE=0 to disable this, which can be useful for working locally.
202+ if std:: env:: var ( "SPEC_RELATIVE" ) . as_deref ( ) != Ok ( "0" ) {
203+ let Some ( url_start) = DOC_URL . shortest_match ( url) else {
204+ eprintln ! ( "expected rustdoc URL to start with {DOC_URL:?}, got {url}" ) ;
205+ std:: process:: exit ( 1 ) ;
206+ } ;
207+ let url_path = & url[ url_start..] ;
208+ let num_dots = chapter. path . as_ref ( ) . unwrap ( ) . components ( ) . count ( ) ;
209+ let dots = vec ! [ ".." ; num_dots] . join ( "/" ) ;
210+ format ! ( "{dots}{url_path}" )
211+ } else {
212+ url. to_string ( )
213+ }
214+ }
0 commit comments