@@ -10,7 +10,7 @@ use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
1010
1111use std:: borrow:: Cow ;
1212use std:: fmt:: Write ;
13- use std:: path:: Path ;
13+ use std:: path:: { Component , Path , PathBuf } ;
1414
1515pub use self :: string:: {
1616 take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
@@ -63,19 +63,55 @@ pub fn id_from_content(content: &str) -> String {
6363 normalize_id ( trimmed)
6464}
6565
66+ /// https://stackoverflow.com/a/68233480
67+ /// Improve the path to try remove and solve .. token. Return the path id
68+ /// by replacing the directory separator with a hyphen.
69+ ///
70+ /// This assumes that `a/b/../c` is `a/c` which might be different from
71+ /// what the OS would have chosen when b is a link. This is OK
72+ /// for broot verb arguments but can't be generally used elsewhere
73+ ///
74+ /// This function ensures a given path ending with '/' will
75+ /// end with '-' after normalization.
76+ pub fn normalize_path_id < P : AsRef < Path > > ( path : P ) -> String {
77+ let ends_with_slash = path. as_ref ( ) . to_str ( ) . map_or ( false , |s| s. ends_with ( '/' ) ) ;
78+ let mut normalized = PathBuf :: new ( ) ;
79+ for component in path. as_ref ( ) . components ( ) {
80+ match & component {
81+ Component :: ParentDir => {
82+ if !normalized. pop ( ) {
83+ normalized. push ( component) ;
84+ }
85+ }
86+ _ => {
87+ normalized. push ( component) ;
88+ }
89+ }
90+ }
91+ if ends_with_slash {
92+ normalized. push ( "" ) ;
93+ }
94+ normalized
95+ . to_str ( )
96+ . unwrap ( )
97+ . replace ( "\\ " , "-" )
98+ . replace ( "/" , "-" )
99+ }
100+
66101/// Fix links to the correct location.
67102///
68103/// This adjusts links, such as turning `.md` extensions to `.html`.
69104///
70105/// `path` is the path to the page being rendered relative to the root of the
71106/// book. This is used for the `print.html` page so that links on the print
72- /// page go to the original location. Normal page rendering sets `path` to
73- /// None. Ideally, print page links would link to anchors on the print page,
74- /// but that is very difficult.
107+ /// page go to the anchors that has a path id prefix. Normal page rendering
108+ /// sets `path` to None.
75109fn adjust_links < ' a > ( event : Event < ' a > , path : Option < & Path > ) -> Event < ' a > {
76110 lazy_static ! {
77111 static ref SCHEME_LINK : Regex = Regex :: new( r"^[a-z][a-z0-9+.-]*:" ) . unwrap( ) ;
78112 static ref MD_LINK : Regex = Regex :: new( r"(?P<link>.*)\.md(?P<anchor>#.*)?" ) . unwrap( ) ;
113+ static ref HTML_MD_LINK : Regex =
114+ Regex :: new( r"(?P<link>.*)\.(html|md)(?P<anchor>#.*)?" ) . unwrap( ) ;
79115 }
80116
81117 fn fix < ' a > ( dest : CowStr < ' a > , path : Option < & Path > ) -> CowStr < ' a > {
@@ -84,9 +120,9 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
84120 if let Some ( path) = path {
85121 let mut base = path. display ( ) . to_string ( ) ;
86122 if base. ends_with ( ".md" ) {
87- base. replace_range ( base. len ( ) - 3 .., ".html " ) ;
123+ base. replace_range ( base. len ( ) - 3 .., "" ) ;
88124 }
89- return format ! ( "{}{}" , base, dest) . into ( ) ;
125+ return format ! ( "# {}{}" , normalize_path_id ( base) , dest. replace ( "#" , "-" ) ) . into ( ) ;
90126 } else {
91127 return dest;
92128 }
@@ -104,18 +140,34 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
104140 if !base. is_empty ( ) {
105141 write ! ( fixed_link, "{}/" , base) . unwrap ( ) ;
106142 }
107- }
108143
109- if let Some ( caps) = MD_LINK . captures ( & dest) {
110- fixed_link. push_str ( & caps[ "link" ] ) ;
111- fixed_link. push_str ( ".html" ) ;
112- if let Some ( anchor) = caps. name ( "anchor" ) {
113- fixed_link. push_str ( anchor. as_str ( ) ) ;
114- }
144+ // In `print.html`, print page links would all link to anchors on the print page.
145+ if let Some ( caps) = HTML_MD_LINK . captures ( & dest) {
146+ fixed_link. push_str ( & caps[ "link" ] ) ;
147+ if let Some ( anchor) = caps. name ( "anchor" ) {
148+ fixed_link. push_str ( anchor. as_str ( ) ) ;
149+ }
150+ } else {
151+ fixed_link. push_str ( & dest) ;
152+ } ;
153+
154+ let mut fixed_anchor_for_print = String :: new ( ) ;
155+ fixed_anchor_for_print. push_str ( "#" ) ;
156+ fixed_anchor_for_print. push_str ( & normalize_path_id ( & fixed_link) . replace ( "#" , "-" ) ) ;
157+ return CowStr :: from ( fixed_anchor_for_print) ;
115158 } else {
116- fixed_link. push_str ( & dest) ;
117- } ;
118- return CowStr :: from ( fixed_link) ;
159+ // In normal page rendering, links to anchors on another page.
160+ if let Some ( caps) = MD_LINK . captures ( & dest) {
161+ fixed_link. push_str ( & caps[ "link" ] ) ;
162+ fixed_link. push_str ( ".html" ) ;
163+ if let Some ( anchor) = caps. name ( "anchor" ) {
164+ fixed_link. push_str ( anchor. as_str ( ) ) ;
165+ }
166+ } else {
167+ fixed_link. push_str ( & dest) ;
168+ } ;
169+ return CowStr :: from ( fixed_link) ;
170+ }
119171 }
120172 dest
121173 }
0 commit comments